Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Spoofing Asset Bundles in Editor

Discussion in 'Asset Bundles' started by Murrrly, Feb 28, 2019.

  1. Murrrly

    Murrrly

    Joined:
    Apr 25, 2013
    Posts:
    16
    I'm starting a new project soon in which I will be utilizing AssetBundles to access all my art assets for the many different characters in the game.

    I've created a tool for use in the editor so artists, adding or changing things, wont have to rebuild the asset bundles in order to test them in the editor. My question is, is this a good idea or have I missed something that would already do this for me?

    This is the script that is used to retrieve assets, it uses AssetBundles when built (or manually enabled in the editor) and will use AssetDatabase to retrieve them while testing in editor.
    Code (CSharp):
    1. //#define USE_BUNDLES
    2. using System.Collections;
    3. using UnityEngine;
    4. #if UNITY_EDITOR && !USE_BUNDLES
    5. using UnityEditor;
    6. using System.IO;
    7. #endif
    8.  
    9. public class AssetBundler : MonoBehaviour
    10. {
    11.     public delegate void OnLoadComplete<T>(T asset);
    12.  
    13. #if UNITY_EDITOR
    14.     public const string RootBundlesPath = "Assets/AssetBundles/Bundles";
    15. #endif
    16.  
    17.     private static MonoBehaviour _coroutineHandler;
    18.  
    19.     private static MonoBehaviour CoroutineHandler
    20.     {
    21.         get
    22.         {
    23.             if (_coroutineHandler != null) return _coroutineHandler;
    24.  
    25.             var go = new GameObject("AssetBundler") {hideFlags = HideFlags.HideInHierarchy};
    26.             _coroutineHandler = go.AddComponent<AssetBundler>();
    27.             DontDestroyOnLoad(go);
    28.             return _coroutineHandler;
    29.         }
    30.     }
    31.  
    32.     public static void GetAsset<T>(string path, string id, out T asset) where T : Object
    33.     {
    34. #if UNITY_EDITOR && !USE_BUNDLES
    35.         path = $"{RootBundlesPath}/{path}/";
    36.         var fileEntries = Directory.GetFiles(path);
    37.        
    38.         asset = null;
    39.         foreach(var fileName in fileEntries)
    40.         {
    41.             var t = AssetDatabase.LoadAssetAtPath<T>(fileName);
    42.             if (t.name != id) continue;
    43.             asset = t;
    44.             break;
    45.         }
    46. #else
    47.         path = $"{Application.streamingAssetsPath}/{path}";
    48.         var bundle = AssetBundle.LoadFromFile(path.ToLower());
    49.         asset = bundle.LoadAsset<T>(id);
    50.         bundle.Unload(false);
    51. #endif
    52.     }
    53.  
    54.     public static Coroutine GetAssetAsync<T>(string path, string id, OnLoadComplete<T> onComplete) where T : Object
    55.     {
    56.         return CoroutineHandler.StartCoroutine(LoadAsset(path, id, onComplete));
    57.     }
    58.  
    59.     public static void CancelRequest(Coroutine coroutine)
    60.     {
    61.         CoroutineHandler.StopCoroutine(coroutine);
    62.     }
    63.  
    64.     private static IEnumerator LoadAsset<T>(string path, string id, OnLoadComplete<T> onComplete) where T : Object
    65.     {
    66.         if (onComplete == null) yield break;
    67.  
    68. #if UNITY_EDITOR && !USE_BUNDLES
    69.         GetAsset(path, id, out T asset);
    70.         onComplete(asset);
    71. #else
    72.         path = $"{Application.streamingAssetsPath}/{path}";
    73.         var load = AssetBundle.LoadFromFileAsync(path.ToLower());
    74.         yield return load;
    75.         var bundle = load.assetBundle;
    76.  
    77.         // Get asset and return
    78.         var asset = bundle.LoadAsset<T>(id);
    79.         onComplete(asset);
    80.  
    81.         // Cleanup
    82.         bundle.Unload(false);
    83. #endif
    84.     }
    85. }
    This is the editor script used to build the asset bundles and ensure the paths in the editor and asset bundles are the same.
    Code (CSharp):
    1. using System.IO;
    2. using UnityEditor;
    3.  
    4. public static class AssetBundlerEditor
    5. {
    6.     private const string StreamingAssets = "Assets/StreamingAssets";
    7.     private const BuildAssetBundleOptions BuildOptions = BuildAssetBundleOptions.ChunkBasedCompression;
    8.  
    9.     [MenuItem("Assets/Asset Bundles/Build Android")]
    10.     public static void Build_Android()
    11.     {
    12.         Build(BuildTarget.Android);
    13.     }
    14.  
    15.     [MenuItem("Assets/Asset Bundles/Build iOS")]
    16.     public static void Build_iOS()
    17.     {
    18.         Build(BuildTarget.iOS);
    19.     }
    20.  
    21.     private static void Build(BuildTarget platform)
    22.     {
    23.         if (Directory.Exists(StreamingAssets)) Directory.Delete(StreamingAssets, true);
    24.         Directory.CreateDirectory(StreamingAssets);
    25.  
    26.         UpdateBundlePaths();
    27.        
    28.         BuildPipeline.BuildAssetBundles(StreamingAssets, BuildOptions, platform);
    29.     }
    30.  
    31.     private static void UpdateBundlePaths()
    32.     {
    33.         void UpdateSubDirectories(string directory)
    34.         {
    35.             var subDirectories = Directory.GetDirectories(directory);
    36.             foreach (var subDirectory in subDirectories)
    37.             {
    38.                 var assetImporter = AssetImporter.GetAtPath(subDirectory);
    39.                 assetImporter.assetBundleName = subDirectory.Substring(AssetBundler.RootBundlesPath.Length + 1);
    40.                 assetImporter.SaveAndReimport();
    41.  
    42.                 UpdateSubDirectories(subDirectory);
    43.             }
    44.         }
    45.  
    46.         UpdateSubDirectories(AssetBundler.RootBundlesPath);
    47.     }
    48. }
     
  2. Reichert

    Reichert

    Unity Technologies

    Joined:
    Jun 20, 2014
    Posts:
    63
  3. Murrrly

    Murrrly

    Joined:
    Apr 25, 2013
    Posts:
    16
    Ah cool! Thanks. So I guess it wasn't a bad idea, it was such a good idea that someone else already did it [better]! :D

    This is great, I would never have the time to be making profiling tools. The 'Virtual Mode' is really cool too