Avoiding Unity Update Loops
The Update loop is the most commonly used function to implement any kind of game behaviour, according to Unity's documentation. As such it's important to address the performance impact it has on your game since logic in the Update function runs every frame. Update is convenient and time-saving because it requires little planning to use and provides immediate results, but this allure can be an issue as your codebase grows. It's especially problematic if you have many characters or smart objects within your game world constantly checking for conditions or executing complex logic. I'll demonstrate how to avoid the Update loop using either Events, Callbacks, Coroutines, or the Invoke function.
Before diving into programming techniques you should first understand when it's appropriate to use the Update function. For me to maintain a performance-based mindset, I generally consider the Update function a bad thing and avoid it when possible. Logic in any of the Update functions should be simple; if something does not need to be calculated every frame then do it elsewhere.
- Use Update for Input and Movement
- Use FixedUpdate for continuous physics related logic
- Use LateUpdate for camera movement/rotation
Usually anything else doesn't need to be in the Update loops and comes down to being handled by an appropriate technique, at an appropriate time. The techniques listed below cover most cases I've encountered where an Update loop could be optimized.
Events
Events are my go-to solution when an action occurs and something (often many) needs to know about it. For example if the player gains money then the UI should be updated to reflect the new total. If the money is gained while browsing a store, some items may need to change state (not purchasable => purchasable). Rather than having the UI constantly check the player's money in an Update loop, or having messy logic where a bunch of methods are called on interested objects, you can instead use an event.
In your IDE (MonoDevelop, Visual Studio, etc) an event is typically associated with an icon of the letter 'E' or a sweet lightning bolt. An event is essentially a list of methods who have subscribed to it, and when the event is raised it iterates through the list, calling each subscribed method. Creating and using an event is accomplished in a few short steps as shown in the below code with comments.
Defining and Raising an Event
Subscribing to Events
You can unsubscribe from an event using the minus sign (inventory.OnMoneyUpdated -= OnMoneyUpdated). I usually see this with static events since they are not reset with scene loads and can reference a method on a destroyed object. Try to avoid this situation as it unnecessarily complicates your codebase.
Callback Functions
I believe callbacks were first seen in the Unity API when they introduced their Social API. They accomplish a similar task as events, but serve only a single subscription rather than many, and are usually called just once. Those who've used JavaScript and JQuery should be familiar with the concept of callbacks.
When calling FunctionA, you can pass another FunctionB as a parameter, usually with the assumption that FunctionB is called once FunctionA has completed its task. This is especially useful when the task you're waiting on may take multiple frames to complete, like a web request to a server, which is why callbacks are seen throughout examples in the Social API. So rather than continuously checking for a server response in an Update loop, use a callback function.
Callback Example
Here you see a method which accepts a callback parameter and uses it later.
While in this example you see the callback method defined and passed as a parameter.
Callback methods can also have parameters.
Callbacks via Lambda Expressions
Often times using anonymous functions is the go-to approach for callbacks, as you may have even noticed in Unity's Social API examples. They save the need to define a function in your class while keeping logic contained and easier to follow at a glance. These are unnamed functions with named parameters that automatically match the types defined in the callback. The lamda expression => is used to create a local function, with parameters on the left and the statement block on the right. Here's the first example from above rewritten with a lambda function.
And here is an example using callback parameters.
You can also create a class property that is a delegate function and assign it for use as a callback. You can simply assign an appropriate function as the delegate callback to be used until otherwise assigned.
Using Coroutines/IEnumerators as Update Loops
Coroutines are common place in Unity games, so I'll assume you have some experience with them already. What you may not have considered though is creating your own form of Update loop using a coroutine. It's not a common use-case, but it can be useful when wanting something to update frequently, but less frequently than every frame. For example you could create a function that runs every 10 frames, or every half-second, or even at a random interval. This can be useful for AI decisions or something that can afford to be a bit 'choppy' such as retro-style character movement.
You can also stop these coroutine loops using the StopCoroutine function.
Invoke & InvokeRepeating
The Invoke function is neat tool to have at your disposal. You pass a method name as a string argument and it calls that method after a specified amount of time. Think of it as an alternative to using time-based coroutines, with built-in logic to avoid edge cases your coroutines might miss, such as dealing with Time.timeScale. It can also be useful (but potentially insecure!) in online multiplayer development where only primitive types are used as RPC arguments, allowing string methods to be invoked in situations you can't account for, such as user-made mods.
InvokeRepeating is what we'll focus on today though, as it can accomplish the same thing as the Update loop. Basically it's the same solution as explained in the Coroutines section above, except it only deals with time. You specify a function to be called repeatedly with a specified delay between each call. You can also stop it from repeating using the CancelInvoke function.