-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathAssetBundleManager.cs
More file actions
559 lines (467 loc) · 17.6 KB
/
AssetBundleManager.cs
File metadata and controls
559 lines (467 loc) · 17.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
using System;
/*
In this demo, we demonstrate:
1. Automatic asset bundle dependency resolving & loading.
It shows how to use the manifest assetbundle like how to get the dependencies etc.
2. Automatic unloading of asset bundles (When an asset bundle or a dependency thereof is no longer needed, the asset bundle is unloaded)
3. Editor simulation. A bool defines if we load asset bundles from the project or are actually using asset bundles(doesn't work with assetbundle variants for now.)
With this, you can player in editor mode without actually building the assetBundles.
4. Optional setup where to download all asset bundles
5. Build pipeline build postprocessor, integration so that building a player builds the asset bundles and puts them into the player data (Default implmenetation for loading assetbundles from disk on any platform)
6. Use WWW.LoadFromCacheOrDownload and feed 128 bit hash to it when downloading via web
You can get the hash from the manifest assetbundle.
7. AssetBundle variants. A prioritized list of variants that should be used if the asset bundle with that variant exists, first variant in the list is the most preferred etc.
*/
namespace AssetBundles
{
// Class takes care of loading assetBundle and its dependencies automatically, loading variants automatically.
public class AssetBundleManager : MonoBehaviour
{
#region Constants
#if UNITY_EDITOR
const string kSimulateAssetBundles = "SimulateAssetBundles";
#endif
#endregion
#region Data
public static AssetBundleManager instance;
static LogMode m_LogMode = LogMode.All;
static string m_BaseDownloadingURL = "";
static string[] m_ActiveVariants = { };
static AssetBundleManifest m_AssetBundleManifest = null;
static Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle> ();
static Dictionary<string, WWW> m_DownloadingWWWs = new Dictionary<string, WWW> ();
static Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string> ();
static List<AssetBundleLoadOperation> m_InProgressOperations = new List<AssetBundleLoadOperation> ();
static Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]> ();
#if UNITY_EDITOR
static int m_SimulateAssetBundleInEditor = -1;
#endif
#endregion
#region Properties
internal static LogMode logMode
{
get { return m_LogMode; }
set { m_LogMode = value; }
}
// The base downloading url which is used to generate the full downloading url with the assetBundle names.
internal static string BaseDownloadingURL
{
get { return m_BaseDownloadingURL; }
set { m_BaseDownloadingURL = value; }
}
// Variants which is used to define the active variants.
internal static string[] ActiveVariants
{
get { return m_ActiveVariants; }
set { m_ActiveVariants = value; }
}
// AssetBundleManifest object which can be used to load the dependecies and check suitable assetBundle variants.
internal static AssetBundleManifest AssetBundleManifestObject
{
set {m_AssetBundleManifest = value; }
}
#endregion
public static void Init()
{
instance = new GameObject("AssetBundleManager", typeof(AssetBundleManager)).GetComponent<AssetBundleManager>();
instance.StartCoroutine(instance.InitRoutine());
}
IEnumerator InitRoutine()
{
SetSourceAssetBundleURL("http://192.168.0.101:7888/");
yield return Initialize();
}
#region Public API
/// <summary>
/// Load asset from the given assetBundle.
/// </summary>
public void LoadAssetAsync<TAsset>(
string assetBundleName,
string assetName,
Action<TAsset> onLoad)
where TAsset : UnityEngine.Object
{
StartCoroutine(LoadAssetAsyncRoutine(
assetBundleName, assetName, onLoad));
}
/// <summary>
/// Load level from the given assetBundle.
/// </summary>
public static AssetBundleLoadOperation LoadLevelAsync(
string assetBundleName,
string levelName,
bool isAdditive)
{
Log(LogType.Info, "Loading " + levelName + " from " + assetBundleName + " bundle");
AssetBundleLoadOperation operation = null;
#if UNITY_EDITOR
if (SimulateAssetBundleInEditor)
{
operation = new AssetBundleLoadLevelSimulationOperation(assetBundleName, levelName, isAdditive);
}
else
#endif
{
//assetBundleName = RemapVariantName(assetBundleName);
LoadAssetBundle(assetBundleName);
operation = new AssetBundleLoadLevelOperation(assetBundleName, levelName, isAdditive);
m_InProgressOperations.Add(operation);
}
return operation;
}
#endregion
IEnumerator LoadAssetAsyncRoutine<TAsset>(
string assetBundleName,
string assetName,
Action<TAsset> onLoad)
where TAsset : UnityEngine.Object
{
AssetBundleLoadAssetOperation loadAsset = LoadAssetAsync<TAsset>(
assetBundleName, assetName);
yield return loadAsset;
onLoad?.Invoke(loadAsset.GetAsset<TAsset>());
}
static AssetBundleLoadAssetOperation LoadAssetAsync<TAsset>(
string assetBundleName,
string assetName)
{
Log(LogType.Info, "Loading " + assetName + " from " + assetBundleName + " bundle");
AssetBundleLoadAssetOperation operation = null;
#if UNITY_EDITOR
if (SimulateAssetBundleInEditor)
{
string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(
assetBundleName, assetName);
if (assetPaths.Length == 0)
{
Debug.LogError("There is no asset with name \"" + assetName + "\" in " + assetBundleName);
return null;
}
// @TODO: Now we only get the main object from the first asset. Should consider type also.
UnityEngine.Object target = AssetDatabase.LoadMainAssetAtPath(assetPaths[0]);
operation = new AssetBundleLoadAssetOperationSimulation(target);
}
else
#endif
{
//assetBundleName = RemapVariantName(assetBundleName);
LoadAssetBundle(assetBundleName);
operation = AssetBundleLoadAssetOperationFull.Create<TAsset>(assetBundleName, assetName);
m_InProgressOperations.Add(operation);
}
return operation;
}
private static void Log(LogType logType, string text)
{
if (logType == LogType.Error)
Debug.LogError("[AssetBundleManager] " + text);
else if (m_LogMode == LogMode.All)
Debug.Log("[AssetBundleManager] " + text);
}
#if UNITY_EDITOR
// Flag to indicate if we want to simulate assetBundles in Editor without building them actually.
public static bool SimulateAssetBundleInEditor
{
get
{
if (m_SimulateAssetBundleInEditor == -1)
m_SimulateAssetBundleInEditor = EditorPrefs.GetBool(kSimulateAssetBundles, true) ? 1 : 0;
return m_SimulateAssetBundleInEditor != 0;
}
set
{
int newValue = value ? 1 : 0;
if (newValue != m_SimulateAssetBundleInEditor)
{
m_SimulateAssetBundleInEditor = newValue;
EditorPrefs.SetBool(kSimulateAssetBundles, value);
}
}
}
#endif
private static string GetStreamingAssetsPath()
{
if (Application.isEditor)
return "file://" + System.Environment.CurrentDirectory.Replace("\\", "/"); // Use the build output folder directly.
else if (Application.platform == RuntimePlatform.WebGLPlayer)
return System.IO.Path.GetDirectoryName(Application.absoluteURL).Replace("\\", "/")+ "/StreamingAssets";
else if (Application.isMobilePlatform || Application.isConsolePlatform)
return Application.streamingAssetsPath;
else // For standalone player.
return "file://" + Application.streamingAssetsPath;
}
public static void SetSourceAssetBundleDirectory(string relativePath)
{
BaseDownloadingURL = GetStreamingAssetsPath() + relativePath;
}
public static void SetSourceAssetBundleURL(string absolutePath)
{
BaseDownloadingURL = absolutePath + Utility.GetPlatformName() + "/";
}
public static void SetDevelopmentAssetBundleServer()
{
#if UNITY_EDITOR
// If we're in Editor simulation mode, we don't have to setup a download URL
if (SimulateAssetBundleInEditor)
return;
#endif
TextAsset urlFile = Resources.Load("AssetBundleServerURL") as TextAsset;
string url = (urlFile != null) ? urlFile.text.Trim() : null;
if (url == null || url.Length == 0)
{
Debug.LogError("Development Server URL could not be found.");
//AssetBundleManager.SetSourceAssetBundleURL("http://localhost:7888/" + UnityHelper.GetPlatformName() + "/");
}
else
{
AssetBundleManager.SetSourceAssetBundleURL(url);
}
}
// Get loaded AssetBundle, only return vaild object when all the dependencies are downloaded successfully.
static public LoadedAssetBundle GetLoadedAssetBundle (string assetBundleName, out string error)
{
if (m_DownloadingErrors.TryGetValue(assetBundleName, out error) )
return null;
LoadedAssetBundle bundle = null;
m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
if (bundle == null)
return null;
// No dependencies are recorded, only the bundle itself is required.
string[] dependencies = null;
if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies) )
return bundle;
// Make sure all dependencies are loaded
foreach(var dependency in dependencies)
{
if (m_DownloadingErrors.TryGetValue(assetBundleName, out error) )
return bundle;
// Wait all the dependent assetBundles being loaded.
LoadedAssetBundle dependentBundle;
m_LoadedAssetBundles.TryGetValue(dependency, out dependentBundle);
if (dependentBundle == null)
return null;
}
return bundle;
}
static public AssetBundleLoadManifestOperation Initialize ()
{
instance = new GameObject("AssetBundleManager", typeof(AssetBundleManager)).GetComponent<AssetBundleManager>();
return instance.Initialize(Utility.GetPlatformName());
}
// Load AssetBundleManifest.
public AssetBundleLoadManifestOperation Initialize (
string manifestAssetBundleName)
{
#if UNITY_EDITOR
Log (LogType.Info, "Simulation Mode: " + (SimulateAssetBundleInEditor ? "Enabled" : "Disabled"));
#endif
DontDestroyOnLoad(instance.gameObject);
#if UNITY_EDITOR
// If we're in Editor simulation mode, we don't need the manifest assetBundle.
if (SimulateAssetBundleInEditor)
return null;
#endif
LoadAssetBundle(manifestAssetBundleName, true);
var operation = new AssetBundleLoadManifestOperation (manifestAssetBundleName, "AssetBundleManifest", typeof(AssetBundleManifest));
m_InProgressOperations.Add (operation);
return operation;
}
// Load AssetBundle and its dependencies.
static protected void LoadAssetBundle(string assetBundleName, bool isLoadingAssetBundleManifest = false)
{
Log(LogType.Info, "Loading Asset Bundle " + (isLoadingAssetBundleManifest ? "Manifest: " : ": ") + assetBundleName);
#if UNITY_EDITOR
// If we're in Editor simulation mode, we don't have to really load the assetBundle and its dependencies.
if (SimulateAssetBundleInEditor)
return;
#endif
if (!isLoadingAssetBundleManifest)
{
if (m_AssetBundleManifest == null)
{
Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
return;
}
}
// Check if the assetBundle has already been processed.
bool isAlreadyProcessed = LoadAssetBundleInternal(assetBundleName, isLoadingAssetBundleManifest);
// Load dependencies.
if (!isAlreadyProcessed && !isLoadingAssetBundleManifest)
LoadDependencies(assetBundleName);
}
// Remaps the asset bundle name to the best fitting asset bundle variant.
static protected string RemapVariantName(string assetBundleName)
{
string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant();
string[] split = assetBundleName.Split('.');
int bestFit = int.MaxValue;
int bestFitIndex = -1;
// Loop all the assetBundles with variant to find the best fit variant assetBundle.
for (int i = 0; i < bundlesWithVariant.Length; i++)
{
string[] curSplit = bundlesWithVariant[i].Split('.');
if (curSplit[0] != split[0])
continue;
int found = System.Array.IndexOf(m_ActiveVariants, curSplit[1]);
// If there is no active variant found. We still want to use the first
if (found == -1)
found = int.MaxValue-1;
if (found < bestFit)
{
bestFit = found;
bestFitIndex = i;
}
}
if (bestFit == int.MaxValue-1)
{
Debug.LogWarning("Ambigious asset bundle variant chosen because there was no matching active variant: " + bundlesWithVariant[bestFitIndex]);
}
if (bestFitIndex != -1)
{
return bundlesWithVariant[bestFitIndex];
}
else
{
return assetBundleName;
}
}
// Where we actuall call WWW to download the assetBundle.
static protected bool LoadAssetBundleInternal (string assetBundleName, bool isLoadingAssetBundleManifest)
{
// Already loaded.
LoadedAssetBundle bundle = null;
m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
if (bundle != null)
{
bundle.m_ReferencedCount++;
return true;
}
// @TODO: Do we need to consider the referenced count of WWWs?
// In the demo, we never have duplicate WWWs as we wait LoadAssetAsync()/LoadLevelAsync() to be finished before calling another LoadAssetAsync()/LoadLevelAsync().
// But in the real case, users can call LoadAssetAsync()/LoadLevelAsync() several times then wait them to be finished which might have duplicate WWWs.
if (m_DownloadingWWWs.ContainsKey(assetBundleName) )
return true;
WWW download = null;
string url = m_BaseDownloadingURL + assetBundleName;
download = new WWW(url);
//// For manifest assetbundle, always download it as we don't have hash for it.
//if (isLoadingAssetBundleManifest)
//else
// download = WWW.LoadFromCacheOrDownload(url, m_AssetBundleManifest.GetAssetBundleHash(assetBundleName), 0);
m_DownloadingWWWs.Add(assetBundleName, download);
return false;
}
// Where we get all the dependencies and load them all.
static protected void LoadDependencies(string assetBundleName)
{
if (m_AssetBundleManifest == null)
{
Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
return;
}
// Get dependecies from the AssetBundleManifest object..
string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
if (dependencies.Length == 0)
return;
for (int i=0;i<dependencies.Length;i++)
dependencies[i] = RemapVariantName (dependencies[i]);
// Record and load all dependencies.
m_Dependencies.Add(assetBundleName, dependencies);
for (int i=0;i<dependencies.Length;i++)
LoadAssetBundleInternal(dependencies[i], false);
}
// Unload assetbundle and its dependencies.
static public void UnloadAssetBundle(string assetBundleName)
{
#if UNITY_EDITOR
// If we're in Editor simulation mode, we don't have to load the manifest assetBundle.
if (SimulateAssetBundleInEditor)
return;
#endif
//Debug.Log(m_LoadedAssetBundles.Count + " assetbundle(s) in memory before unloading " + assetBundleName);
UnloadAssetBundleInternal(assetBundleName);
UnloadDependencies(assetBundleName);
//Debug.Log(m_LoadedAssetBundles.Count + " assetbundle(s) in memory after unloading " + assetBundleName);
}
static protected void UnloadDependencies(string assetBundleName)
{
string[] dependencies = null;
if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies) )
return;
// Loop dependencies.
foreach(var dependency in dependencies)
{
UnloadAssetBundleInternal(dependency);
}
m_Dependencies.Remove(assetBundleName);
}
static protected void UnloadAssetBundleInternal(string assetBundleName)
{
string error;
LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
if (bundle == null)
return;
if (--bundle.m_ReferencedCount == 0)
{
bundle.m_AssetBundle.Unload(false);
m_LoadedAssetBundles.Remove(assetBundleName);
Log(LogType.Info, assetBundleName + " has been unloaded successfully");
}
}
#region Events
protected void Update()
{
// Collect all the finished WWWs.
var keysToRemove = new List<string>();
foreach (var keyValue in m_DownloadingWWWs)
{
WWW download = keyValue.Value;
// If downloading fails.
if (download.error != null)
{
m_DownloadingErrors.Add(keyValue.Key, string.Format("Failed downloading bundle {0} from {1}: {2}", keyValue.Key, download.url, download.error));
keysToRemove.Add(keyValue.Key);
continue;
}
// If downloading succeeds.
if(download.isDone)
{
AssetBundle bundle = download.assetBundle;
if (bundle == null)
{
m_DownloadingErrors.Add(keyValue.Key, string.Format("{0} is not a valid asset bundle.", keyValue.Key));
keysToRemove.Add(keyValue.Key);
continue;
}
//Debug.Log("Downloading " + keyValue.Key + " is done at frame " + Time.frameCount);
m_LoadedAssetBundles.Add(keyValue.Key, new LoadedAssetBundle(download.assetBundle) );
keysToRemove.Add(keyValue.Key);
}
}
// Remove the finished WWWs.
foreach( var key in keysToRemove)
{
WWW download = m_DownloadingWWWs[key];
m_DownloadingWWWs.Remove(key);
download.Dispose();
}
// Update all in progress operations
for (int i=0;i<m_InProgressOperations.Count;)
{
if (!m_InProgressOperations[i].Update())
{
m_InProgressOperations.RemoveAt(i);
}
else
i++;
}
}
#endregion
}
}