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

Clean Code and Game Architecture

Updated: May 16, 2026
30 min read

# CHAPTER 16

Clean Code and Game Architecture

1. Introduction

When you first start programming, your only goal is to make the game work. You write a 1,000-line script called Player.cs that handles movement, health, shooting, playing sounds, and updating the UI. This is called "Spaghetti Code." It works for a 2-day Game Jam, but if you try to build a commercial indie game this way, the code will become so tangled that adding a single new feature will break five others. In this chapter, we transition from being a programmer to an Architect. We will learn how to write Clean Code using SOLID principles, decouple our scripts using the Observer Pattern (Events), and structure our project using dedicated Game Managers.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand and apply the Single Responsibility Principle (SRP).
  • Transition from deep Inheritance to Component-Based Architecture.
  • Decouple scripts using C# Action delegates (The Observer Pattern).
  • Implement the Singleton pattern correctly for Game Managers.
  • Organize a scalable, professional game codebase.

3. The Single Responsibility Principle (SRP)

The 'S' in SOLID stands for Single Responsibility. A class should have one, and only one, reason to change.
  • *Bad:* Player.cs handles movement, health, and inventory.
  • *Good:* PlayerMovement.cs handles walking. HealthComponent.cs handles taking damage. InventoryManager.cs handles items.
If there is a bug with jumping, you know exactly which small, isolated script to open.

4. Component-Based Architecture

Instead of writing an Enemy class, and making an Orc inherit from it, modern engines (like Unity) use Composition. You create a blank "GameObject" and attach small, reusable Lego blocks (Components) to it.
csharp
123456789101112131415
// REUSABLE HEALTH COMPONENT
// This can be attached to the Player, an Orc, or even a Wooden Crate!
class HealthComponent
{
    public int maxHealth = 100;
    private int currentHealth;

    public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if (currentHealth <= 0) Die();
    }

    void Die() { Destroy(gameObject); }
}

5. The Observer Pattern (C# Events)

If the player takes damage, the UI needs to update the health bar.
  • *Bad (Tightly Coupled):* The Player script calls UIManager.Instance.UpdateHealthBar(). The Player now relies on the UI existing.
  • *Good (Decoupled):* The Player script simply shouts, "I took damage!" to the void. The UI script is listening and updates itself. The Player doesn't even know the UI exists.
This is achieved using C# Action delegates.
csharp
12345678910111213141516
using System;

class PlayerHealth
{
    // Define an event that broadcasts an integer
    public static event Action<int> OnHealthChanged;

    int health = 100;

    void TakeDamage(int dmg)
    {
        health -= dmg;
        // Broadcast the event! Any script listening will run its code.
        OnHealthChanged?.Invoke(health); 
    }
}

6. Managing the Game (Singletons)

A game needs global systems. A GameManager tracks the current level and the score. An AudioManager plays music. We use the Singleton Pattern to ensure only ONE instance of this manager exists, and it is easily accessible from any script.
csharp
1234567891011121314151617181920
class GameManager
{
    // The single, globally accessible instance
    public static GameManager Instance { get; private set; }

    public int playerScore = 0;

    void Awake()
    {
        // Enforce the Singleton rule
        if (Instance != null && Instance != this)
        {
            Destroy(this); // Delete duplicates
        }
        else
        {
            Instance = this; 
        }
    }
}

7. Visual Learning: Monolithic vs. Component Architecture

txt
123456789101112
[ MONOLITHIC (Bad) ]
      Player.cs
   - Move(), Jump(), Shoot()
   - TakeDamage(), Heal()
   - PlaySound(), UpdateUI()
   (1000 lines, impossible to read)

[ COMPONENT-BASED (Good) ]
    [ Player GameObject ]
      |- PlayerMotor.cs (Only moves)
      |- Health.cs (Only takes damage)
      |- WeaponController.cs (Only shoots)

8. Best Practices

  • Avoid "Manager" Bloat: If your GameManager is handling audio, spawning enemies, saving data, and drawing the UI, it violates the Single Responsibility Principle. Break it down into an AudioManager, SpawnManager, SaveManager, and UIManager.

9. Common Mistakes

  • Overusing Singletons: Beginners learn the Singleton pattern and suddenly make *everything* a Singleton (e.g., Player.Instance). This is an "anti-pattern." It creates hidden dependencies and makes the code very hard to test. Only use Singletons for true global systems (Audio, Scene Loading, Game State).

10. Mini Project: Implement the Observer Pattern

Objective: Decouple a coin collection system from the UI using Events.
csharp
123456789101112131415161718192021222324252627282930313233343536373839404142
using System;

// --- SCRIPT 1: The Coin ---
class Coin
{
    // The Event Definition
    public static event Action OnCoinCollected;

    void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Player")
        {
            // Shout to the void!
            OnCoinCollected?.Invoke(); 
            Destroy(gameObject);
        }
    }
}

// --- SCRIPT 2: The UI ---
class ScoreUI
{
    int score = 0;

    void OnEnable()
    {
        // Subscribe to the event (Listen for the shout)
        Coin.OnCoinCollected += UpdateScore;
    }

    void OnDisable()
    {
        // Always unsubscribe when destroyed to prevent memory leaks!
        Coin.OnCoinCollected -= UpdateScore;
    }

    void UpdateScore()
    {
        score += 10;
        Console.WriteLine("Score UI Updated: " + score);
    }
}

11. Practice Exercises

  1. 1. What does the "S" in SOLID principles stand for, and what does it mean?
  1. 2. Why is it dangerous to have the Player.cs script directly reference the UIManager.cs script to update a health bar?

12. MCQs with Answers

Question 1

You want to create a script that manages background music and sound effects across the entire game. You need to ensure that every other script can access it easily, and that only one copy of it ever exists. Which architectural pattern should you use?

Question 2

In the Observer Pattern example, why is the ? used in OnCoinCollected?.Invoke();?

13. Interview Questions

  • Q: Explain the Single Responsibility Principle. Describe a scenario where a massive "God Class" violates this principle and explain how you would refactor it.
  • Q: Contrast Tightly Coupled code with Decoupled code. How does implementing C# Action delegates (Events) achieve decoupling between a Player's health component and the UI Canvas?
  • Q: What is a Singleton? While useful for Game Managers, why is the Singleton pattern frequently referred to as an "anti-pattern" when overused by junior developers?

14. FAQs

Q: Should I completely avoid Inheritance? A: No! Inheritance is still highly useful for Data and purely logical structures (e.g., an Item base class, with Weapon and Potion inheriting from it). However, for visual objects that live in the game world (Enemies, Players, Props), favor Component-Based Composition.

15. Summary

In Chapter 16, we learned how to engineer scalable software. We broke our massive, unreadable scripts into small, focused Lego blocks using the Single Responsibility Principle. We decoupled our game mechanics from our UI using C# Events, allowing scripts to communicate without knowing about each other. Finally, we learned how to organize our global state using the Singleton pattern. Our code is now professional, maintainable, and ready for a large team.

16. Next Chapter Recommendation

Our architecture is clean, but the game is lagging because we are spawning and destroying 500 bullets a minute. Proceed to Chapter 17: Performance Optimization for Games.

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: ·