Optimization means making your game use less CPU, GPU and memory. A well-optimized game runs at 60 frames per second (FPS) even on low-end devices.
- Reduces lag and stuttering
- Saves battery on mobile devices
- Allows more objects on screen
Technique 1: Object Pooling
Creating and destroying objects repeatedly causes lag spikes. Object pooling reuses objects instead.
public class BulletPool : MonoBehaviour
{
public GameObject bulletPrefab;
public int poolSize = 20;
private Queue<GameObject> pool = new Queue<GameObject>();
void Start()
{
for (int i = 0; i < poolSize; i++)
{
GameObject bullet = Instantiate(bulletPrefab);
bullet.SetActive(false);
pool.Enqueue(bullet);
}
}
public GameObject GetBullet()
{
GameObject bullet = pool.Dequeue();
bullet.SetActive(true);
pool.Enqueue(bullet);
return bullet;
}
}
Instead of Instantiate() and Destroy(), you activate and deactivate objects. This eliminates garbage collection spikes.
Technique 2: Reduce Draw Calls
Each object with a unique material creates a draw call. Too many draw calls slow down the GPU.
Problems that increase draw calls
- 10 trees with 10 different materials
- UI elements with individual images
- Characters with separate material per body part
Solutions
- Use texture atlases (one image for multiple objects)
- Share materials between objects
- Combine meshes for static objects
Technique 3: Limit Update() Usage
Every script with Update() runs every frame. Too many Update() methods hurt performance.
Bad:
void Update()
{
// Heavy calculation every frame
FindObjectOfType<Enemy>();
GetComponent<Renderer>().material.color = Color.red;
}
Better:
void Start()
{
enemy = FindObjectOfType<Enemy>(); // Find once
myRenderer = GetComponent<Renderer>();
}
void Update()
{
// Use cached references
myRenderer.material.color = Color.red;
}
Best: Use Invoke or Coroutines for non-critical tasks
void Start()
{
InvokeRepeating("DoHeavyTask", 0f, 0.5f); // Run every 0.5 seconds, not every frame
}
Technique 4: Pool Particles
Particle effects create many GameObjects. Pool them instead.
public class ParticlePool : MonoBehaviour
{
public ParticleSystem particlePrefab;
private Queue<ParticleSystem> pool = new Queue<ParticleSystem>();
public void PlayAt(Vector3 position)
{
ParticleSystem ps;
if (pool.Count > 0)
ps = pool.Dequeue();
else
ps = Instantiate(particlePrefab);
ps.transform.position = position;
ps.Play();
StartCoroutine(ReturnToPool(ps, ps.main.duration));
}
IEnumerator ReturnToPool(ParticleSystem ps, float delay)
{
yield return new WaitForSeconds(delay);
ps.Stop();
pool.Enqueue(ps);
}
}
Technique 5: Optimize UI
UI can be a major performance killer.
Tips for UI optimization:
- Disable Raycast Target on non-interactive UI elements.
- Use Canvas Group to hide entire menus.
- Separate frequently updating UI (score) into its own Canvas.
- Use TextMeshPro instead of legacy Text.
Example:
// Bad: Entire UI updates every frame
void Update()
{
scoreText.text = "Score: " + score; // Forces UI rebuild
}
// Good: Update only when needed
public void UpdateScore()
{
scoreText.text = "Score: " + score; // Called only when score changes
}
Technique 6: Level of Detail (LOD)
Distant objects don't need high detail. LOD uses simpler models for far away objects.
Setup:
- Select a 3D model.
- Add Component - LOD Group.
- Assign high-detail model for LOD 0 (close).
- Assign medium-detail model for LOD 1 (medium distance).
- Assign low-detail model for LOD 2 (far).
Technique 7: Occlusion Culling
Unity renders everything in camera view, even behind walls. Occlusion culling hides objects blocked by others.
Setup:
- Select objects - Check "Occluder Static" and "Occludee Static" in Inspector.
- Window - Rendering - Occlusion Culling.
- Click "Bake".
Without occlusion culling, all objects are rendered. With occlusion culling, objects behind walls are not rendered.