Lesson 49 - Dealing with Null References

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Transcript

Alright, before we go on to the topic for this lesson, which is dealing with null references, there's a bug that we produced a few lessons back when we moved values that were assigned at the class level, we moved them to Start() method. The bug that popped up is kind of interesting and I want to quickly fix it and cover the reason for why it popped up. First I'll show you the bug. I’ll try to time this accurately so we’ll be in PowerUp right before the next spawn. Alright, so a good place to start to figure out why this is happening, is to look at the Speed field here for the SphereController. After all, that's what seems to be at fault. The Spheres are going too fast when thy shouldn't be. Right. Why is this happening exactly? Well, here's why.

The Speed field, here, it's static so it's shared across all instances of Spheres. But now that we moved the initialization of the static field to the Start() method, each time a new instance is created it runs that instance’s Start() method. That Start() method will reset the value back to its original value which we expect to be half the value when we're in powered up mode. Right. That's the simple explanation for why this bug popped up. It's a good example of how you can fix one problem, we had the problem of static fields retaining their values when restarting the game. That's the reason why we tried to fix that problem. It caused another bug to pop up, like coding whack-a-mole, as often happens.. I think the easiest way to solve this is to have the Speed value .. Move it back to being assigned and instantiation and then when the game resets we just make sure to reset these values, as well, in the GameStartManager. Let's re-move this here, reassign it back here. We'll do the same for the CubeController just for consistency sake.

The manager, the GameStartManager, we'll just make sure to reset those values. Alright, onto the main topic of this lesson, null references. Null reference means exactly what it implies. An object in code, a reference type, remember, that gets destroyed and yet other blocks of code continue to refer to the object as if it exists. These code blocks that refer to it, they get null returned because that memory pointer or reference is no longer pointing to a particular spot in memory since it's been destroyed. Right. We've had a null reference hanging around our code ever since the beginning of this course, which I'm betting you've noticed whenever our Cube gets eaten and the Debug.Log() complains. Sorry, Let's clean up our Debug.Log() here. Let's get rid of that. We don't need that anymore. Let's do this again. Keep an eye on the console for that null reference error to pop up. As you've probably seen a bunch of times, it’s actually hidden behind here, ‘MissingReferenceException: the object of type game object has been destroyed but you're still trying to access it.

Your script should either check if it's null or it should not destroy the object.’ That's a pretty good hint. Actually, it implies exactly what we're going to do to fix this, in this lesson. Now with our project almost done, I feel this is the right time to deal with this issue. We absolutely have to deal with it, null references are not something you want lingering in your code. The correct way to deal with null references or at least the most basic way of dealing with them, is to take all of your code blocks that refer to the object that might become null, and wrap these code blocks in a conditional check that first checks whether or not the object is null, and only executes the relevant code if it's not null. In other words, we would have to do something like this. Our Cube is being destroyed and becoming null, so wherever we have a reference to the Cube GameObject, one way we can do this is to just search our entire document for the Cube.

Well that's going to take a little too long, so I'll just show you an example of one way of fixing this without committing to it. We know that there's a reference to the Cube GameObject in the SphereController, right? For example, where we're tracking the movement of the Cube right here. We can just wrap this entire code block in a check, to check if it's null. If (CubeReference !=null) so as long as it's not equal to null, go ahead and execute this code block; Otherwise if it returns null, then don't. That avoids that null reference error but you'd have to do this with every case in which you have reference to this game object. We could do that, but we have to ask, “Do we even have to destroy the Cube GameObject to begin with?" In this example .. Here, I'll undo all this since we're not going to commit to it. In this example, this is the culprit for the null reference error. Now, if this were an object that could get out of control, in memory in some way, I'll say it was a fired bullet. In which case, each fired bullet, if it were not destroyed in memory, you'd imagine it would just build and build until you have this cache of unwanted bullets that you still don't want to get rid of for some unknown reason.

Kind of a twisted programmer’s version of a hoarder. Just clogging memory space for no particularly good reason. In this case, we're not in that kind of danger. It's just a single GameObject. We only need it to be gone at the end of the game, at which point we have very few options but to restart the game or quit. What we can do instead is just make the object invisible, and handle this quite differently and maybe even come up with a more interesting game over scenario. I'm going to comment this out and place a comment here for future reference. “Removed to fix null reference in final build.” Now, we'll deal with the GameObject this way, when it gets eaten. I'm going to put a comment here and say “Disable CubeController Script” so we can't move, for example. To do that, tap into the CubeReference, use GetComponent<CubeController>() to grab the CubeController, the actual script, and just set it's enabled property to false. Alright, simple.

That takes care of that part. Comment, say, “Cube gets swallowed up.” We'll animate a swallowed up animation, by the Sphere. For that just say, take that CubeReference, and take its transform.localScale and have that as a result of a Vector3.Lerp() from it's current transform.localScale. Get this out of the way. All the way to the end state of a new Vector3. We'll want to scale it all the way down until it's pretty much invisible. And at this rate, why not make it a little bit more interesting, the animation? Let's make it spin. “Cube spins as it's getting swallowed.” Again, reference the transform, this time rotation. Set that to a new Quaternion() which is just a fancy term for rotation.. It's very similar to a Vector3, except it has four parameters x, y, z and w. For that, we'll set it to 0, 0... The only part we're actually interested in on a 2D plane is transform.rotation.z. That's the... That'll be that parameter that would affect the rotation that we'd see visible on 2D plane.

We'll multiply it, we'll make it spin half the Speed, we'll multiply it by .5. Half the speed of whatever the rotation speed of the Sphere is rotating at. We need a ‘w’ argument so just pass in the current transform.rotation.w. Alright, now we're going to need to keep the SphereController script alive just long enough so that it creates this game over animation. As soon as the Cube's almost invisible, so by the time it Lerps its scale down to about .01 or so, we can check for when it's just about down to that low and make the Cube invisible. And also, more importantly, disable the SphereController entirely, so that points no longer get awarded and more spheres get spawned. For that, we just add a conditional, say if CubeReference.transform.localScale… and we can use x or y. I'm going to use the y property. If it's less than .011 so just about, almost all the way down to the end of the Lerp.

I'll say GetComponent<SphereController>() for this particular GameObject that this script is attached to. We'll have multiple scripts attached to multiple GameObjects, just remember that. That's why this is going to work. Set enabled = false. We'll also want to disable the SpriteRenderer for the Cube. I'll say GetComponent<SpriteRenderer>().enabled = false. That's the way we're going to solve this null reference issue. It's far from perfect. I mean, one obvious issue that we have left over is that the score keeps on increasing for a moment as we're getting swallowed up, but that's okay. I'm not going to worry about solving that issue. You can take that up as a personal challenge if you want. There's a lot of ways you can go about solving that. The way that we fixed the main issue is all I was really interested in, and it's good enough for what we need. Alright, that's it for this issue. I'll see you the next lesson.


Related Articles in this Tutorial:

Lesson 1 - Who This Course is For

Lesson 2 - What to Expect from this Course

Lesson 3 - Installation and Getting Started

Lesson 4 - Starting the First Project

Lesson 5 - Prototype Workflow

Lesson 6 - Basic Code Review

Lesson 7 - Game Loop Primer

Lesson 8 - Prototyping Continued

Lesson 9 - C# Fundamentals and Hello World

Lesson 10 - Variables and Operations

Lesson 11 - Variables and Operations Continued

Lesson 12 - Floats, Bools and Casting

Lesson 13 - If Statement Conditionals

Lesson 14 - If Statements Continued

Lesson 15 - Complex Evaluations and States

Lesson 16 - Code Syntax vs. Style

Lesson 17 - Variable Scope

Lesson 18 - Object-Oriented Programming Intro

Lesson 19 - OOP, Access Modifiers, Instantiation

Lesson 20 - Object Containment and Method Returns

Lesson 21 - "Has-A" Object Containment

Lesson 22 - "Is-A" Inheritance Containment

Lesson 23 - Static Fields and Methods

Lesson 24 - Method Inputs and Returns

Lesson 25 - Reference vs. Value Types

Lesson 26 - Introduction to Polymorphism

Lesson 27 - Navigating the Unity API

Lesson 28 - Applying What You've Learned and Refactoring

Lesson 29 - Constructors, Local Variables in the Update Method

Lesson 30 - Collecting Collectibles, Items and Powerups

Lesson 31 - Spawning and Managing Prefab Powerups

Lesson 32 - Implementing Powerup State Logic

Lesson 33 - Displaying Text, OnGUI, Method Overloading

Lesson 34 - Referencing Instantiated GameObjects, Parenting

Lesson 35 - Understanding the Lerp Method

Lesson 36 - Creating Pseudo Animations in Code

Lesson 37 - Understanding Generic Classes and Methods

Lesson 38 - Animations Using SpriteSheets and Animator

Lesson 39 - Working with Arrays and Loops

Lesson 40 - Debugging Unity Projects with Visual Studio

Lesson 41 - Camera Movement and LateUpdate

Lesson 42 - Playing Audio Clips

Lesson 43 - Routing Audio, Mixers and Effects

Lesson 44 - Adding Scoring Mechanics and Enhancements

Lesson 45 - Scene Loading and Game Over Manager

Lesson 46 - Understanding Properties

Lesson 47 - Controller Mapping and Input Manager

Lesson 48 - Understanding Enums

Lesson 49 - Dealing with Null References

Lesson 50 - Handling Variable Framerates with time.DeltaTime

Lesson 51 - Preparing the Project for Final Build

Lesson 52 - Final Build and Project Settings

Lesson 53 - Introduction to the Unity Physics Engine

Lesson 54 - Understanding FixedUpdate vs. Update

Lesson 55 - Movement Using Physics

Lesson 56 - Attack Script and Collision Events with OnCollisionEnter2D

Lesson 57 - Projectiles and Stomping Attack

Lesson 58 - Parallax Background and Scrolling Camera

Lesson 59 - Infinitely Tiling Background Sprites

Lesson 60 - OOP Enemy Classes

Lesson 61 - OOP Enemy Classes Continued

Lesson 62 - Trigger Colliders and Causing Damage

Lesson 63 - Multi-Dimensional Arrays and Procedural Platforms

Lesson 64 - Finishing Touches

Lesson 65 - Series Wrap


Comments

Please login or register to add a comment