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 Help: "reference not set..." error trying to access a list in a scriptable object.

Discussion in 'Scripting' started by GiuDilGaming, Sep 26, 2023.

  1. GiuDilGaming

    GiuDilGaming

    Joined:
    May 17, 2023
    Posts:
    5
    Hi everyone,
    in a "to-be" card game that I'm trying to dev, i came out using a scriptable object to create and access static lists of cards for the hands of players, played cards etc. These lists are all in the same ScriptableObject class FloatLists, defined for example as:

    public class FloatLists : ScriptableObject
    {
    public List<Card> chosenCards;
    ...
    }
    Then in various c# class scripts, for example CardManager.cs, I define a macro variable 'fLists' of the type FloatList, that lets me access each list defined through a declaration:
    public FloatList fLists;
    and then for example:
    fLists.chosenCards.Add(oneCard);

    All of these seems to work generally fine, but....

    when running the code, though the list is there already with some items, the instruction works in a first passage, but fails at a following one, giving the cited error. If I debug.log(fLists.chosenCards.Count), the error moves to that line, making me think that the access to the list, that have worked in a previous passage of the same run, gets lost somehow.
    I managed in previous cases of the same problem to solve it moving calls to another script, but I didn't understand why it is giving the problem, so it is something that i get to face again...
    Could it be a problem of static instances (that I use for nearly every class)?
    Or what else?
    Many Thanks in advance to whom can give me a hint.
    Giuseppe
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    I guess you're new to C#?

    This:
    Code (CSharp):
    1. public List<Card> chosenCards;
    ... isn't a list object. It's a field that can refer to such a list. You've not initialized it so it'll be NULL so accessing "chosenCards" will give you a Null reference exception.

    Code (CSharp):
    1. public List<Card> chosenCards = new List<Card>();
    Here's how to post code on the forums btw: https://forum.unity.com/threads/using-code-tags-properly.143875/
     
    GiuDilGaming likes this.
  3. GiuDilGaming

    GiuDilGaming

    Joined:
    May 17, 2023
    Posts:
    5
    New to this forum too... thanks!
     
    MelvMay likes this.
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    You're most welcome, hopefully that sorted it for you.

    Good luck!
     
  5. GiuDilGaming

    GiuDilGaming

    Joined:
    May 17, 2023
    Posts:
    5
    It seemed to me at first, but it was because I disabled the access to that list. I initialized the objects in the scriptable object with 'new', but it keeps giving the same (strange to me) error: the first access goes ok, the second seems to lose the link to the list and gives a null reference notice... Now I have bypassed the problem saving a list of int Ids of the cards, but I don't like to let things unexplained. I repeat the problem:
    I defined a scriptable object class FloatList
    Code (CSharp):
    1. public class FloatLists : ScriptableObject
    2. {
    3.     public List<tCard> tgtRounds = new List<tCard>();
    4. }
    Then it is linked from a script ActionDecks where I have:
    Code (CSharp):
    1. public class ActionsDeck : MonoBehaviour
    2. {
    3. public FloatLists fLists;
    4. }
    and then accessed (to add cards) from another script through:

    Code (CSharp):
    1.     ActionsDeck.instance.fLists.tgtRounds.Add(CardDatabase.tCardList[targetRndNm]);
    2.  
    As I already reported, the first time I call this list, it works and a tCard is added to the list. The second time the thing just doesn't go through, though the tCard exists because a debug.log reports its properties fine just before the error.
    Thanks a lot to anyone that can give me a clue...

    The log of the error:
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. GameManager.RoundSetup (System.Int32 roundNr) (at Assets/Scripts/Managers/GameManager.cs:78)
    3. TurnManager.EndTurn () (at Assets/Scripts/Managers/TurnManager.cs:59)
    4. TurnUIController.EndTurnBtnPressed () (at Assets/Scripts/Controllers/TurnUIController.cs:34)
    5. UnityEngine.Events.InvokableCall.Invoke () (at /Users/bokken/build/output/unity/unity/Runtime/Export/UnityEvent/UnityEvent.cs:178)
    6. UnityEngine.Events.UnityEvent.Invoke () (at /Users/bokken/build/output/unity/unity/Runtime/Export/UnityEvent/UnityEvent/UnityEvent_0.cs:58)
    7. UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
    8. UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
    9. UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
    10. UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
    11. UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:514)
    12.  
     
    MelvMay likes this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,560
    Not sure how a bog-standard nullref post got this long but the answer is always the same... ALWAYS!

    How to fix a NullReferenceException error

    https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

    Three steps to success:
    - Identify what is null <-- any other action taken before this step is WASTED TIME
    - Identify why it is null
    - Fix that
     
  7. GiuDilGaming

    GiuDilGaming

    Joined:
    May 17, 2023
    Posts:
    5
    Before all, thank you, Kurt, for your time.
    In the third line of following code, I am sure that tcardOne is OK, not null.
    So what is null must be 'ActionsDeck.instance.fLists.tgtRounds'
    But why does it get null after a passage in which it was OK and the permanent list of the scriptable object has the element just added in it?
    Code (CSharp):
    1.     tCard tcardOne = CardDatabase.tCardList[targetRndNm];
    2.     Debug.Log("tcardOne.Id = " + tcardOne.id);
    3.     ActionsDeck.instance.fLists.tgtRounds.Add(tcardOne);
    4.  
    I had already found your post and I did solve some other occurrences of the null ref error by that.
    But in this case what confuse me are two things:
    1. the error shows itself at the second passage on that line, the first goes smooth
    2. the permanent list to which an item is added in the first passage (round 1) is there up (initialized) and living.
    This would address my investigation onto the fact that is the link to the list that is lost, since the list itself is initialized and with an element inside... But here my fresh (and poor) experience in c# does the rest... Could it be that I use static instances in scripts and that trying to access the scriptable object list through one instance of one clas makes me lose the link to that list from the script that worked at first passage, when I get in it again for the second round? This is what I am trying to investigate... with no luck for now...wrong clue?
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,560
    Then find out what changed. There's only a handful of variables. Don't guess and ponder. Print the variables out. See below.

    Static references are always going to live forever. If you chose to use them, then it's on you to set them back to null if the target object they reference gets destroyed.

    If you want singleton-ish behaviour, try these patterns:

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance

    Alternately you could start one up with a
    RuntimeInitializeOnLoad
    attribute.

    Be sure you solidly distinguish what you want to happen at editor time vs playtime. It's important.

    --------------------

    Time to start debugging! Here is how you can begin your exciting new debugging adventures:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the names of the GameObjects or Components involved?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    If your problem is with OnCollision-type functions, print the name of what is passed in!

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    "When in doubt, print it out!(tm)" - Kurt Dekker (and many others)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
    GiuDilGaming likes this.
  9. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    That's why public fields are a bad idea.

    You're probably overwriting the list somewhere else in your code. I'd change the SO class to expose a read only public property so it cannot be modified/overwritten by accident (i.e. "Encapsulation")

    Code (CSharp):
    1. public class FloatLists : ScriptableObject
    2. {
    3.     [SerializeField] // Add this attribute if you want the list editable in Unity Editor
    4.     private List<Card> chosenCards = new(); // private, no other class can access this
    5.  
    6.     public IReadOnlyList<Card> ChosenCards => chosenCards; // we just return our private list
    7.     ...
    8. }
    Now use FloatLists.ChosenCards instead of FloatLists.chosenCards in all your code.

    If you want to initialize your list during runtime, do it inside the ScriptableObject, either in OnEnable event, or by creating a public method InitializeCards() and call it at the start of your game.
     
    GiuDilGaming likes this.
  10. GiuDilGaming

    GiuDilGaming

    Joined:
    May 17, 2023
    Posts:
    5
    tried your solution, but then Add() and RemoveRange() don't work with IReadOnlyList<>.
    Methods to do the same things? (If you know them ready, do not waste time searching...and thanks!)
     
  11. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    Yes, it's called an IReadOnlyList, you can only read from it. It's a bad idea to Add/Remove items from a list from anywhere, it should only be the responsibility of the class containing the list to do it. If you want to add or remove items to it, do it to the chosenCards private field, which is a normal List.