Search Unity

Question Order of Prefab creation and AddObjectToAsset calls

Discussion in 'Asset Database' started by Thygrrr, Oct 17, 2022.

  1. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    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.
     
    Last edited: Oct 17, 2022
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,989
    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. ;)
     
  3. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    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.
     
    Last edited: Oct 19, 2022
  4. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    Code (CSharp):
    1.         private string ExportAndGetPrefabReference()
    2.         {
    3.             var context = GetComponent<ProvinceAuthoring>();
    4.        
    5.             var prefabName = _provincesPrefabNamePrefix + name;
    6.             var prefabPath = _provincesDirectorySavePath + prefabName + ".prefab";
    7.  
    8.             //Create & Overwrite Prefab.
    9.             var tempProvince = (GameObject) PrefabUtility.InstantiatePrefab(_provinceBasePrefab);
    10.             tempProvince.transform.position = context.transform.position;
    11.             tempProvince.name = prefabName;
    12.             PrefabUtility.SaveAsPrefabAsset(tempProvince, prefabPath);
    13.             DestroyImmediate(tempProvince.gameObject);
    14.                  
    15.             using (var scope = new PrefabUtility.EditPrefabContentsScope(prefabPath))
    16.             {
    17.                 var root = scope.prefabContentsRoot;
    18.                 var prefab = root.GetComponent<ProvinceView>();
    19.            
    20.                 prefab.GetComponent<ProvinceModelBinder>().id = context.id;
    21.                 PopulateProvincePrefab(context, prefab, prefabPath); //creates new gameobjects and meshes
    22.             }
    23.        
    24.             BakeMeshesIntoAsset(prefabPath, tempProvince); //used to be inside "Populate" methods, now tries to do it all at once
    25.             AssetDatabase.SaveAssets();
    26.  
    27.             return prefabPath;
    28.         }
    29.  

    Code (csharp):
    1.  
    2. private void BakeMeshesIntoAsset(string assetPath, GameObject source)
    3. {
    4.    foreach (var filter in source.GetComponentsInChildren<MeshFilter>())
    5.       AssetDatabase.AddObjectToAsset(filter.sharedMesh, assetPath);
    6. }
    7.  
     
  5. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    Current streamlined state is (and seems to work a bit better?) is:

    Code (CSharp):
    1.             var context = GetComponent<ProvinceAuthoring>();
    2.          
    3.             var prefabName = _provincesPrefabNamePrefix + name;
    4.             var prefabPath = _provincesDirectorySavePath + prefabName + ".prefab";
    5.  
    6.             //Clean Overwrite? Deleting is nasty, and StartAssetEditing / EndAssetEditing doesn't work with SaveasPrefabAsset
    7.             AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(_provinceBasePrefab),  prefabPath);
    8.        
    9.             //Create
    10.             var tempProvince = (GameObject) PrefabUtility.InstantiatePrefab(_provinceBasePrefab);
    11.             tempProvince.transform.position = context.transform.position;
    12.             tempProvince.name = prefabName;
    13.             tempProvince.GetComponent<ProvinceModelBinder>().id = context.id;
    14.             PopulateProvincePrefab(context, tempProvince.GetComponent<ProvinceView>());
    15.  
    16.             var prefabAsset = PrefabUtility.SaveAsPrefabAsset(tempProvince, prefabPath);
    17.             BakeMeshesIntoAsset(prefabPath, tempProvince);
    18.          
    19.             AssetDatabase.SaveAssets();
    20.             DestroyImmediate(tempProvince);
     
    Last edited: Oct 19, 2022
  6. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    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))
     
    Last edited: Oct 19, 2022
  7. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    So this permutation seems to work, but feels super hacky and perhaps it, too, will lead to corruptiuon:

    Code (CSharp):
    1.             var context = GetComponent<ProvinceAuthoring>();
    2.          
    3.             var prefabName = _provincesPrefabNamePrefix + name;
    4.             var prefabPath = _provincesDirectorySavePath + prefabName + ".prefab";
    5.  
    6.             //Delete by Overwriting
    7.             AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(_provinceBasePrefab), prefabPath);
    8.  
    9.             //Spawn new Prefab.
    10.             var tempProvince = (GameObject) PrefabUtility.InstantiatePrefab(_provinceBasePrefab);
    11.            
    12.             tempProvince.transform.position = context.transform.position;
    13.             tempProvince.name = prefabName;
    14.             tempProvince.GetComponent<ProvinceModelBinder>().id = context.id;
    15.          
    16.             //Create Meshes and stuff.
    17.             PopulateProvincePrefab(context, tempProvince.GetComponent<ProvinceView>());
    18.  
    19.             //Package Meshes into the Asset
    20.             BakeMeshesIntoAsset(tempProvince, prefabPath);
    21.          
    22.             //HACK - save to DB: Abusing the fact that sub-assets stick around even on overwrite?
    23.             var prefabAsset = PrefabUtility.SaveAsPrefabAsset(tempProvince, prefabPath);
    24.  
    25.  
    26.             //Necessary?
    27.             AssetDatabase.SaveAssets();
    28.  
    29.             //Cleanup
    30.             DestroyImmediate(tempProvince);
     
    koirat likes this.
  8. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,074
    Had similar problem, your post helped me quite a lot.
    Now I'm doing it like this

    Code (csharp):
    1.  
    2. GameObject prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(go, prefabPath,InteractionMode.AutomatedAction);
    3. AssetDatabase.AddObjectToAsset(mesh, prefab);
    4. prefab.GetComponent<MeshFilter>().sharedMesh = mesh; //<--we have to do linking here
    5. PrefabUtility.SavePrefabAsset(prefab);
    6.  
     
    Thygrrr likes this.