Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question AssetPostprocessor AddObjectToAsset doesn't work at all for ScriptableObjects

Discussion in 'Editor & General Support' started by burningmime, Oct 28, 2021.

  1. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    I'm trying to add a ScriptableObject to an asset in OnPostprocessModel, and the result is not there. The workflow is basically:

    Code (CSharp):
    1. class ShadowCacheData : ScriptableObject { }
    2. class ShadowCacheComponent : MonoBehaviour { ShadowCacheData  data; }
    3.  
    4. // In OnPostprocessModel()
    5. var myScriptableObject = ScriptableObject.CreateInstance<ShadowCacheData>();
    6. var myComponent = go.AddComponent<ShadowCacheComponent>();
    7. myComponent.data = myScriptableObject;
    8. context.AddObjectToAsset("some_unique_name", myScriptableObject);
    The ShadowCacheComponent correctly shows up, but the reference to the ScriptableObject is null, and the object isn't anywhere in the asset.

    Is this a bug (if so, I can get a minimal repro going), or am I just missing something fundamental about how ScriptableObjects work with AssetPostprocessors?
     
    Last edited: Oct 31, 2021
  2. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Bump. This is semi-blocking. It would be nice to at least have an indication of what's wrong.
     
  3. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Bump.

    Has anyone managed to add a ScriptableObject to an asset during OnPostprocessModel(), and if so, can you share (some bit of) the code?
     
  4. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,904
    I tried to look at your problem twice already, but I'm lost a bit, how are you doing this? What is the context in your code exactly?
    Because if I look at this chain: AssetPostprocessor > AssetPostprocessor-context > AssetImporters.AssetImportContext > AssetImporters.AssetImportContext.AddObjectToAsset

    It expects at least two parameters, you only pass the ScriptableObject. So could you please elaborate how you end up where you are currently?
    BTW, I've never seen ScriptableObject added to other assets as secondary, we always write it to disk as a separate asset and then reference it in prefabs and game objects, so IDK if it is possible.
     
    burningmime likes this.
  5. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Thanks for the response, Lurking Ninja!

    Sorry; I retyped that to simplify it and left out the name parameter. The real code is here: https://gitlab.com/burningmime/urpg.../shadow/editor/ShadowMeshPostprocessor.cs#L82 -- line 82 that's commented out is what I want to be doing. But when I add it that way (and reference it from the MB) it is missing from the result.

    What I want to do is add the object to the asset and reference it. The cache data can get big, and I want to be able to reference it from from each copy of the mesh instead of putting the data inside the component. Also, one model can have multiple copies of the same mesh (for example, you can imagine a whole house as an FBX and 4 of the same lamp in different places in the house).

    Writing it as a separate file means that 2 files will need to be managed. I want this to be as easy/bulletproof as possible if I will release it on the asset store. And I mean what is the purpose of "AddObjectToAsset" if the method doesn't actually add anything?

    Here's what the code would look like in an ideal world:

    Code (CSharp):
    1. ShadowCache cache = ScriptableObject.CreateInstance<ShadowCache>();
    2. cache.name = mesh.name + "__ShadowData";
    3. cache.data = data;
    4. context.AddObjectToAsset(cache.name, cache);
    5. foreach(GameObject go in group)
    6. {
    7.     ShadowCaster caster = go.AddComponent<ShadowCaster>();
    8.     caster.cache = cache;
    9.     atLeastOne = true;
    10. }
     
    Last edited: Oct 31, 2021
  6. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,904
    Not "doesn't actually add anything", but there are limitations what can you add. You cannot add assets only reference to assets or objects.
    And as I suspected, if you have your SO on disk, you can reference it just fine.
    This is the result of my very quick test rig:
    screenshot.png

    That
    a (Test SO)
    is reference to the asset
    /Assets/a.asset
    which is a
    TestSo
    type ScriptableObject written on disk and read back to reference it. (I also tested through code, so the data is accessible)

    Obviously I oversimplified everything in order to not to redevelop your software. :)
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. namespace Editor
    5. {
    6.     public class ModelPostProcessor : AssetPostprocessor
    7.     {
    8.         private void OnPostprocessModel(GameObject root)
    9.         {
    10.             var comp = root.AddComponent<TestSoHolder>();
    11.             comp.data = AssetDatabase.LoadAssetAtPath<TestSo>("Assets/a.asset");
    12.             comp.color = Color.cyan;
    13.             context.AddObjectToAsset(root.GetHashCode().ToString(), comp);
    14.         }
    15.     }
    16. }
    I'm pretty sure this whole thing is because how the serialization works. SOs don't get inlined, they get referenced, so they need to be either recreated in memory when your application starts and they need to be referenced on the spot or they need to be written inside an .asset file.
     
    Last edited: Oct 31, 2021
  7. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Thanks for the response!

    So, your example above would work without AddObjectToAsset.

    Here's what I want my code to look like, using that framework:

    Code (csharp):
    1.         private void OnPostprocessModel(GameObject root)
    2.         {
    3.             var comp = root.AddComponent<TestSoHolder>();
    4.             var compData = ScriptableObject.CreateInstance<TestSo>();
    5.             comp.data = compData;
    6.             comp.color = Color.cyan;
    7.             context.AddObjectToAsset(compData.GetHashCode().ToString(), compData);
    8.             context.AddObjectToAsset(comp.GetHashCode().ToString(), comp);
    9.         }
    They will both be separate artifacts in the library folder within the scope of the single asset (just like a model can have many meshes). In a regular AssetImporter (not AssetPostprocessor), this is used to add multiple textures/meshes/etc (see for example ShaderGraphImporter), and adding ScriptableObjects works just fine. So this method behaves differently in the scope of an AssetPostprocessor than an AssetImporter.
     
    Last edited: Oct 31, 2021
  8. MylesLambert

    MylesLambert

    Joined:
    Dec 31, 2012
    Posts:
    61
    Hi @burningmime were you able to get this working? I'm trying to do something similar and running in the same issue.

    EDIT: Actually this seems to work fine, I just had a mistake elsewhere in my code :) Unity 2022.2.2f1 & in my case I was saving a mesh asset.
     
    Last edited: May 30, 2023