Search Unity

Question Create additional Prefab assets in ScriptedImporter and track them!

Discussion in 'Prefabs' started by Moritz5thPlanet, Aug 19, 2021.

  1. Moritz5thPlanet

    Moritz5thPlanet

    Joined:
    Feb 5, 2019
    Posts:
    73
    I'd like to create an extra Prefab in a ScriptedImporter and store a reference in the importer's Settings to it. It's important that this Prefab gets handled and imported by Unity independently (exactly as if it were a manually created prefab), and does not get re-created by the original file being changed or reimported. Only a basic stub should be created automatically on first import, and this stub will later be extended through functions called by a custom Editor.

    Code (CSharp):
    1.     [ScriptedImporter(3, "io")]
    2.     public class StudioImporter : ScriptedImporter
    3.     {
    4.         [SerializeField]
    5.         internal GameObject LinkedPrefab;
    6.  
    7.        public override void OnImportAsset(AssetImportContext ctx)
    8.         {
    9.             Debug.Log($"Importing {ctx.assetPath}");
    10.  
    11.             //StudioFile scriptable Object - just a basic SO that contains a string property.
    12.             var file = ScriptableObject.CreateInstance<StudioFile>();
    13.  
    14.             ctx.AddObjectToAsset("import data", file);
    15.             ctx.SetMainObject(file);
    16.  
    17.             if (!LinkedPrefab)
    18.             {
    19.                 var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    20.                 var prefab = PrefabUtility.SaveAsPrefabAsset(obj, PrefabPath, out var success);
    21.                 LinkedPrefab = prefab;
    22.                 Debug.Log($"Created prefab {file.Prefab} at {PrefabPath}, success: {success}.");
    23.  
    24.                 DestroyImmediate(obj);
    25.                 //... I tried SaveAndReimport() here, but it doesn't work.
    26.             }
    27.         }
    28. }
    29.  
    So, this doesn't work - or rather, doesn't *quite* work:
    The prefab gets successfully created on first import, but it has no name while still in the Scope of OnImportAsset - in fact, it's... some form of "null" value? I can't do anything with it at this point.

    Code (CSharp):
    1. Created prefab  at Assets\Dioramas\TestDiorama\StudioFiles\test-studio-file2.prefab, success: True.
    2. UnityEngine.Debug:Log (object)
    3. Dioramas.Authoring.Editor.StudioImporter:OnImportAsset (UnityEditor.AssetImporters.AssetImportContext) (at Assets/Scripts/Authoring/Editor/StudioImporter.cs:106)
    4. UnityEditor.AssetImporters.ScriptedImporter:GenerateAssetData (UnityEditor.AssetImporters.AssetImportContext)
    upload_2021-8-19_16-40-10.png
    In the inspector, you can see all the fields also being None (GameObject), even though the prefab object does appear in the Project folder as intended.


    When you then MANUALLY REIMPORT the same file a second time (right-click, Reimport), LinkedPrefab will appear in the inspector as expected - but it will have been erroneously recreated nonetheless (what is different about the 2nd creation that this time, it sticks?!):

    Code (CSharp):
    1. Created prefab test-studio-file2 (UnityEngine.GameObject) at Assets\Dioramas\TestDiorama\StudioFiles\test-studio-file2.prefab, success: True.
    2. UnityEngine.Debug:Log (object)
    3. Dioramas.Authoring.Editor.StudioImporter:OnImportAsset (UnityEditor.AssetImporters.AssetImportContext) (at Assets/Scripts/Authoring/Editor/StudioImporter.cs:106)
    4. UnityEditor.AssetImporters.ScriptedImporter:GenerateAssetData (UnityEditor.AssetImporters.AssetImportContext)
    5.  
    upload_2021-8-19_16-39-46.png

    How do I do this properly? There's an expensive import and mesh culling / database querying step involved in creating the prefab (outside the input process), and all the prefab would need to know whether it is up to date with its source asset or not (and display this situation in its own editor, but take no steps automatically).

    In case you wonder - why the separate prefab? As far as I understand it, should I make this prefab the MainObject on the imported asset, it would need to get re-created on every import, and any changes to it would be lost between imports. I want this created object to persist as a "normal" Prefab (model Prefab would be even better), no matter what.
     
    Last edited: Aug 19, 2021
  2. Cameo221

    Cameo221

    Joined:
    Aug 2, 2018
    Posts:
    37
    I think that the reason that the linked prefab only appears in the field after importing a second time, is because the exported prefab needed to be properly imported first. During the first prefab save, The asset database may have not had enough time to know that the saved prefab exists yet. Maybe it works a second time because the asset now exists, and so it returned the previously saved prefab asset, but that's my guess. Maybe you need to use AssetDatabase.Refresh, or use AssetDatabase.ImportAsset on the new prefab?


    Side tangent, but I believe I came across a similar situation to this. Maybe it can help.

    In my own setup, I would assign a prefab into a field of an importer inspector, which is expected to be instantiated into the hierarchy of the ScriptedImporter GameObject upon import.

    Upon the 1st import, the prefab doesn't exist in the hierarchy, as if the prefab assigned was null (even though it was indeed assigned in the importer inspector)
    Upon importing for a 2nd, the prefabs then successfully exist in the imported hierarchy.

    So when I tried figuring this out, my thought process was that somehow the prefab was not a proper asset at the time, considering that it was null. (Perhaps it was not properly imported yet)

    One piece of evidence was that the prefab issue was only happening once every so often. If a prefab was already imported recently, then there would be no problems. Turns out that it looks like it's mostly happening if I did a "Reimport all" or if both the prefab and project were imported at the same time, so it indicated to me that the ScriptedImporter was trying to import before prefabs were.

    So with this idea, I tested if I could somehow have prefabs imported before the ScriptedImporter did, by changing the import order with the ScriptedImporterAttribute.importQueuePriority.
    The documentation didn't display the import order of native Unity asset types, which meant I needed to try testing on my own to find out the import order of prefabs.
    And so I went and tested various import orderings to see if I could get a prefab imported before the ScriptedImporter, and reported my findings in this post.

    After configuring the correct import order so that prefabs are now imported before the ScriptedImporter, it solved my original problem!
    It does however have a new side effect, which is that the ScriptedImporter GameObject hierarchy runs into various breaking problems when it is nested inside a prefab(or prefab variant) when a prefab containing it reimports.

    So lately, I've reverted to making the ScriptedImporter import before prefabs, but I'm trying to find ways to make the prefab correctly import before It's needed by the ScriptedImporter to be instantiated. I've tried using AssetDatabase.ImportAsset during the ScriptedImporter import process, but it's had its own set of problems:
    upload_2021-12-28_0-15-31.png
    I'm still trying to figure this out successfully, but I hope my explanation can possibly help you out in understanding your issue, or potentially helping me with mine if you find a cool discovery. Cheers :)
     
  3. Cameo221

    Cameo221

    Joined:
    Aug 2, 2018
    Posts:
    37
    I have found a solution to my prefab import order dilemma. I've created a .unitypackage (attached) with the expected result that I wanted. It does use EditorApplication.delayCall, so it unexpectedly collapses the expanded hierarchy for the imported gameobject hierarchy upon importing, but I'm happy with the result.
    I hope that my research can help out anyone who might have the same issue as I did.
    My explanation for how it works is outlined in the importer script, but I'll also have it here:

    This ScriptedImporter is imported first as an "empty" import, then the delay call will do the actual import where it adds a prefab as children. (Tested in 2019.3.0, 2020.3.0, and 2021.2.0)

    This situation can happen by multi-selecting the "Prefab" asset and the ".importtest" asset and "Reimport", or by doing "Reimport All".

    The import orders are critically important. There is a very specific reason why we want to have the importer run before prefabs import, and then import the importer again.
    These were some previous situations that had their own set of problems before forming this solution we have now:

    Situation 1: (Import Prefabs first, then import ScriptedImporter)
    The prefabs are properly instantiated as expected. However, reimporting any other prefabs or prefab variants that would nest the ScriptedImporter GameObject will break.

    Situation 2: (Import ScriptedImporter first, then import Prefabs)
    When importing the ScriptedImporter first, the prefabs to add cannot be loaded because the prefabs are not imported yet (null). The issue is only solved by reimporting the ScriptedImporter for a second time.

    So after some research and testing, I came up with a solution after checking this forum thread message: https://forum.unity.com/threads/coroutine-in-scriptedimporter.495474/#post-3221985
    1:
    Import the ScriptedImporter. It adds a basic root GameObject to the Hierarchy, essentially a blank object at first.
    The reason we add a basic GameObject is so that the AssetImportContext.SetMainObject can be fulfilled for the first import to not cause potential import issues.
    (At this point in time, Prefabs are NOT imported yet; they would be null.)
    2:
    Do the EditorApplication.delayCall to reimport this asset slightly later, so that prefabs are imported and ready to be used in the actual import process.
    3:
    The EditorApplication.delayCall will now invoke the second reimport.
    The prefabs are now available to load and do not cause any issues that were brought up in the two situations discussed above.

    Edit: There is an issue where objects in the scene that reference objects in the imported hierarchy are lost because of how the hierarchy is gone for just a short moment. Maybe the first reimport should generate the entire hierarchy instead of just a single root object.
     

    Attached Files:

    Last edited: Jan 9, 2022