Design Patterns and Techniques - Cheat Sheet

Object Pooling

Object pooling is a design pattern used to improve performance by reusing objects instead of creating and destroying them repeatedly. This is particularly useful for frequently spawned objects like bullets or particles.

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }

    public List<Pool> pools;
    public Dictionary<string, Queue<GameObject>> poolDictionary;

    void Start()
    {
        poolDictionary = new Dictionary<string, Queue<GameObject>>();

        foreach (Pool pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();

            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
            }

            poolDictionary.Add(pool.tag, objectPool);
        }
    }

    public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            Debug.LogWarning("Pool with tag " + tag + " doesn't exist.");
            return null;
        }

        GameObject objectToSpawn = poolDictionary[tag].Dequeue();

        objectToSpawn.SetActive(true);
        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

        poolDictionary[tag].Enqueue(objectToSpawn);

        return objectToSpawn;
    }
}

// Usage
// ObjectPool objectPool = GetComponent<ObjectPool>();
// GameObject bullet = objectPool.SpawnFromPool("Bullet", transform.position, Quaternion.identity);
Singleton

The Singleton pattern ensures a class has only one instance and provides a global point of access to it. This is useful for manager classes that need to persist across scenes and maintain global state.

using UnityEngine;

public class GameManager : MonoBehaviour
{
    private static GameManager _instance;
    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<GameManager>();
                if (_instance == null)
                {
                    GameObject go = new GameObject("GameManager");
                    _instance = go.AddComponent<GameManager>();
                }
            }
            return _instance;
        }
    }

    public int Score { get; private set; }

    void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(this.gameObject);
        }
        else
        {
            _instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
    }

    public void AddScore(int points)
    {
        Score += points;
        Debug.Log("Score: " + Score);
    }
}

// Usage
// GameManager.Instance.AddScore(10);
Observer Pattern

The Observer pattern allows objects to be notified automatically of any state changes in other objects. This is useful for decoupling systems and creating event-driven architectures.

using System;
using UnityEngine;

public class GameEvents : MonoBehaviour
{
    public static event Action<int> OnScoreChanged;
    public static event Action OnGameOver;

    public static void ScoreChanged(int newScore)
    {
        OnScoreChanged?.Invoke(newScore);
    }

    public static void GameOver()
    {
        OnGameOver?.Invoke();
    }
}

public class Player : MonoBehaviour
{
    private int score = 0;

    private void OnEnable()
    {
        GameEvents.OnScoreChanged += UpdateUI;
    }

    private void OnDisable()
    {
        GameEvents.OnScoreChanged -= UpdateUI;
    }

    public void AddScore(int points)
    {
        score += points;
        GameEvents.ScoreChanged(score);
    }

    private void UpdateUI(int newScore)
    {
        Debug.Log("UI updated with new score: " + newScore);
    }
}

// Usage
// player.AddScore(10);
// GameEvents.GameOver();
State Machine

A State Machine manages the behavior of an object based on its internal state. This pattern is useful for managing complex behaviors in games, such as character animations or game flow.

using UnityEngine;

public class PlayerStateMachine : MonoBehaviour
{
    public enum PlayerState
    {
        Idle,
        Walking,
        Running,
        Jumping
    }

    private PlayerState currentState;

    private void Start()
    {
        SetState(PlayerState.Idle);
    }

    private void Update()
    {
        switch (currentState)
        {
            case PlayerState.Idle:
                // Handle idle behavior
                if (Input.GetKeyDown(KeyCode.W))
                    SetState(PlayerState.Walking);
                break;
            case PlayerState.Walking:
                // Handle walking behavior
                if (Input.GetKeyDown(KeyCode.LeftShift))
                    SetState(PlayerState.Running);
                else if (!Input.GetKey(KeyCode.W))
                    SetState(PlayerState.Idle);
                break;
            case PlayerState.Running:
                // Handle running behavior
                if (Input.GetKeyUp(KeyCode.LeftShift))
                    SetState(PlayerState.Walking);
                break;
            case PlayerState.Jumping:
                // Handle jumping behavior
                // Transition back to Idle when landing
                break;
        }

        if (Input.GetKeyDown(KeyCode.Space) && currentState != PlayerState.Jumping)
            SetState(PlayerState.Jumping);
    }

    private void SetState(PlayerState newState)
    {
        currentState = newState;
        Debug.Log("Player state changed to: " + newState);
        // Trigger animations or other state-specific actions here
    }
}
Command Pattern

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. This is particularly useful for implementing complex input systems or AI behavior.

using System.Collections.Generic;
using UnityEngine;

public interface ICommand
{
    void Execute();
    void Undo();
}

public class MoveCommand : ICommand
{
    private Transform objectToMove;
    private Vector3 direction;

    public MoveCommand(Transform objectToMove, Vector3 direction)
    {
        this.objectToMove = objectToMove;
        this.direction = direction;
    }

    public void Execute()
    {
        objectToMove.Translate(direction);
    }

    public void Undo()
    {
        objectToMove.Translate(-direction);
    }
}

public class InputHandler : MonoBehaviour
{
    public Transform playerTransform;
    private List<ICommand> commandHistory = new List<ICommand>();

    void Update()
    {
        ICommand command = null;

        if (Input.GetKeyDown(KeyCode.W))
            command = new MoveCommand(playerTransform, Vector3.forward);
        else if (Input.GetKeyDown(KeyCode.S))
            command = new MoveCommand(playerTransform, Vector3.back);
        else if (Input.GetKeyDown(KeyCode.A))
            command = new MoveCommand(playerTransform, Vector3.left);
        else if (Input.GetKeyDown(KeyCode.D))
            command = new MoveCommand(playerTransform, Vector3.right);

        if (command != null)
        {
            command.Execute();
            commandHistory.Add(command);
        }

        if (Input.GetKeyDown(KeyCode.Z) && commandHistory.Count > 0)
        {
            ICommand lastCommand = commandHistory[commandHistory.Count - 1];
            lastCommand.Undo();
            commandHistory.RemoveAt(commandHistory.Count - 1);
        }
    }
}
Component Pattern

The Component pattern is a core principle in Unity, where objects are composed of multiple, reusable components. This promotes modularity and flexibility in game design.

using UnityEngine;

// Health component
public class Health : MonoBehaviour
{
    public int maxHealth = 100;
    private int currentHealth;

    private void Start()
    {
        currentHealth = maxHealth;
    }

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

    private void Die()
    {
        Debug.Log(gameObject.name + " has died.");
        Destroy(gameObject);
    }
}

// Movement component
public class Movement : MonoBehaviour
{
    public float speed = 5f;

    private void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(horizontal, 0f, vertical).normalized;
        transform.Translate(movement * speed * Time.deltaTime);
    }
}

// Player class combining components
public class Player : MonoBehaviour
{
    private Health health;
    private Movement movement;

    private void Awake()
    {
        health = GetComponent<Health>();
        movement = GetComponent<Movement>();
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            health.TakeDamage(10);
        }
    }
}

// Usage
// Attach Health, Movement, and Player scripts to a GameObject