Developing good habits so you don’t have to fix it later.
If it ain’t broke, don’t fix it. An old adage, and one you may have heard applied to code optimization.
This is true. Nothing that follows is meant to imply that it isn’t. Before you spend sleepless nights poring over code, scrutinizing every assignment by ever-dwindling candlelight, trying to squeeze cycles and bytes out of every nook and cranny, use the profiler. Before you decide to fix a problem, make sure it actually is a problem.
In this series, though, I want to think about something else. I want to think about habits. In particular, I want us to start thinking about how we can structure our code so we don’t create those optimization “opportunities” for our more frazzled future selves in the first place.
Sure, we’re having fun wearing our cowboy hats and allocating memory out the wazoo now. How will we feel in eight months, when our game is leaking memory out the very same wazoo? When our future selves travel back in time to slap us around and force us to read this article? We’ve all been there.
Premature optimization can lead to wasted time. But it isn’t optimization if you write it in an optimal way the first time. Let’s get used to writing that code.
Cache first, ask questions later
One common optimization is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class PositionJitter : MonoBehaviour { public bool jitter; void Update() { // Don't do this. Transform[] transforms = GetComponentsInChildren(); if (jitter) { JitterAll(transforms); } } public static void JitterAll(Transform[] transforms) { // ... } } |
Optimized to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class PositionJitter : MonoBehaviour { Transform[] transforms; public bool jitter; void Start() { transforms = GetComponentsInChildren(); } void Update() { if (jitter) { JitterAll(transforms); } } public static void JitterAll(Transform[] transforms) { // ... } } |
You’ll see this suggestion all over, including in the Unity documentation.
The difference: rather than using a GetComponent() call in the Update() method, we cache the result at Start() in a field of the MonoBehavior.
The reason: GetComponent() and its variants are slow. Doing it on every frame adds overhead to that frame. Most of the time, the result is the same, so we only need to do the search once.
Side note:
Those of you who have been using Unity since version 3-ish may remember some now-deprecated properties of GameObject, like GameObject.rigidbody, GameObject.renderer, and so forth. They were aliases for a slow GetComponent() call, which would happen each time you referenced them, but they looked like cached references that should be quick to use. That is why they were deprecated (I believe) – too tempting to put into Update(). They’ll still show up in your IDE, crossed-out. Don’t use them!
So, if you have this going on in your code, you should fix it. That would be an optimization. But what I am suggesting is to write the latter version in the first place. Rule of thumb: when you know you need to GetComponents(), put the call wherever it will be called least. Store the result in a class member rather than a variable declared in a method, where it will get tossed out at the end of the method. Start(), instead of Update(). In another public method, if the result needs to be refreshed when something else happens. These references are cheap to store but expensive to acquire.
A possible objection might be: “Well, this collection of components changes often, and I need to make sure it’s up-to-date.”
Sure, but is it as often as every frame? Might it only be a few times per scene? Only when the user takes a particular action? Figure out what causes collection to go out of date, and put the call to refresh it there. If that spot is performance-critical, maybe find another way to anticipate it. Ah, but we are getting into optimization again. The point is, whatever you do, it could hardly be worse than every frame. The idea for today is: get into the mindset that references should be acquired once and cached.
Be lazy!
In our example, we are caching the transforms at Start(). This is pretty common. But in your real-world game, things are probably complicated. One tool you might use to help organize your objects is the property.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class NonLazyExample : MonoBehaviour { private Transform[] transforms; // field public Transform[] Transforms // Property { get { return transforms; } } void Start() { transforms = GetComponentsInChildren(); } } |
A property lets you expose some internal data without letting other classes change it, if you only define get{} and not set{}. If you think this looks like a simple method that returns the private property – you’re right. Properties are just a convenient way to express a method with that purpose. If you’ve ever checked a List.Count, you’ve used a property.
Edit 19 Oct 2017: In this case, the property is a reference to an array. The reference can’t be overwritten by users of the property, but elements can still be accessed and changed – the way I phrased it was a bit misleading. If your goal is to make sure that collection isn’t changed by user classes (an admirable goal!), you might try returning an IEnumerable, or make the array into a List and return transforms.AsReadOnly(). Thanks to the redditors in this thread for pointing out these issues.
Since the getter is just a method, it can do anything a method can do. Any amount of processing you like. Maybe that property isn’t backed by data – maybe it’s calculated on every call. The point of it is that the caller doesn’t need to care.
This gives you room for another handy habit. You can make this property lazy:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class LazyExample : MonoBehaviour { private Transform[] transforms; // field public Transform[] Transforms // Property { get { if (transforms == null) transforms = GetComponentsInChildren(); return transforms; } } } |
This gives you some flexibility. If that field isn’t needed, the GetComponents call never happens, and you don’t have to worry about the performance or storage hit. The data is also cached and available to be reused by any other caller who asks for it.
However, be careful. Instead of happening on Start, the GetComponents call could happen at any time. That means it could cause a performance hitch when your game is running, if you aren’t careful about when it’s called. With this implementation, you also have to be sure that the result of that operation won’t change. Lazy properties are a useful hammer, but don’t swing it unless you mean to.
Caveats?
GetComponent() is the go-to case, but not every reference acquisition requires an expensive search. If you are using a property of another object, which you know to be a simple reference lookup, don’t worry about it. However, even if you do cache this reference, it isn’t costly.
Also, references are cheap — that is, members that are stored by reference (C# objects). Value types (like ints, floats, or C# structs) don’t require a search if they’re fields of some object – a reference to which you would need anyway. Only cache them if they’re properties (not fields!) that are expensive to calculate, or you know they won’t go out of date (like another GameObject’s position would). For example, you’d need a reference to PlayerActor
, but once you have that, there’s no need to cache PlayerActor.transform.position
.
One more thing. Caching assumes that references don’t change so much. If they’ve changed and you need them, you have to refresh the cache. If they change all the time, caching may not be a valuable tool. A colleague brought up this example: When you move things into and out of a GameObject’s hierarchy (make them child objects, or remove them as child objects), any cache of children you might have been storing goes out of date. If this might happen every time you need those references, you might want to just use the GetComponent() call anyway. Another possibility is to use a callback when the change happens – check out MonoBehaviour.OnTransformChildrenChanged().
Takeaway: What’s the habit I need to learn?
If you know you need to use GetComponent(), FindObject(), or any of their variants, or anything else that requires a search, put the call in Awake(), Start(), or another method where it will be called as infrequently as possible. There’s never a good reason to put it in Update(), LateUpdate(), or FixedUpdate(). When in doubt, err on the side of caching. It’s usually only as expensive as storing a single reference.
Make your fields as specific as you can
I see a lot of MonoBehaviors whose reference fields are mostly of type GameObject. But what you’ll almost always see is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class SpecialAbility : MonoBehaviour { public GameObject weapon; public GameObject particleEffect; public GameObject target; public Texture icon; public void DoSpecialThing() { // preliminaries... if (weapon == null) throw new System.Exception("Didn't have a weapon"); Weapon weaponComponent = weapon.GetComponent<Weapon>(); if (weaponComponent == null) { throw new System.Exception("Weapon GameObject didn't have a Weapon component."); } var particleSys = particleEffect.GetComponent<ParticleSystem>(); Texture2D abilityIcon = (Texture2D)icon; if (particleSys != null) { SendParticles(weaponComponent.emitPoint, particleSys, target.transform.position); } // ... } } |
We can avoid jumping through a lot of these hoops. Consider this code, instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class SpecialAbility { public Weapon weapon; public ParticleSystem particleEffect; public Transform target; public Texture2D icon; public void DoSpecialThing() { if (weapon == null) throw new System.Exception("Didn't have a weapon."); if (particleEffect != null) { SendParticles(weaponComponent.emitPoint, particleEffect, target.position); } // ... } } |
The part that slips people up about this is: we tend to think of everything that we store in prefabs as GameObjects. And they are! But that is the least specific thing about them. We attach components to GameObjects to give them extra capabilities, and if we want to access those capabilities in our code, we have to chase down references to those components.
Look at what happens in the editor if you aren’t specific:
You can drag the weapon, WeaponizedCylinder, into the weapon slot, as you would expect. But you can also drag the regular old cube, UnrelatedCube into there. It doesn’t have a weapon component. Same with the slot for the particle effect – you’re asking for a GameObject, not a ParticleSystem! So any old object is allowed in there.
That means, even if there is an object in the slot, the first variation of our code will throw the exception – “Weapon GameObject didn’t have a weapon component.” Not only is this an extra thing for the game designer to be careful of when setting up the asset, there are more potential errors for us to keep track of.
Contrast that with what happens in the editor if you are specific:
Notice the empty slots don’t say GameObject anymore. The weapon slot wants a Weapon type. The particle effect slot wants a ParticleSystem type. If you try to drop in the wrong one, it isn’t accepted. This makes things easier on the designer: he won’t think it was OK to put the plain old cube in the particle effect slot. It makes things easier on QA: one less potential problem to check for, even when it isn’t there.
It makes things easier on the dev, too. More concise code – obviously. The exception is more specific, if you get it: no weapon object was there. There’s no ambiguity about whether it was the right type. Less checking overall.
One last nice thing: say you DO need the GameObject of one of those components after all. So are we back to using GameObject for the weapon field, and all that ambiguity and checking?
Nope. Unity has you covered here. Every MonoBehaviour has a gameObject field. So you can just do weapon.gameObject
.
Caveats?
Interfaces. Interfaces are a mixed blessing. You shouldn’t be afraid to use them in your code. But they are not, on the surface, able to be displayed in the editor the way MonoBehavior references are. Let’s talk about that in a future article!
Takeaway: What’s the habit I need to learn?
This one is easy. Whenever you are making a field of type GameObject, think about whether you are just going to get some other component from it immediately after. If so, make the field of that type instead!
Don’t poll, call back.
Here’s a situation I recently saw in a client game:
Under heavy deadline pressure, one engineer fixed it as quickly as he could, adding this component only to the text graphic on the hat:
(Note: I’ve changed the code and distilled it to the issue at hand to protect the innocent)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class MaintainFlip : MonoBehaviour { Character guy; // The character that owns this bit of clothing SpriteRenderer spriteRenderer; // of the text graphic only public float flipRotation; // rotational offset when flipped void Start() { guy = transform.GetComponentInParent(); spriteRenderer = this.GetComponent(); } void Update() { if (guy != null) { if (guy.FacingRight) { spriteRenderer.flipX = true; spriteRenderer.gameObject.transform.rotation = Quaternion.Euler(0, 0, 180 + flipRotation); } else { spriteRenderer.flipX = false; spriteRenderer.gameObject.transform.rotation = Quaternion.Euler(0, 0, 0); } } } } |
A few things to notice about this code:
- It caches the references to the character and to the sprite renderer. We only need to get them once. That is a good habit.
- It does a null check, two assignments, and an arithmetic calculation on every frame, even if nothing has changed. On almost every frame, nothing will have changed.
- It uses polling.
Polling means checking the status of something repeatedly to watch for a change. Polling is the kids asking “Are we there yet? Are we there yet?” Wouldn’t it be nice if the kids didn’t ask, but rather waited until they were told when we’re there yet?
Instead of this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Dad : MonoBehaviour { public bool arrived; void OnCollisionEnter(Collision collision) { if (collision.collider.compareTag("destination")) { arrived = true; } } } public class Kid : MonoBehaviour { public Dad dad; void Update() { // Kids poll dad 30+ times per second! Are we there yet? if (dad.Arrived) Debug.Log("Yay!"); } } |
Do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class Dad : MonoBehaviour { public UnityEvent Arrived; void OnCollisionEnter(Collision collision) { if (collision.collider.compareTag("destination")) { Arrived.Invoke(); } } } public class Kid : MonoBehaviour { public Dad dad; void Start() { // LISTEN to Dad instead of pestering him dad.Arrived.AddListener(OnArrived); } void OnArrived() { Debug.Log("Yay!"); } } |
Note: I’m not crazy about UnityEvents, but this implementation of the pattern works out of the box and illustrates the point.
In the second implementation, the OnArrived() method is what is known as a callback – another part of the code keeps a reference to this specific method, and calls it when it’s ready. And, by the way, this is also an example of the Observer Pattern.
This example is pretty trivial. But the real-world example above ended up leading to a problem further down the line, because it was doing unnecessary work on every frame. If we are talking only about optimization, well, nothing in that update loop is particularly heavy. It’s doubtful it would have made a blip on the profiler.
But the rotation assignment ended up squashing another component’s effect on rotation, which led to the feature (text on clothes) being delayed until that could be fixed. It also buried an edge case – the character’s facing direction was not always the same as its rendered sprites’ facing direction (in some cases the character would be moving backward). This would have been caught if using a callback, because the engineer would have had to register it with the correct event – and written the event if it hadn’t existed.
Caveats?
For some low-level code, polling makes sense. For example, checking for input. If you are writing gameplay code in Unity, it’s unlikely you’re doing that. Your input code should be encapsulated in one place (which, yes, can poll), but the rest of the game should generally provide callbacks for events. If you are rolling your own graphics card drivers, you can ignore this section.
The reason we don’t want to poll for changes is because changes are rare, but polling is constant and frequent – this is wasteful.
Takeaway: What’s the habit I need to learn?
The Update loop is for something that has to happen every frame. If your component only needs to do something once in a while, then you don’t want to be checking, every frame, to see whether that once in a while is NOW. You want to be informed by the thing that changed that the time is now.
About Tricky Fast Studios
Tricky Fast Studios is a US-based game studio featuring long-time industry veterans. We provide a full spectrum of game development services including bug fixing, feature development, porting, temporary staffing, and complete development. Our recent work includes The Walking Dead: March To War for Disruptor Beam, Poptropica Worlds for StoryArc Media, the Star Trek: Timelines Facebook and Steam ports for Disruptor Beam, and Wheel of Fortune Slots Casino for The Game Show Network. We’re here to build your story!
Thanks for the article!
Pingback: If It Ain't Broke, Don't Break It: Part 2 | Tricky Fast Studios October 24, 2017
[…] This is the second article in a series about cultivating good habits so you don’t have to fix the problems that bad habits will cause. The goal here is to anticipate the problems you may need to solve and incorporate the solutions into your design. See Part 1. […]