How to use factory, object pool, singleton, command, state, and observer patterns in your games
Factory pattern
Here is an example of how you can implement the factory pattern in C#:
// 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;
}
}
}
// 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);
}
}
}
}
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:
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#:
// 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)
{
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:
// 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#:
// 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;
// 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:
// 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#:
/ 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()
{
// 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()
{
// 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:
// 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!👩💻👨💻
Post a Comment
image video quote pre code