GetComponent().variable returning empty variable (not null)

I am trying to use a C# script to access a public variable in another C# script, and to do so I am using GetComponent. It gave me no problems until a few days ago. However, now GetComponent is returning an empty variable. Not null - it has the same type that it is supposed to, and does not throw a Null object exception, it’s just…empty. I don’t know what the technical term for this is. If the variable I am trying to access is an array, it returns an array of length 0. If it is a string, it returns an empty string.

The first script in my case is called TextHandler. It is attached to a GameObject called TextHandler in my scene, and reads a CSV file one line at a time and outputs each line into a float[ ] called nums.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class TextHandler : MonoBehaviour
{
   string path = "Assets/" + "test.csv";
   StreamReader reader;
   public float[] nums;
  
   float[] ParseString(string str) {
      
       string[] spl = str.Split(",");
       int l = spl.Length;
       float[] arr = new float[l];
      
       for (int i = 0; i < l; i++) {
           arr[i] = float.Parse(spl[i]);
       }
      
       return arr;
   }
  
   // Start is called before the first frame update
   void Start()
   {
       reader = new StreamReader(path);
   }

   // Update is called once per frame
   void Update()
   {
       //Read the text from directly from the test.csv file
       string line = reader.ReadLine();
       Debug.Log(line);
      
       nums = ParseString(line);

   }
}

It also prints each line to the console. It does this with no issues. I also used a loop to print each element of nums to the console, and also had no issues with that.

The second script is called TestNums, attached to another GameObject in the scene. It tries to access the variable nums in TextHandler, and print its first element to the console.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestNums : MonoBehaviour
{
   TextHandler data;
   float[] nums;
  
   // Start is called before the first frame update
   void Start()
   {
       data = GameObject.Find("TextHandler").GetComponent<TextHandler>();
   }

   // Update is called once per frame
   void Update()
   {
       nums = data.nums;
      
       Debug.Log("num: " + data.GetComponent<TextHandler>().nums[0]);
      
   }
}

This script gives me an IndexOutOfBounds error at runtime, because nums[0] doesn’t exist. nums in the TestNums script is a float[ ] with length 0. It is supposed to have length 22.

There are no compilation errors or other runtime errors. GetComponent successfully accesses the TextHandler GameObject, the TextHandler component within that GameObject, identifies the variable within the component, and returns a variable with the same type. It just doesn’t return the right value.

Things I tried already:

  • Printing nums itself to the console instead of one of its elements, to see whether GetComponent was accessing the correct variable. This gives an output of System.Single[ ], which is expected for a float[ ].
  • Initializing nums as a float[22], in case the call to nums[0] happened before the variable was assigned the first time. No effect, same error.
  • Making nums in TestNums public and using the Inspector to set nums as a list of 22 elements (same reason as above). No effect, same error.
  • Making the variable line in TextHandler public and trying to access it instead of nums, in case the problem was just with the one variable. It returns an empty string.

I am using Unity 2022.3.5f1 on Windows 11. Does anyone know what the problem here is? Did I miss something in my code, or is there a bug in Unity?

That’s a lot of things to try before reaching for debugging!!

Debugging lets you find WHAT the problem is and then you go fix it, rather than just sorta fishing around.

My first suspicion is simply order of execution, since you have cross-script dependencies both running in Update()

You’re also reading the entire file every Update() call. That’s weird.

The way you read (out of Assets/) will fail 100% of the time in built games. It will only ever work in the editor.

And you’re also using GameObject.Find(), which is almost always a Bad Idea™. (See bottom)

If you ever need more information about what your program is doing as well as how and where it is deviating from your expectations, that means it is…

Time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

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://discussions.unity.com/t/700551 or this answer for Android: https://discussions.unity.com/t/699654

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://discussions.unity.com/t/839300/3

If you are looking for how to attach an actual debugger to Unity: https://docs.unity3d.com/2021.1/Documentation/Manual/ManagedCodeDebugging.html

“When in doubt, print it out!™” - Kurt Dekker (and many others)

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

Remember the first rule of GameObject.Find():

Do not use GameObject.Find();

More information: https://starmanta.gitbooks.io/unitytipsredux/content/first-question.html

More information: https://discussions.unity.com/t/899843/12

In general, DO NOT use Find-like or GetComponent/AddComponent-like methods unless there truly is no other way, eg, dynamic runtime discovery of arbitrary objects. These mechanisms are for extremely-advanced use ONLY.
If something is built into your scene or prefab, make a script and drag the reference(s) in. That will let you experience the highest rate of The Unity Way™ success of accessing things in your game.

“Stop playing ‘Where’s GameWaldo’ and drag it in already!”

Don’t use GameObject.Find, just assign a reference to the component via the inspector.

1 Like

Done. That alone didn’t fix the issue, as I suspect (as Kurt Dekker above does) that it has to do with update execution order, but if it helps in other ways, like using less memory, than that’s good.

Execution order was my first thought too, and following your suggestion to do more debugging confirmed that. I did not actually know until just now (even though there’s a button right there in the Inspector? hello???) that you could actually change that in the Project Settings. So I manually set TextHandler to execute before TestNums, and all was well once more. LOL.
I’m not sure what determines the default order in which scripts are loaded. Maybe it changed somehow in between last week and now, which is why this problem seemed to appear out of nowhere. In any case, thank you for your help!

Regarding your other points, I’m not sure what you mean by reading the whole file every Update? As far as I can tell, the StreamReader only reads one line per Update. Also, the CSV file is a temporary testing thing, don’t worry. The numbers in the actual game are meant to come from a custom controller.
I did remove the Find() call and replaced it with an Inspector reference. Hopefully that will help the game in other ways.

Execution order, aside from the order of execution manager, is non-deterministic. But you didn’t need to modify execution order to solve this.

We have both Awake and Start for a reason. Use Awake for self initialisation, and Start for initialisation that depends on anything else.

1 Like