OnBuild callback

It would be useful if there was an OnBuild callback similar to other callbacks like OnBeforeSerialize/OnAfterDeserialize. This would be called per component during the build process and called only for components included in the build.

One use case is creating a build artefact for a component. For example, a component could have serialised data which is better represented as a texture in builds. By creating the texture during the build process, one can avoid having large build artefacts in SVC.

Trying to accomplish something similar is not currently possible as per the following thread:

The thread is about shaders but applies to components. The gist is that it is not possible for a developer to trawl through the scenes and prefabs to determine what is included in a build from the current build interfaces like IPreprocessBuildWithReport as the spec on what is included is not even known.

Thank you.

You can customize the build process for sure!

If you need to do something per component, you would use the AssetDatabase to find those types/assets and do something with it. If it’s a script/component you can then call your own “OnBuild” method on it. Best to add an IOnBuild interface and cast the script like so to make this simple and efficient:

if (obj is IOnBuild script) script.OnBuild();

Here’s my test script just to see how it works and what I could do with custom build (pre)processing:

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace CodeSmile.EditorTests
{
    [InitializeOnLoad]
    public class TestCustomBuildProcess
    {
        private static string m_TempScene;
        private static BuildPlayerOptions ProcessBuildOptions(BuildPlayerOptions options)
        {
   
            // NOTE: this will open a dialog where the user can select the location of the build to be made.
            options = BuildPlayerWindow.DefaultBuildMethods.GetBuildPlayerOptions(options);

            // remember the scene the user had open
            var userOpenScenePath = SceneManager.GetActiveScene().path;
            Debug.Log($"open scene: {userOpenScenePath}");

            var firstScene = options.scenes[0];
           
            // make temp copy of scene
            var tempScene = firstScene.Replace(".unity", "_temp_build_copy.unity");
            m_TempScene = tempScene;
            AssetDatabase.CopyAsset(firstScene, tempScene);

            var tempGuid = AssetDatabase.GUIDFromAssetPath(tempScene);
            var tempGuid2 = AssetDatabase.AssetPathToGUID(tempScene);
            Debug.Log($"tempGuid: {tempGuid} ({tempGuid.ToString().Length}) / {tempGuid2} ({tempGuid2.ToString().Length})");

             tempGuid = AssetDatabase.GUIDFromAssetPath(tempScene + "notexist");
             tempGuid2 = AssetDatabase.AssetPathToGUID(tempScene + "notexist");
            Debug.Log($"not exist tempGuid: {tempGuid} / {tempGuid2}");

            //var sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(tempScene);
            //Debug.Log($"temp scene asset: {sceneAsset.name}");
           
            // replace scene in build list
            options.scenes = new[] { tempScene };
           
            // open the temp scene
            //Debug.Log($"scene: {tempScene}, open for editing ...");
            EditorSceneManager.OpenScene(tempScene, OpenSceneMode.Single);

            // modify temp scene
            var replaceMe = GameObject.FindGameObjectsWithTag("ReplaceMe");
            for (int i = replaceMe.Length - 1; i >= 0; i--)
            {
                var go = replaceMe[i];
                Debug.LogWarning($"removing unwanted object from build: {go.name}");
                GameObject.DestroyImmediate(go);
            }

            EditorSceneManager.SaveScene(SceneManager.GetActiveScene(), tempScene, false);
           
            // re-open the last scene the user had open just because we're trying not to be annoying, right ;)
            Debug.Log($"re-open scene: {userOpenScenePath}");
            EditorSceneManager.OpenScene(userOpenScenePath);
           
            return options;
        }

        private static void OnClickBuildPlayer(BuildPlayerOptions options)
        {
            BuildPlayerWindow.DefaultBuildMethods.BuildPlayer(options);
            Debug.Log("perform default build - DONE");
            if (string.IsNullOrWhiteSpace(m_TempScene) == false)
            {
                AssetDatabase.DeleteAsset(m_TempScene);
            }
        }

        static TestCustomBuildProcess()
        {
            BuildPlayerWindow.RegisterGetBuildPlayerOptionsHandler(ProcessBuildOptions);
            BuildPlayerWindow.RegisterBuildPlayerHandler(OnClickBuildPlayer);
        }
    }
}
#endif

This approach will not fully cover Unity’s mechanism for including/excluding assets in a build. Either Unity needs to publish a spec or they need to add the requested feature (or something similar).

Is it possible to have that component (I’m assuming you mean a C# script) in a separate scene that is not included in the build, and upon making the build open the scene, run the script and generate the texture? Save that texture data into an existing texture asset that is used by an asset which in turn is in a scene that’s included in the build. That way the serialized data of the component won’t be in the build, but the texture will be.

And of course add these textures to the ignore list for version control.

This is for a utility which will be used by others. I am not going to be able to add much friction (if any) to the process.