Skip to main content
C# for Games – Complete Beginner to Advanced Guide
CHAPTER 17 Beginner

Performance Optimization for Games

Updated: May 16, 2026
35 min read

# CHAPTER 17

Performance Optimization for Games

1. Introduction

A game with beautiful art and clean architecture is worthless if it runs at 15 Frames Per Second (FPS). Performance optimization is the dark art of game development. It requires a deep understanding of how the computer's CPU and RAM actually process your C# code. Every time you spawn a bullet, calculate a square root, or search for an object, you are spending microscopic amounts of time. In this chapter, we will master Performance Optimization. We will learn how to avoid the dreaded Garbage Collector stutter, drastically reduce CPU load using Object Pooling, and write lightning-fast Update() loops.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Identify CPU bottlenecks in your game logic.
  • Understand C# Garbage Collection (GC) and how to avoid triggering it.
  • Implement the Object Pooling design pattern for projectiles.
  • Optimize continuous calculations in the Update loop.
  • Use square magnitude math to bypass expensive distance calculations.

3. The Garbage Collector (The Framerate Killer)

C# is a "managed" language. When you create a new variable, C# asks the RAM for memory. When you no longer need that variable, C#'s Garbage Collector (GC) eventually freezes the game for a few milliseconds, scans the RAM, and deletes the unused data.
  • If you create and destroy objects constantly, the GC runs constantly, causing massive micro-stuttering (FPS drops).
  • The Goal: Generate as little "garbage" as possible during active gameplay.

4. The Solution: Object Pooling

If you are making a shooter, do not use Instantiate() to spawn a bullet, and Destroy() when it hits a wall. This creates massive garbage. Use Object Pooling:
  1. 1. When the level loads, spawn 50 bullets.
  1. 2. Hide them all (SetActive(false)).
  1. 3. When the player shoots, grab a hidden bullet, move it to the gun, and turn it on.
  1. 4. When it hits a wall, *don't destroy it*. Just hide it again!
You are recycling the same 50 objects infinitely. Zero garbage created!

5. Optimizing the Update Loop

Code in Update() runs 60 to 144 times a second.
  • Never use Find() in Update: GameObject.Find("Player") forces the CPU to scan the entire level comparing text strings. It is incredibly slow. Find the player *once* in Start(), save the reference to a variable, and use the variable in Update().
  • Avoid GetComponent() in Update: Same rule. Cache your components in Start().
csharp
12345678910111213141516171819
// BAD - Lags the game!
void Update()
{
    GameObject.Find("Player").GetComponent<Health>().TakeDamage(1);
}

// GOOD - Lightning fast!
Health playerHealth;

void Start()
{
    // Cache it once!
    playerHealth = GameObject.Find("Player").GetComponent<Health>();
}

void Update()
{
    playerHealth.TakeDamage(1);
}

6. Math Optimization (sqrMagnitude)

If you want to check if an enemy is close to the player, you usually use Vector3.Distance().
  • However, Distance requires the CPU to calculate a Square Root, which is a very complex math operation.
  • Instead, use sqrMagnitude (the distance *squared*) and compare it to the distance you want, also squared!
csharp
12345
// Expensive (Requires Square Root calculation)
if (Vector3.Distance(enemy.pos, player.pos) < 5.0f) { ... }

// Cheap & Fast (No Square Root required!)
if ((enemy.pos - player.pos).sqrMagnitude < (5.0f * 5.0f)) { ... }

7. Visual Learning: Object Pooling

txt
12345678
[ BULLET POOL (List of 5 Bullets) ]
1. [HIDDEN]  2. [HIDDEN]  3. [HIDDEN]  4. [HIDDEN]  5. [HIDDEN]

*Player Shoots!* -> Grab Bullet 1, Move to Gun, Make VISIBLE.
1. [ACTIVE]  2. [HIDDEN]  3. [HIDDEN]  4. [HIDDEN]  5. [HIDDEN]

*Bullet Hits Wall!* -> Move to Pool, Make HIDDEN. (NO DESTROY CALLED)
1. [HIDDEN]  2. [HIDDEN]  3. [HIDDEN]  4. [HIDDEN]  5. [HIDDEN]

8. Best Practices

  • Use Timers for Non-Critical Logic: Does an enemy really need to search for the player 60 times a second? No. Use a Coroutine or a Timer variable to run the detection logic only 4 times a second (every 0.25s). The player won't notice the difference, and you just saved 93% of the CPU cost for that script!

9. Common Mistakes

  • String Concatenation in Update: Writing scoreText.text = "Score: " + currentScore; inside Update() generates a brand new string in RAM every single frame. Strings are immutable in C#. This generates massive garbage. Only update UI strings *exactly when the score changes*.

10. Mini Project: Build a Simple Object Pool

Objective: Create a class that dispenses recycled objects.
csharp
1234567891011121314151617181920212223242526272829303132
using System.Collections.Generic;

class BulletPool
{
    public GameObject bulletPrefab;
    private List<GameObject> pool = new List<GameObject>();

    void Start()
    {
        // 1. Pre-warm the pool with 20 hidden bullets
        for (int i = 0; i < 20; i++)
        {
            GameObject obj = Instantiate(bulletPrefab);
            obj.SetActive(false); // Hide it
            pool.Add(obj);
        }
    }

    // 2. Dispense a bullet when requested
    public GameObject GetBullet()
    {
        foreach (GameObject bullet in pool)
        {
            if (!bullet.activeInHierarchy) // If it's hidden, it's available!
            {
                bullet.SetActive(true); // Turn it on
                return bullet;
            }
        }
        return null; // Pool is empty! (In reality, you would expand the pool here)
    }
}

11. Practice Exercises

  1. 1. Why is calling GameObject.Find() inside the Update() loop considered a severe performance flaw?
  1. 2. What is the fundamental goal of the Object Pooling design pattern?

12. MCQs with Answers

Question 1

To prevent the C# Garbage Collector from freezing the game during intense combat, which pair of functions should you avoid using repeatedly?

Question 2

When calculating the distance between two objects, which mathematical property should you use to completely bypass the expensive CPU calculation of a Square Root?

13. Interview Questions

  • Q: Explain the concept of Garbage Collection in C#. How does rapid memory allocation and deallocation lead to framerate micro-stuttering in a game engine?
  • Q: Walk me through the architecture of an Object Pool. Why is toggling an object's active state significantly faster than instantiating and destroying it?
  • Q: A junior developer writes an Enemy AI script that uses GameObject.FindWithTag("Player") inside the Update() method. Explain the CPU cost of this action and how you would refactor it using caching in the Start() method.

14. FAQs

Q: Does empty space in my code slow down the game? A: No! Blank lines, comments, and long variable names have absolutely zero impact on performance. The compiler strips all of that away when converting your C# into binary machine code. Write code for humans to read!

15. Summary

In Chapter 17, we learned the dark arts of high-FPS programming. We discovered that the C# Garbage Collector causes stuttering when we throw away data, leading us to adopt the Object Pooling pattern to recycle our bullets and enemies infinitely. We optimized our Update loops by caching expensive Find() and GetComponent() calls in the Start method. Finally, we utilized sqrMagnitude to perform lightning-fast distance checks. Our game code is now lean, mean, and perfectly optimized for 60+ FPS.

16. Next Chapter Recommendation

You have mastered raw C#. It is time to fully interface with the most popular C# game engine on the planet. Proceed to Chapter 18: Working with Unity and C#.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·