Optimizing Your Code
Practical tips for making Unity scripts run faster and stutter less. Each tip shows the slow version, the better version, and why it matters. For the overall process (budgets, profiling on devices, engine settings), see Optimization Best Practices.
Rule zero: measure first
Most code is fast enough. Before changing anything, find out what is actually slow using the Profiler (Window > Analysis > Profiler). Press Play, record a few seconds, click a spike in the CPU graph, and look at the Hierarchy view sorted by time. It tells you exactly which scripts and methods took the most time that frame.
A doctor doesn't operate before running tests. Optimizing without profiling is guessing: you might spend a day speeding up code that takes 0.01% of the frame while the real problem is somewhere else. The Profiler is your X-ray.
The frame budget
At 60 FPS, everything (scripts, physics, animation, rendering) must fit in 16.6 ms per frame. At 30 FPS you have 33 ms. If a single frame takes 50 ms, the player sees a stutter. So both average cost and spikes matter.
1. Cache what you look up
GetComponent, GameObject.Find, FindObjectsByType and FindWithTag search for things. Searching every frame is wasted work because the answer is almost always the same.
// SLOW: searches every frame
void Update()
{
GetComponent<Rigidbody>().AddForce(Vector3.up);
GameObject.Find("Player").transform.position = Vector3.zero;
}
// BETTER: search once, remember the result
private Rigidbody rb;
[SerializeField] private Transform player; // or assign in the Inspector: no search at all
void Awake()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
rb.AddForce(Vector3.up);
}
Looking up a friend's number in the phone book every time you call, versus saving it in your contacts. The first time is unavoidable; every time after that is wasted.
2. Avoid creating garbage every frame
C# automatically frees memory you're no longer using. This is the garbage collector (GC). It's convenient, but when it runs, it can pause your game for a moment. The less garbage you create, the less often it runs.
Using paper plates at a party. Each plate is cheap, but eventually someone has to stop and take out the trash. If you use thousands of plates a minute, that trash run happens constantly and the party keeps pausing. Reusing real plates (pooling, caching) means no trash runs.
Common sources of garbage in Update:
| Creates garbage | Better |
|---|---|
"Score: " + score every frame | Only update the text when the score changes |
new List<Enemy>() every frame | Create once as a field; call list.Clear() to reuse it |
LINQ (.Where(), .OrderBy(), .ToList()) in hot code | A plain for loop |
Physics.RaycastAll, Physics.OverlapSphere (return new arrays) | Physics.RaycastNonAlloc, Physics.OverlapSphereNonAlloc with a reused array |
new WaitForSeconds(1f) inside a coroutine loop | Store it in a field and reuse it |
gameObject.tag == "Enemy" | CompareTag("Enemy") |
GetComponents<T>() (returns a new array) | GetComponents<T>(list) with a reused list |
| Lambdas that capture local variables, created every frame | Named methods or cached delegates |
// BETTER: overlap check with no garbage
private readonly Collider[] hits = new Collider[32]; // max results we care about
void CheckNearby()
{
int count = Physics.OverlapSphereNonAlloc(transform.position, 5f, hits);
for (int i = 0; i < count; i++)
{
// use hits[i]
}
}
To find garbage, select the GC Alloc column in the Profiler's CPU Hierarchy view and sort by it.
3. Pool objects you spawn a lot
Instantiate and Destroy are expensive when done dozens of times per second (bullets, particles, damage numbers). Reuse objects with an object pool instead. Unity includes UnityEngine.Pool.ObjectPool<T>; the Object Pooling guide covers it in depth.
4. Don't run code every frame if you don't have to
// WASTEFUL: checks a slow thing 60+ times per second
void Update()
{
FindClosestEnemy();
}
// BETTER: check 5 times per second, still feels instant for AI
private float nextCheck;
void Update()
{
if (Time.time < nextCheck) return;
nextCheck = Time.time + 0.2f;
FindClosestEnemy();
}
- React to events instead of checking every frame ("health changed" instead of reading health in
Update). See UnityEvents. - Use a coroutine with
WaitForSecondsfor slow-repeating work. - Delete empty
Update()methods. Unity still calls them. - Disable scripts (
enabled = false) on far-away or off-screen objects.OnBecameVisible/OnBecameInvisiblecan help.
5. Cheaper math and lookups
// Distance check without a square root
if ((a - b).sqrMagnitude < range * range) { } // instead of Vector3.Distance(a, b) < range
// Animator parameters: hash once
private static readonly int SpeedId = Animator.StringToHash("Speed");
animator.SetFloat(SpeedId, speed); // instead of SetFloat("Speed", speed)
// Shader/material properties: same idea
private static readonly int ColorId = Shader.PropertyToID("_BaseColor");
material.SetColor(ColorId, Color.red);
// Set position and rotation together
transform.SetPositionAndRotation(pos, rot); // instead of setting each separately
These are small savings each, but they add up in code that runs on hundreds of objects every frame.
6. material vs. sharedMaterial
Reading renderer.material makes a copy of the material for that object the first time (so you can change its color without affecting others). Copies can't be batched together when drawing, and you must Destroy them yourself when you're done or they leak memory.
- Changing one object's look?
materialis fine, but for many objects, prefer aMaterialPropertyBlock. - Changing all objects using that material? Use
sharedMaterial. (In the Editor this changes the asset on disk.)
7. Physics tips
- Use primitive colliders (box, sphere, capsule) over Mesh Colliders whenever possible.
- Use the Layer Collision Matrix to turn off pairs that never need to collide (e.g. "Pickups" vs "Pickups").
- Pass a layer mask and max distance to raycasts so they check fewer colliders.
- Don't move static colliders (no Rigidbody) every frame. If it moves, give it a kinematic Rigidbody.
- Only raise the physics rate (
Time.fixedDeltaTime) if you really need to; each step costs CPU.
8. UI tips
- When anything on a Canvas changes, Unity rebuilds that Canvas's mesh. Put frequently changing UI (timers, health bars) on a separate Canvas from static UI.
- Untick Raycast Target on images and text that don't need to be clicked.
- Hide UI by disabling the Canvas component or setting a CanvasGroup's alpha to 0, rather than repeatedly calling
SetActiveon many elements.
9. Logging
Debug.Log is slow and still runs in builds. Remove logs from Update when you're done debugging, or wrap them:
[System.Diagnostics.Conditional("UNITY_EDITOR")]
static void EditorLog(string msg) => Debug.Log(msg); // calls are removed from builds entirely
Quick checklist
- Profile. Find the actual biggest cost.
- No
Find/GetComponentinUpdate. - No allocations per frame (check the GC Alloc column).
- Pool anything spawned frequently.
- Run slow checks on a timer or on events, not every frame.
- Profile again to confirm it helped.
See also
- Optimization Best Practices (process, profiling, engine settings)
- GPU Instancing & Batching
- Object Pooling
- Better Ways to Do Things
- Debug
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Unity documentation.