How to use factory, object pool, singleton, command, state, and observer patterns in your games


In this lessen, I will explain what are some common game programming patterns and how you can use them in your Unity games with C#. Game programming patterns are reusable solutions to common problems that arise when developing games. They can help you write cleaner, more efficient, and more maintainable code. Let's take a look at six of these patterns: factory, object pool, singleton, command, state, and observer.

Factory pattern


The factory pattern is a way of creating objects without exposing the logic of how they are created. This can be useful when you have different types of objects that share a common interface or base class. For example, you might have different types of enemies in your game that all inherit from an abstract Enemy class. Instead of creating each enemy type directly with the new keyword in your code, you can use a factory class that has a method for creating enemies based on some parameters. This way, you can easily change the logic of how enemies are created without affecting the rest of your code.

Here is an example of how you can implement the factory pattern in C#:


csharp
// The abstract base class for all enemies public abstract class Enemy { public string Name { get; set; } public int Health { get; set; } public int Damage { get; set; } // The method for attacking the player public abstract void Attack(); } // A concrete subclass for zombies public class Zombie : Enemy { public Zombie() { Name = "Zombie"; Health = 100; Damage = 10; } // The zombie attacks by biting public override void Attack() { Debug.Log(Name + " bites the player for " + Damage + " damage."); } } // A concrete subclass for skeletons public class Skeleton : Enemy { public Skeleton() { Name = "Skeleton"; Health = 50; Damage = 20; } // The skeleton attacks by swinging its sword public override void Attack() { Debug.Log(Name + " swings its sword at the player for " + Damage + " damage."); } } // The factory class for creating enemies public static class EnemyFactory { // The method for creating enemies based on a type parameter public static Enemy CreateEnemy(string type) { switch (type) { case "Zombie": return new Zombie(); case "Skeleton": return new Skeleton(); default: return null; } }
csharp
} // The class for testing the factory pattern public class FactoryTest : MonoBehaviour { // The list of enemy types to create private string[] enemyTypes = { "Zombie", "Skeleton", "Zombie", "Skeleton" }; // The method for creating and testing enemies private void Start() { // Loop through the enemy types and create enemies using the factory foreach (string type in enemyTypes) { Enemy enemy = EnemyFactory.CreateEnemy(type); // Check if the enemy is not null if (enemy != null) { // Print the enemy's name and health Debug.Log("Created a " + enemy.Name + " with " + enemy.Health + " health."); // Make the enemy attack enemy.Attack(); } else { // Print an error message Debug.LogError("Invalid enemy type: " + type); } } }
markdown
}


This will output something like this:

apache
Created a Zombie with 100 health. Zombie bites the player for 10 damage. Created a Skeleton with 50 health. Skeleton swings its sword at the player for 20 damage. Created a Zombie with 100 health. Zombie bites the player for 10 damage. Created a Skeleton with 50 health. Skeleton swings its sword at the player for 20 damage.

Explanation

The factory pattern is a way of creating objects without specifying their concrete classes. This can be useful when you have different types of objects that share a common interface or base class, but have different implementations or behaviors. For example, you might have different types of enemies in your game that inherit from an abstract Enemy class, but have different names, health, damage, and attack methods. Instead of creating these enemies directly in your code, you can use a factory pattern to create a static EnemyFactory class that has a method for creating enemies based on a type parameter. This way, you can easily add new types of enemies without modifying your existing code.

Now you can use the factory method to create enemies like this:


csharp
Enemy enemy1 = EnemyFactory.CreateEnemy("Zombie"); Enemy enemy2 = EnemyFactory.CreateEnemy("Skeleton"); enemy1.Attack(); // Zombie bites the player for 10 damage. enemy2.Attack(); // Skeleton swings its sword at the player for 20 damage.

Object pool pattern

The object pool pattern is a way of reusing objects instead of creating and destroying them repeatedly. This can be useful when you have objects that are expensive to create or destroy or when you need to spawn many objects at once. For example, you might have bullets in your game that are fired from a gun. Instead of creating a new bullet object every time you fire and destroying it when it hits something or goes out of bounds, you can use an object pool that stores a fixed number of bullet objects and recycles them as needed. This way, you can avoid performance issues caused by frequent memory allocation and garbage collection.

Here is an example of how you can implement the object pool pattern in C#:


csharp
// The bullet class that implements IPoolable interface public class Bullet : MonoBehaviour , IPoolable { // The speed of the bullet public float speed = 10f; // The reference to the object pool private ObjectPool<Bullet> pool; // The method for setting the object pool public void SetPool(ObjectPool<Bullet> pool) { this.pool = pool; } // The method for updating the bullet movement private void Update() { transform.position += transform.forward * speed * Time.deltaTime; // If the bullet goes out of bounds , return it to the pool if (transform.position.magnitude > 50f)
csharp
{ ReturnToPool(); } } // The method for returning the bullet to the pool private void ReturnToPool() { gameObject.SetActive(false); pool.ReturnObject(this); } // The method for reactivating the bullet when it is taken from the pool public void OnObjectSpawn() { gameObject.SetActive(true); } } // The interface for poolable objects public interface IPoolable { // The method for setting the object pool void SetPool(ObjectPool<T> pool); // The method for reactivating the object when it is taken from the pool void OnObjectSpawn(); } // The generic class for object pools public class ObjectPool<T> where T : MonoBehaviour , IPoolable { // The prefab of the object to be pooled private T prefab; // The queue of available objects private Queue<T> pool; // The constructor that takes a prefab and a size parameter public ObjectPool(T prefab , int size) { this.prefab = prefab; pool = new Queue<T>(); // Fill the pool with inactive objects for (int i = 0; i < size; i++) { T obj = GameObject.Instantiate(prefab); obj.gameObject.SetActive(false); obj.SetPool(this); pool.Enqueue(obj); } } // The method for taking an object from the pool public T GetObject() { if (pool.Count > 0) { T obj = pool.Dequeue(); obj.OnObjectSpawn(); return obj; } else { Debug.Log("No objects available in the pool."); return null; }


Now you can use the object pool to spawn bullets like this:


csharp
// Create an object pool of 10 bullets ObjectPool<Bullet> bulletPool = new ObjectPool<Bullet>(bulletPrefab , 10); // Spawn a bullet from the pool at a given position and rotation Bullet bullet = bulletPool.GetObject(); bullet.transform.position = spawnPosition; bullet.transform.rotation = spawnRotation;

Singleton pattern

The singleton pattern is a way of ensuring that there is only one instance of a class and providing a global access point to it. This can be useful when you have a class that represents a unique system or resource in your game. For example, you might have a game manager class that controls the game state and logic. Instead of creating multiple instances of this class or passing it around as a parameter, you can use a singleton pattern to make sure there is only one game manager instance and access it from anywhere in your code.

Here is an example of how you can implement the singleton pattern in C#:

csharp
// The game manager class that implements MonoBehaviour and uses singleton pattern public class GameManager : MonoBehaviour { // The static instance of the game manager private static GameManager instance; // The property for accessing the instance public static GameManager Instance { get { return instance; } } // The variable for storing some game data public int score;
csharp
// The method for initializing the instance private void Awake() { // Check if there is already an instance of game manager if (instance != null && instance != this) { // Destroy the duplicate instance Destroy(gameObject); } else { // Set this as the instance and don't destroy it when loading new scenes instance = this; DontDestroyOnLoad(gameObject); } } // The method for updating the game logic private void Update() { // Do some game logic here } // The method for increasing the score by a given amount public void IncreaseScore(int amount) { score += amount; Debug.Log("Score: " + score); } }


Now you can access the game manager instance from anywhere in your code like this:

csharp
// Get a reference to the game manager instance GameManager gameManager = GameManager.Instance; // Call a method on the game manager instance gameManager.IncreaseScore(10);


Command pattern

The command pattern is a way of encapsulating actions as objects and executing them later. This can be useful when you want to implement features such as undo/redo, input handling, or AI behaviors. For example, you might have different types of commands in your game that represent different actions that can be performed by the player or an enemy. Instead of hard-coding these actions in your code, you can use a command pattern to create command objects that store the information needed to execute these actions and invoke them when needed. This way, you can easily change or extend these actions without modifying your existing code.

Here is an example of how you can implement the command pattern in C#:

csharp
/ The abstract foundation class for all commands. public abstract class Command { // The method for executing the command public abstract void Execute(); } // A concrete subclass for moving commands public class MoveCommand : Command { // The reference to the transform component of the object to be moved private Transform transform; // The direction and distance of the movement private Vector3 direction; private float distance; // The constructor that takes a transform , a direction , and a distance parameter public MoveCommand(Transform transform , Vector3 direction , float distance) { this.transform = transform; this.direction = direction; this.distance = distance; } // The method for executing the move command public override void Execute()
csharp
{ // Move the transform by the direction and distance transform.position += direction * distance; } } // A concrete subclass for jumping commands public class JumpCommand : Command { // The reference to the rigidbody component of the object to be jumped private Rigidbody rigidbody; // The force of the jump private float force; // The constructor that takes a rigidbody and a force parameter public JumpCommand(Rigidbody rigidbody , float force) { this.rigidbody = rigidbody; this.force = force; } // The method for executing the jump command public override void Execute() { // Add an upward force to the rigidbody rigidbody.AddForce(Vector3.up * force , ForceMode.Impulse); } } // The class for storing and executing commands public class CommandInvoker { // The list of commands private List<Command> commands; // The constructor that initializes the list public CommandInvoker() { commands = new List<Command>(); } // The method for adding a command to the list public void AddCommand(Command command) { commands.Add(command); } // The method for executing all the commands in the list public void ExecuteCommands()
csharp
{ // Loop through the list and execute each command foreach (Command command in commands) { command.Execute(); } // Clear the list after execution commands.Clear(); } }

Now you can use the command invoker to create and execute commands like this:

csharp
// Create a command invoker instance CommandInvoker invoker = new CommandInvoker(); // Get references to some game objects with transform and rigidbody components Transform cube = GameObject.Find("Cube").transform; Rigidbody sphere = GameObject.Find("Sphere").GetComponent<Rigidbody>(); // Create some commands with different parameters Command moveLeft = new MoveCommand(cube , Vector3.left , 1f); Command moveRight = new MoveCommand(cube , Vector3.right , 1f); Command jumpUp = new JumpCommand(sphere , 5f); // Add some commands to the invoker invoker.AddCommand(moveLeft); invoker.AddCommand(jumpUp); invoker.AddCommand(moveRight); // Execute all the commands in the invoker invoker.ExecuteCommands();

This will make the cube move left, then right, and make the sphere jump up.

Conclusion

In this lessen, we have covered three common design patterns that can help you write better code for your games: object pool, singleton, and command. These patterns can help you optimize performance, manage resources, implement features, and extend functionality. Of course, these are not the only design patterns that exist. There are many more that you can learn and apply to your projects. We hope that this lessen has given you some inspiration and guidance on how to use design patterns in your game development. Happy coding!👩‍💻👨‍💻