Search Unity

Scriptable Object requires reimport before every play

Discussion in 'Editor & General Support' started by GrayedFox, Jan 14, 2021.

  1. GrayedFox

    GrayedFox

    Joined:
    Jan 28, 2014
    Posts:
    70
    Hello all, I've run into an issue that probably comes from my shallow understanding of serialization and how scriptable objects work.

    Some of my scriptable objects hold references to an array of other scriptable objects, like so:

    Code (CSharp):
    1. using System.Linq;
    2. using UnityEngine;
    3. using static Item;
    4. using static SpellRank;
    5.  
    6. public class SkillObject : ScriptableObject
    7. {
    8.     [Header("About this Skill")]
    9.     public string skillName = "Name";
    10.     public string skillDescription = "Description";
    11.  
    12.     public AttributeObject[] attributes;
    13.     public DamageObject[] damages;
    14.  
    15.     [Header("Spell Ranks")]
    16.     [Tooltip("Define if the spell rank modifier requires a specific item type be equipped to be applied")]
    17.     public ItemTypes spellRankItemTypes;
    18.     [Range(-5, 5)]
    19.     public int modifier;
    20.     public HandheldSpells handheldSpells;
    21.  
    22.     public Skill Init()
    23.     {
    24.         var permanentAttributeArray = attributes.Select(a => a.Init()).ToArray();
    25.  
    26.         // problem occurs here - never during the attribute.Init (even when AttributeObject arr. is empty)
    27.         var damageArray = damages.Select(d => d.Init()).ToArray();
    28.  
    29.         return new Skill(skillName,
    30.                          skillDescription,
    31.                          permanentAttributeArray,
    32.                          damageArray,
    33.                          spellRanks);
    34.     }
    35. }
    Each scriptable object has an Init() method which returns a class that the scriptable object represents, so in our example the SkillObject.Init() method returns an instance of a Skill. AttributeObject.Init() returns a new Attribute, DamageObject.Init() returns a Damage instance, etc.

    The problem is every time I change anything on a SkillObject - for example, let's say I add another AttributeObject to the AttributeObject array:

    unity-scriptable-object-array.png

    Unity will now error because it thinks the DamageObject array is null (null, not empty). Here's the DamageObject code:


    Code (CSharp):
    1. public class DamageObject : ScriptableObject
    2. {
    3.     [Header("About this damage")]
    4.     public string description = "Damage Description";
    5.  
    6.     [Header("Stats")]
    7.     public int damageMin = 0;
    8.     public int damageMax = 0;
    9.     public int damageOverTimeMin = 0;
    10.     public int damageOverTimeMax = 0;
    11.     public float damageOverTimeDuration = 0;
    12.  
    13.     public DamageTypes damageTypes;
    14.     public DamageApplication damageApplicationType;
    15.  
    16.     public Damage Init()
    17.     {
    18.         return new Damage((damageMin, damageMax),
    19.                          (damageOverTimeMin, damageOverTimeMax, damageOverTimeDuration),
    20.                          damageTypes,
    21.                          damageApplicationType);
    22.     }
    23. }
    I've narrowed it down to this specific scriptable object. Even if adding an attribute, it's the damage object which for some reason becomes null.

    If I right click my Resources folder and hit Reimport, the problem goes away and everything works as expected.

    Is it a bad idea to have scriptable objects store references to other scriptable objects? Am I using scriptable objects in a really gimpy way, or am I just missing something obvious in the DamageObject code?

    For reference, the AttributeObject never has this problem (tested by commenting out DamageObject array and refactoring the Skill to not require Damage instances). Here is the AttributeObject code:


    Code (CSharp):
    1. public class AttributeObject : ScriptableObject
    2. {
    3.     [Header("About this attribute")]
    4.     public string attributeDescription = "Attribute Description";
    5.  
    6.     [Header("Stats")]
    7.     public int attributeAmount = 0;
    8.     public AttributeTypes attributeTypes;
    9.     public bool isMultiplicative = true;
    10.  
    11.     public Attribute Init()
    12.     {
    13.         return new Attribute(attributeAmount, attributeTypes, isMultiplicative);
    14.     }
    15. }
    Would appreciate any insights here - feels like it could be a UnityEditor bug since a reimport fixes everything - but again, maybe not and my workflow is the issue.
     
  2. GrayedFox

    GrayedFox

    Joined:
    Jan 28, 2014
    Posts:
    70
    Ah sorry, the error thrown without reimporting is:


    Code (CSharp):
    1. ArgumentNullException: Value cannot be null.
    2. Parameter name: source
    3. System.Linq.Enumerable.Select[TSource,TResult] (System.Collections.Generic.IEnumerable`1[T] source, System.Func`2[T,TResult] selector) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
    4. SkillObject.Init () (at Assets/Scripts/Skills/SkillObject.cs:27)
    So, Line 27 is when we try and Init the DamageObjects. Again, if the AttributeObject array is empty we don't have this problem. Even if the DamageObjects array is not empty, this problem will occur. I also tried putting this in a startup script to at least workaround me having to manually click Reimport before every play and build:


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [InitializeOnLoad]
    5. public class Startup
    6. {
    7.     static Startup()
    8.     {
    9.         ReimportSkills();
    10.     }
    11.  
    12.     public static void ReimportSkills()
    13.     {
    14.         string pathRsc = "Assets/Resources/Skills/";
    15.         AssetDatabase.ImportAsset(pathRsc, ImportAssetOptions.ImportRecursive);
    16.         Debug.Log("Reimported all skills");
    17.     }
    18. }
    But that doesn't work - I have to actually right click and reimport every ScriptableObject in the Skills folder for the game to load without error.
     
  3. KyleOlsen

    KyleOlsen

    Joined:
    Apr 3, 2012
    Posts:
    237
  4. GrayedFox

    GrayedFox

    Joined:
    Jan 28, 2014
    Posts:
    70
    Thanks for your reply. Hmm, the thing is, I already have scriptable object instances (they are assets that live in the resources folder). So I'm not sure why I would need to basically clone the existing scriptable object instance created using the CreateAssetMenuAttribute?
     
  5. KyleOlsen

    KyleOlsen

    Joined:
    Apr 3, 2012
    Posts:
    237
    My apologies I misread what your Init() was doing :)
     
  6. japhib

    japhib

    Joined:
    May 26, 2017
    Posts:
    65
    Definitely seems like a Unity bug to me. Nothing about your workflow seems weird at all
     
  7. GrayedFox

    GrayedFox

    Joined:
    Jan 28, 2014
    Posts:
    70
    Hmm, I found out yesterday that by editing every single skill scriptable object (I bulk edited them all by searching the Skills folder), and simply changing a value that is currently unused from 0 to 1 and back to 0 again, the game now loads fine. Weird.

    It's almost like touching all the existing scriptable objects has some how forced Unity to "see" them properly again and handle all the SO references.