Search Unity

Getting list of all assets in build

Discussion in 'Scripting' started by joshcamas, Jan 29, 2019.

  1. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Hello friends :)

    I'm wanting to build an automated lookup table that stores an asset's instance id and a reference to the owning asset, for use in a game build.

    However, I only want to put assets that are actually being used into this lookuptable... So I need to somehow get a list of assets that are packed into the build, but before the build has been made, since I want to add this lookuptable to the build....

    Any ideas?
     
  2. You basically described addressables. :D
     
  3. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Would addressables be the right path for literally saving *all* scriptable objects?
     
  4. I think so, although I only read about it so far, I haven't played with it. But I think it worth the try, head over the Addressables topic and try to ask the guys about your use case, they may help you to determinate if it's a perfect solution for you or not.
     
  5. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    After looking into it, it's not exactly what I'm looking for... it's sort of the reverse. IE instead of simply allowing you to reference an asset, it sort of moves the asset out of the context of being built into the build. Thus, it must be accessed in a certain way.

    In other words, addressables sort of solves a different question. A cool solution, and I would like to use addressables someday, (although it would take a hefty amount of work to get it set up in my project) but I don't *think* it's the solution.
     
    Lurking-Ninja likes this.
  6. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    OOoooooo This is getting close! I'm guessing I wouldn't be able to modify assets when this event is run, but I'll try! Thank you so much!
     
  7. Actually you can. I was able to overwrite a .cs file and write the build date and time into it (static class, static variable).
    Edit: and the static class and value worked, of course. :D
     
    Last edited by a moderator: Jan 30, 2019
    joshcamas likes this.
  8. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Oh heck yeah, then this sounds like 100% what I need! Thank you!!
     
    Lurking-Ninja likes this.
  9. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Hmmm... For some reason, the BuildReport object has an empty "files" array...

    EDIT: Alright, it looks like only OnPostprocessBuild actually supports the files array... So currently there seems to be no easy way to figure out what assets are going to be in a build before it's actually built :/
     
    Last edited: Jan 30, 2019
  10. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,277
    Sadly, no solution yet. There doesn't seem to be any way to actually be able to get a list of assets in a build. Sigh.

    I wonder if I'd be able to do something insane, like run a program that would read the build's resource files using modding tools, then inject a list of references somehow. Of course, there is no way of loading a reference without either bundling it, or automatically loading it, so... Yeah I'm pretty much entirely screwed.
     
    Last edited: Dec 24, 2019
    SolidAlloy likes this.
  11. SolidAlloy

    SolidAlloy

    Joined:
    Oct 21, 2019
    Posts:
    58
    I found a very hacky way to get the list of assets. It's based on the internal
    BuildPipeline.BuildPlayerData()
    method that packs all the scenes and assets but doesn't compile assemblies to IL2CPP or WebAssembly, so it doesn't take that long. However, it also compiles shaders, so it's not optimal if you want to just get a list of assets. Although this can be a temporary solution to prototype out the feature, I would create my own algorithm to find all the used assets. Thinking about it, it's not that difficult:
    1. Get a list of scenes from build settings.
    2. Open each scene, grab all the game objects in it.
    3. Gather all the components from game objects, create a
    SerializedObject
    instance for each.
    4. Iterate over serialized properties on each serialized object.
    5. If
    SerializedProperty.propertyType
    is
    ObjectReference
    , get its
    .objectReferenceValue
    .
    6. If the obtained value is prefab, treat it like a scene: get all the game objects inside, their components, create serialized objects out of them.
    7. If the obtained value is ScriptableObject, create a serialized object out of it and iterate its serialized properties.

    Code (CSharp):
    1. [InitializeOnLoad]
    2. public static class BuildProcessor
    3. {
    4.     static BuildProcessor()
    5.     {
    6.         // Replace the default action of building a player with a custom one.
    7.         BuildPlayerWindow.RegisterBuildPlayerHandler(BuildPlayer);
    8.     }
    9.  
    10.     private static void BuildPlayer(BuildPlayerOptions options)
    11.     {
    12.         // A partial copy of BuildPlayerWindow.DefaultBuildMethods.BuildPlayer(options)
    13.         // so that we exit prematurely if the build is known to fail.
    14.         if (EditorApplication.isCompiling)
    15.             return;
    16.  
    17.         if (!BuildPipeline.IsBuildTargetSupported(options.targetGroup, options.target))
    18.             throw new BuildPlayerWindow.BuildMethodException("Build target is not supported.");
    19.  
    20.         if (Unsupported.IsBleedingEdgeBuild())
    21.         {
    22.             StringBuilder stringBuilder = new StringBuilder();
    23.             stringBuilder.AppendLine(
    24.                 "This version of Unity is a BleedingEdge build that has not seen any manual testing.");
    25.             stringBuilder.AppendLine("You should consider this build unstable.");
    26.             stringBuilder.AppendLine("We strongly recommend that you use a normal version of Unity instead.");
    27.             if (EditorUtility.DisplayDialog("BleedingEdge Build", stringBuilder.ToString(), "Cancel", "OK"))
    28.                 throw new BuildPlayerWindow.BuildMethodException();
    29.         }
    30.  
    31.         var activeBuildTargetGroup = (BuildTargetGroup) typeof(EditorUserBuildSettings).GetProperty(
    32.             "activeBuildTargetGroup",
    33.             BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
    34.  
    35.         if (EditorUserBuildSettings.activeBuildTarget != options.target ||
    36.             activeBuildTargetGroup != options.targetGroup)
    37.         {
    38.             if (!EditorUserBuildSettings.SwitchActiveBuildTargetAsync(options.targetGroup, options.target))
    39.             {
    40.                 throw new BuildPlayerWindow.BuildMethodException("Could not switch to build target.");
    41.             }
    42.  
    43.             return;
    44.         }
    45.  
    46.         var report = GetBuildReport(options);
    47.         // Now you can iterate report.packedAssets and do something to them
    48.         // After that, don't forget to launch the real build:
    49.         BuildPlayerWindow.DefaultBuildMethods.BuildPlayer(options);
    50.     }
    51.  
    52.     private static BuildReport GetBuildReport(BuildPlayerOptions options)
    53.     {
    54.         var playerDataOptionsType = typeof(BuildPipeline).Assembly.GetType("UnityEditor.BuildPlayerDataOptions");
    55.         var runtimeClassRegistryType = typeof(BuildPipeline).Assembly.GetType("UnityEditor.RuntimeClassRegistry");
    56.  
    57.         var playerDataOptions = GetPlayerDataOptions(options, playerDataOptionsType);
    58.         var registry = Activator.CreateInstance(runtimeClassRegistryType);
    59.  
    60.         var buildPlayerDataMethod = typeof(BuildPipeline).GetMethod("BuildPlayerData",
    61.             BindingFlags.NonPublic | BindingFlags.Static, null,
    62.             new[] {playerDataOptionsType, runtimeClassRegistryType.MakeByRefType()}, null);
    63.         return (BuildReport) buildPlayerDataMethod.Invoke(null, new[] {playerDataOptions, registry});
    64.     }
    65.  
    66.     private static object GetPlayerDataOptions(BuildPlayerOptions options, Type playerDataOptionsType)
    67.     {
    68.         var dataOptions = Activator.CreateInstance(playerDataOptionsType);
    69.         var scenes = playerDataOptionsType.GetProperty("scenes");
    70.         var targetGroup = playerDataOptionsType.GetProperty("targetGroup");
    71.         var target = playerDataOptionsType.GetProperty("target");
    72.         var optionsProperty = playerDataOptionsType.GetProperty("options");
    73.  
    74.         scenes.SetValue(dataOptions, EditorBuildSettingsScene.GetActiveSceneList(EditorBuildSettings.scenes));
    75.         targetGroup.SetValue(dataOptions, options.targetGroup);
    76.         target.SetValue(dataOptions, options.target);
    77.         optionsProperty.SetValue(dataOptions, options.options);
    78.  
    79.         return dataOptions;
    80.     }
    81. }
    82.  
     
    Last edited: Jan 14, 2022
    joshcamas likes this.