Overview
When writing Unity scripts, we naturally use Update() for per-frame processing. But when hundreds or thousands of objects each have their own Update(), have you considered what's happening under the hood?
Unity's Update() calls carry non-negligible overhead from crossing between C# managed code and Unity engine native code (C++). As object counts increase, this overhead accumulates and can become a performance bottleneck. This issue is frequently discussed in high-performance development communities.
This article details the advanced optimization technique Custom Update Manager for solving this Update() call cost problem.
Why Does Update() Get Slow?
MonoBehaviour's Update() is called by the Unity engine. The process:
- Unity engine (native code) iterates through the list of all active objects with
Update(). - For each object, it crosses the "bridge" from native to C# managed code to call
Update(). - After C#
Update()completes, it crosses back to native code.
This "bridge crossing" (also called marshalling) has a cost even if Update() is empty. With 1000 objects, there are 1000 bridge round-trips per frame. This is especially wasteful when Update() heavily uses early returns (if (!condition) return;).
The Custom Update Manager Concept
The idea solving this problem is the "Custom Update Manager":
"A single special manager class in the scene receives Unity's Update() call, then directly calls the update methods of all other objects that need updating from within C# code."
This reduces native-to-C# bridge round-trips to just one per frame. The manager's calls to each object happen entirely within C# managed code, making them very fast.
Implementation
Let's look at the concrete implementation.
Step 1: Define the Updatable Interface
First, define an interface as the common "contract" for objects with update processing. This lets the manager call a defined method regardless of the actual class type.
// IUpdatable.cs
public interface IUpdatable
{
void ManagedUpdate(float deltaTime);
}
Step 2: Create the Update Manager
Next, create a Singleton pattern manager class placed once per scene. This class receives Unity's Update() and calls ManagedUpdate() on registered objects.
// UpdateManager.cs
using System.Collections.Generic;
using UnityEngine;
public class UpdateManager : MonoBehaviour
{
// Singleton instance
public static UpdateManager Instance { get; private set; }
// List holding objects to update
private readonly List<IUpdatable> updatables = new List<IUpdatable>();
void Awake()
{
// Singleton setup
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
void Update()
{
float dt = Time.deltaTime;
// Call update method on all registered objects
for (int i = 0; i < updatables.Count; i++)
{
updatables[i].ManagedUpdate(dt);
}
}
// Method to register objects to update list
public void Register(IUpdatable updatable)
{
if (!updatables.Contains(updatable))
{
updatables.Add(updatable);
}
}
// Method to remove objects from update list
public void Unregister(IUpdatable updatable)
{
updatables.Remove(updatable);
}
}
Step 3: Implement the Updated Class
Finally, implement the IUpdatable interface on classes that need updating. Register with the manager in OnEnable, unregister in OnDisable. This class no longer uses Update().
// MovableObject.cs
using UnityEngine;
public class MovableObject : MonoBehaviour, IUpdatable
{
public float speed = 2f;
void OnEnable()
{
// Register self with update manager
UpdateManager.Instance.Register(this);
}
void OnDisable()
{
// Check instance exists before unregistering
if (UpdateManager.Instance != null)
{
UpdateManager.Instance.Unregister(this);
}
}
// Called by manager instead of Update()
public void ManagedUpdate(float deltaTime)
{
transform.Translate(Vector3.forward * speed * deltaTime);
}
}
Done! Place an empty GameObject with UpdateManager in your scene, attach MovableObject to many objects, and run. Visually identical to using Update(), but the Profiler shows dramatically reduced Update.ScriptRunBehaviourUpdate call counts and improved performance.
Benefits and Considerations
- Benefit: Major performance improvement in scenes with many objects.
- Benefit: Dynamically control update enable/disable via
Register/Unregister—remove objects from the update list entirely when not needed, reducing wasteful if-statements. - Consideration: No need to convert all project
Updates to this system—apply only to truly numerous objects (bullets, enemy swarms, effects). - Consideration: Similar managers can be created for
FixedUpdateandLateUpdate.
Summary
Custom Update Managers are a refined technique that understands Unity's Update calling mechanism and circumvents its bottlenecks. Not essential for all projects, but especially for mobile games or large-scale games with thousands of moving objects, this knowledge is a powerful weapon.
Update()calls have native/managed overhead.- Custom Update Managers consolidate calls into one.
- Implement using interfaces, singletons, and lists.
Remember this technique when pushing performance limits.