Order of Prefab creation and AddObjectToAsset calls

In unity 2022, in which order do I create a PrefabAsset and add (non-asset) Meshes to its asset via AddObjectToAsset if the Meshes themselves are references by MeshFilters on the Prefab itself?

A little bit of a chicken and egg problem it appears: The meshes can’t be added if they are already assets, so the Prefab needs to exist before the Mesh Assets… But I have to write the prefab with the meshes as assets before the prefab is written, but I want the meshes as part of the prefab asset.

(Probably not understanding AssetDatabase’s object references right or what happens when an Object like a Mesh becomes an Asset)

Example: I have a GameObject, and save that as prefab in Code.

Then I get a prefab editing scope. And add more gameobjects to it. A couple of these have MeshFilters, I create some meshes and add them to the Prefab Asset (using the path) right away.

I’m seeing hard native editor crashes and really ancient AssetDatabase errors. So I believe my order of operations is wrong no or I’m doing something within a Prefab Editing scope I am not supposed to do.

Just a hunch: when you add those GameObjects to the prefab with existing MeshFilters on them, where are these GameObjects coming from? And the mesh they reference?

I believe if they’re part of the scene you cannot add them straight to the prefab since they are bound to the scene. I may be wrong though.

You could try adding empty GameObjects, and then for each component add that component to that GameObject and filling in the details such as assigning a mesh as you go along. You may have to make copies of each and ensure that all references are assets themselves, certainly the mesh must exist as an asset to begin with.

Some code would help. :wink:

I clone the meshes and add the objects. I’m now adding the objects and cloned meshes in the prefab editing scope (the meshes will still be floating somewhere, but where?), and then in a single pass write all sharedMeshes into the Asset.

It feels super wonky.

The prefab can live in either the Scene or a PrefabEditingScope, but once it’s an asset, it also lives … somewhere?! It’s super confusing.

Also, if I use “SaveAsPrefabAsset”, which is kind of important, any existing subassets are somehow preserved (so it’s not a real overwrite). I can clear the assets but it feels pretty wrong.

I can CopyAsset for the prefab to not instantiate a temporary object that gets destroyed 3 lines later, but then lose the prefab link, which is important.

        private string ExportAndGetPrefabReference()
        {
            var context = GetComponent<ProvinceAuthoring>();
       
            var prefabName = _provincesPrefabNamePrefix + name;
            var prefabPath = _provincesDirectorySavePath + prefabName + ".prefab";

            //Create & Overwrite Prefab.
            var tempProvince = (GameObject) PrefabUtility.InstantiatePrefab(_provinceBasePrefab);
            tempProvince.transform.position = context.transform.position;
            tempProvince.name = prefabName;
            PrefabUtility.SaveAsPrefabAsset(tempProvince, prefabPath);
            DestroyImmediate(tempProvince.gameObject);
                 
            using (var scope = new PrefabUtility.EditPrefabContentsScope(prefabPath))
            {
                var root = scope.prefabContentsRoot;
                var prefab = root.GetComponent<ProvinceView>();
           
                prefab.GetComponent<ProvinceModelBinder>().id = context.id;
                PopulateProvincePrefab(context, prefab, prefabPath); //creates new gameobjects and meshes
            }
       
            BakeMeshesIntoAsset(prefabPath, tempProvince); //used to be inside "Populate" methods, now tries to do it all at once
            AssetDatabase.SaveAssets();

            return prefabPath;
        }
private void BakeMeshesIntoAsset(string assetPath, GameObject source)
{
   foreach (var filter in source.GetComponentsInChildren<MeshFilter>())
      AssetDatabase.AddObjectToAsset(filter.sharedMesh, assetPath);
}

Current streamlined state is (and seems to work a bit better?) is:

            var context = GetComponent<ProvinceAuthoring>();
         
            var prefabName = _provincesPrefabNamePrefix + name;
            var prefabPath = _provincesDirectorySavePath + prefabName + ".prefab";

            //Clean Overwrite? Deleting is nasty, and StartAssetEditing / EndAssetEditing doesn't work with SaveasPrefabAsset
            AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(_provinceBasePrefab),  prefabPath);
        
            //Create
            var tempProvince = (GameObject) PrefabUtility.InstantiatePrefab(_provinceBasePrefab);
            tempProvince.transform.position = context.transform.position;
            tempProvince.name = prefabName;
            tempProvince.GetComponent<ProvinceModelBinder>().id = context.id;
            PopulateProvincePrefab(context, tempProvince.GetComponent<ProvinceView>());

            var prefabAsset = PrefabUtility.SaveAsPrefabAsset(tempProvince, prefabPath);
            BakeMeshesIntoAsset(prefabPath, tempProvince);
         
            AssetDatabase.SaveAssets();
            DestroyImmediate(tempProvince);

Actually no, this loses the meshes on Scene Change.

Before it would work and the meshes would persist, but something in the AssetDatabase was badly broken. (just really ancient errors that really don’t feel like AssetDatabase V2 and brutal hard crashes to desktop at very strange moments (usually near a domain reload or asset refresh))

So this permutation seems to work, but feels super hacky and perhaps it, too, will lead to corruptiuon:

            var context = GetComponent<ProvinceAuthoring>();
          
            var prefabName = _provincesPrefabNamePrefix + name;
            var prefabPath = _provincesDirectorySavePath + prefabName + ".prefab";

            //Delete by Overwriting
            AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(_provinceBasePrefab), prefabPath);

            //Spawn new Prefab.
            var tempProvince = (GameObject) PrefabUtility.InstantiatePrefab(_provinceBasePrefab);
           
            tempProvince.transform.position = context.transform.position;
            tempProvince.name = prefabName;
            tempProvince.GetComponent<ProvinceModelBinder>().id = context.id;
          
            //Create Meshes and stuff.
            PopulateProvincePrefab(context, tempProvince.GetComponent<ProvinceView>());
  
            //Package Meshes into the Asset
            BakeMeshesIntoAsset(tempProvince, prefabPath);
          
            //HACK - save to DB: Abusing the fact that sub-assets stick around even on overwrite?
            var prefabAsset = PrefabUtility.SaveAsPrefabAsset(tempProvince, prefabPath);


            //Necessary?
            AssetDatabase.SaveAssets();

            //Cleanup
            DestroyImmediate(tempProvince);
1 Like

Had similar problem, your post helped me quite a lot.
Now I’m doing it like this

GameObject prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(go, prefabPath,InteractionMode.AutomatedAction);
AssetDatabase.AddObjectToAsset(mesh, prefab);
prefab.GetComponent<MeshFilter>().sharedMesh = mesh; //<--we have to do linking here
PrefabUtility.SavePrefabAsset(prefab);
1 Like