Game Update Manager¶
Overview¶
A Custom Game Update Manager is a common design choice in intermediate - large sized games that have hundreds
or thousands of objects calling Update() each frame. It allows us to remove the overhead from the
Unity base Update() and set different update priority tiers so everything doesn't need to be called
each frame.
Tutorial Video¶
Recommended Experience¶
I would consider the Custom Update Manager to be an Intermediate topic in Unity.
Its implementation is very straight forward, but it requires an understanding of some basic concepts and isn't necessary on smaller scale beginner projects.
I recommend you go into this with a base understanding of:
- C# Interfaces
- Unities Lifecycle Calls ie.
Update(),FixedUpdate(),LateUpdate() - Understanding of memory Allocation and De-Allocation with
OnEnable()andOnDisable() - Understanding of the Singleton Pattern and how Instances work
Using a Custom Game Update Manager¶
Why Use One?¶
-
Performance
Less overhead than the base
Update()call which saves CPU Performance. -
Prioritisation
Specify how often
Update()needs to be called in each script. -
Cache Efficiency
Groups similar priority updates together compared to
Update()being scattered across multiple components. -
Debugging and Profiling
Centralised update logic makes it easier to debug and profile performance
Common Use Cases¶
Some common use cases for a Custom Update Manager would include:
- Mobile, VR and XR Games where CPU optimization and performance is critical.
- Games with hundreds of active entities such as a Tower Defence or RTS Game.
- A City Builder game with hundreds of update calls for NPC's, vehicles and buildings.
Beginner Method Replacement¶
The beginner-friendly approach that Game Update Manager replaces would just be using
the Unity MonoBehaviour Update() for every component in the game.
When Not to Use Game Update Manager¶
Situations where Game Update Manager may be unnecessary or overkill would be for small projects or rapid prototypes. The overhead is negligible unless Update is being called on many different components.
Game Update Manager System Diagram¶
Here you can see the flow of how the system works. The key feature of an Update Manager is the Update Priority which you define when registering.
flowchart TD
Component[Component]
IUpd[IUpdateable]
Register[Register with Manager]
Manager[GameUpdateManager]
High[High Registry]
Medium[Medium Registry]
Low[Low Registry]
IterateH[Iterate High]
IterateM[Iterate Medium]
IterateL[Iterate Low]
OnUpdate[Call OnUpdate]
Component --> IUpd
IUpd --> Register
Register --> Manager
Manager --> High
Manager --> Medium
Manager --> Low
High --> IterateH --> OnUpdate
Medium --> IterateM --> OnUpdate
Low --> IterateL --> OnUpdate
Game Update Manager Code¶
Code Examples¶
IUpdateable Interface
Here we have the interface that Components inherit so they can have access to the
OnUpdate() method.
Update Priority Enum
We create an Update Priority enum so we can safely separate our components into lists to be called at their set rate. This is passed through when registering so the component can be added to the related list.
Game Update Manager
Here we have the main manager file which is a singleton that handles registering,
unregistering, and updating every component that uses the IUpdateable interface.
public class GameUpdateManager : MonoBehaviour
{
public static GameUpdateManager Instance { get; private set; }
private readonly List<IUpdateable> _highPriorityUpdates = new();
private readonly List<IUpdateable> _mediumPriorityUpdates = new();
private readonly List<IUpdateable> _lowPriorityUpdates = new();
private const float MediumPriorityInterval = 0.2f;
private const float LowPriorityInterval = 0.4f;
private float _mediumPriorityTimer;
private float _lowPriorityTimer;
private static void Iterate(List<IUpdateable> updates)
{
for (int i = 0; i < updates.Count; i++)
updates[i].OnUpdate(Time.deltaTime);
}
private void HighPriorityUpdate()
{
Iterate(_highPriorityUpdates);
}
private void MediumPriorityUpdate()
{
_mediumTimer += Time.deltaTime;
if(_mediumTimer < MediumUpdateInterval) return;
Iterate(_mediumPriorityUpdates);
_mediumTimer = 0f;
}
private void LowPriorityUpdate()
{
_lowTimer += Time.deltaTime;
if(_lowTimer < LowUpdateInterval) return;
Iterate(_lowPriorityUpdates);
_lowTimer = 0f;
}
public void Register(IUpdateable updateable, UpdatePriority priority)
{
switch (priority)
{
case UpdatePriority.High:
_highPriorityUpdates.Add(updateable);
break;
case UpdatePriority.Medium:
_mediumPriorityUpdates.Add(updateable);
break;
case UpdatePriority.Low:
_lowPriorityUpdates.Add(updateable);
break;
default:
throw new ArgumentOutOfRangeException(nameof(priority), priority, null);
}
}
public void Unregister(IUpdateable updateable)
{
if (_highPriorityUpdates.Remove(updateable)) return;
if (_mediumPriorityUpdates.Remove(updateable)) return;
_lowPriorityUpdates.Remove(updateable);
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
private void Update()
{
UpdateHighPriority();
UpdateMediumPriority();
UpdateLowPriority();
}
}
Tutorial Video Source Files¶
Final Thoughts¶
Once you get the hang of using your own custom Game Update Manager, it becomes very fast and easy to implement. It's a nice way to learn how to start doing your own thing in an engine like Unity where things are made very easy for you in places. That does not mean they are made optimally though and this is just one of a few tricks professional studios use to squeeze as much performance as they can out of it.