Unit returning null when called by GridObject script

I’ve been following the course so far up to the implementation of the Level Grid and I’ve hit my first snag. I’ve rewatched the video a few times but haven’t been able to pinpoint why exactly the Unit returns null when it’s called in Grid Object’s ToString function. To demonstrate what is happening:

Now, I’ve tried to isolate that it wasn’t something completely silly so I tested that it wasn’t a case of the text on the new line getting cut off. Logging the unit also confirmed that it’s returning null. By using _unit.name, I was also able to get a breadcrumb trail of where the problem may be coming from… but this is where I’m lost.

It doesn’t help that I’ve not 100% understood the implementation in this video. In past videos, I’ve been able to take on the challenges and figure them out on my own, but the challenge in this video is where I started to draw a blank. So it’s completely possible that I’m overlooking something major because of that. I figured a fresh pair of eyes would help me see what I’m missing.

As for what I’ve tried so far:

  • Since I got the error that the reference was not set to an instance of an object error, I’ve doublechecked all of my game objects and confirmed that nothing is missing its respective reference/prefab.
  • Prior to this video, all unit/grid/mouse functionality was working as intended so my best guess is that the problem is isolated to one of the scripts covered in this video. But after several passes through the content, I’ve not been able to spot where my scripts and the video’s scripts differ.
  • In my scripts, I did change some naming conventions. Mostly, it was stuff like using underscores for private variables because I was starting to lose track of what was an argument being passed in and what was a private class variable. I’ve used both Visual Studio’s refactor tool and reference search tool to try to see if it was a simple typo issue. But, as far as I’ve spotted, everything should be about the same.

Still, here is a screenshot of my scripts just in case it really is just a typo issue or if I mistakenly left out a line of code:

This is what I understand is happening, and is maybe what’s going wrong:

  1. In Level Grid’s Awake function, a new Grid System is created (a 10 by 10 group of cells that are 2f in size).
  2. A new Grid System is created — instantiating a new Grid Object array, creating a new Grid Position for each cell in the (in this case) 10 by 10 group of cells, and creating a Grid Object at each position.
  3. Level Grid also calls Level System’s CreateDebugObjects function which also creates a new Grid Position for each cell in the specified number of cells and instantiates a debugPrefab object (which has a TextMeshPro component). It then sets a private variable to reference the GridObjectDebugger script on each object and sets the Grid Object for each one.
  4. In the GridObjectDebugger script itself, the respective Grid Object is set by the called SetGridObject function along with the TextMeshPro’s text field (to whatever Grid Object’s ToString field is).
  5. Grid Object’s ToString string is set to both whatever that Grid Object’s Grid Position ToString field is set to plus the name of the unit on a new line.
  6. Each Grid Position has already been created and the ToString string has been set to whatever x and z value it was instantiated with. And that is confirmed to show correctly on the debugPrefab text.

I’m getting lost in the sauce, but bear with me. My guess is that in this wild turn of events, each Grid Object is not receiving a reference to a unit before the debugPrefab’s text is set by checks notes Level Grid (?) when it creates each debug object. But:

  1. In the Unit script, a private Grid Position reference is created using the Level Grid singleton’s GetGridPosition function which should pass the unit’s current position into Level Grid’s private Grid System and return a new Grid Position based on the x and z values it was given. This new Grid Position is saved as a private variable by the Unit script.
  2. The Unit script then moseys up to the Level Grid singleton again to use the SetUnitAtGridPosition function passing in the private Grid Position just created and itself. Said function creates a private Grid Object reference that uses Level Grid’s private Grid System reference to pass the Unit’s private Grid Position reference into the GetGridObject function.
  3. The aforementioned function returns a reference to Grid System’s private Grid Object Array (at the coordinates passed in as arguments) … uh. Hold on. Let me see where I’m at. Actually, this is where I’m lost.

Now, again, it’s possible that this error is just a typo or something stupid. But, since I don’t wholly understand what’s going on in the code, I’d really appreciate if anyone could explain the steps I’m misunderstanding.

EDIT: I searched the references to the SetUnit function in the Grid Object script. _unit should be set to an instance of a unit by the SetUnitAtGridPosition function or set to Null by ClearUnitAtGridPosition. So… I’m maybe back to the drawing boards entirely. Technically, most cells should return null so that’s not really an error. But there are at least two units in my scene right now so 2 out of 100 cells should return a unit rather than null… but they aren’t. And I still don’t know why.

I’m not seeing the Debug.Log(); in your screenshots, what were you trying to print? _unit?
The screenshot that you posted only has _gridPosition as being accessed and _gridPosition is a struct so it can’t possibly be null.

From the stack trace in that screenshot I can see it happens exactly after calling GridObjectDebugger.SetGridObject(); are you trying to access the unit there? Only 2 positions should have a unit, all the others will have null so you can’t directly try to access the _unit.name on all of them.

It seems like the issue is just that, you’re trying to access the _unit as soon as the GridObjectDebugger is created, but you have those debug objects being created on Awake(); whereas the _unit is only set on Start();
So at the time when GridObjectDebugger is created all the _unit fields will still be null.

Add a Debug.Log(_unit); on GridObject.SetUnit(); it should be receiving the Unit reference and should print twice.

Add a Debug.Log(); on the LevelGrid.SetUnitAtGridPosition(); are you calculating the GridPosition correctly? Are you receiving the Unit reference? Is it called twice?

1 Like

Here’s the screenshot of both the Debug.Log in Grid Object script and the output. All 100 objects return null so my guess is still that the unit isn’t being passed before ToString is called:

I managed to fix it (somewhat) by placing _gridSystem.CreateDebugObjects(gridObjectDebugger); in Start rather than Awake. But… it created a new issue where only one unit did not return null. It’s still possible that not all Unit references are passed before GridObject’s ToString is finished, but I’m not completely sure how to manage the load order without breaking something else (if I move everything from Unit’s Start into Awake, there’s also a chance that that will happen before Level Grid’s Awake is finished).

Logging _unit in SetUnit did confirm that it’s happening out of order. Thank you! My dilemma now is figuring out a better way to structure the order of functions like this. Is there a way to manage the order manually in cases like this? Otherwise, I worry I’m at the mercy of Start and Awake functions happening as I hope they will.

I also added a Debug.Log to SetUnitAtGridPosition. It confirms that the function is also being called after Grid Object’s ToString function. But I’m still on the fence about how to manage the call order. I’m going to test moving Unit’s Start into Awake, but I have a feeling that might be a band-aid fix even if it works.

…aaand as feared, moving Unit’s Start into Awake creates another weird loading order issue. This time, it’s the unit at 0,0 that makes it way to Grid Object’s ToString function first (go figure). And there’s a NullReferenceException likely because Level Grid and Unit are trying to interact before either are finished doing what they need to do (creating Level System and setting a reference of itself on the grid, respectively).

EDIT: I did… technically get it to work. Somehow, this felt wrong, but I added a coroutine to wait .002f seconds and… Well. The function happens just after Unit’s Start and just in time for GameObject’s ToString to have a reference to _unit. Now… I have to assume that ‘working’ in this case is not the best practice. Would there be a more eloquent solution here?

EDIT2: Success! Mostly! I added Unit to -5 in Unity’s Script Execution Order (like UnitActionSystem was set to -20 in a previous video) and that also has the same result! Now, I don’t know if this may have some further issues down the line with load orders in the future. But, I prefer this to the Wait coroutine which could easily be thrown out of whack due to a PC’s ability to load Unit’s Start happening too slow (slower than 0.002f seconds or whatever arbitrary value I set it to).

Oh my God. I’m actually dying. It’s too late for me.

So… while continuing with the video, I couldn’t — for the life of me — figure out why the units were not updating on the grid. Debug.Log was indeed showing me that the references were being passed, but the text was not updating.

That’s when it hit me. I opened the GridObjectDebug script, checked the references to the SetGridObject function and realized, “…wait. This is only ever called once at LevelGrid’s start.”

So… as it would happen. The GridObjectDebug script did not have an Update function. I don’t know how I missed it, but I guess since everything had been working up until that point, I blanked on it and moved on.

I guess the mistake led to me also troubleshooting ways to tweak script load orders, but jeez. Originally, the script would’ve not found a unit on said grid position on LevelGrid’s start function but would have eventually just updated itself anyway. I guess my project now technically has the units displayed on GridObject’s ToString function the first frame it’s called rather than being updated a fraction of a second later. I’m not sure it truly matters though.

4 Likes

Heh that sounds like you went through a very stressful journey, but you also learned a lot!

4 Likes

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms