Unity C# Design Patterns

Swapnil More
4 min readJan 24, 2024

--

Welcome to the world of Unity game development! As you embark on your journey to create captivating games, it’s crucial to have a solid foundation in writing clean, organized, and efficient code. In this introduction, we’ll delve into the realm of design patterns — a set of blueprints that make your coding not only more structured but also more enjoyable.

Design patterns are like secret recipes for success in game development. They are tried-and-tested solutions to common problems that game developers face. Think of them as your handy toolkit, helping you build games with less stress and more creativity.

Throughout this exploration, we’ll keep things simple. No complex jargon or confusing terminology. Just easy-to-understand insights into five design patterns: Singleton, Observer, Command, Factory, and State.

Singleton Pattern: One is Enough

The Singleton pattern ensures that a class has only one instance and provides a global way to access it.

Example: Let’s take the GameManager as an example. This ensures there’s only one game manager controlling the game.

public class GameManager : MonoBehaviour
{
private static GameManager _instance;

public static GameManager Instance
{
get
{
// If no instance exists, create one
if (_instance == null)
{
_instance = FindObjectOfType<GameManager>();
if (_instance == null)
{
GameObject singleton = new GameObject("GameManager");
_instance = singleton.AddComponent<GameManager>();
}
}
return _instance;
}
}

// Other GameManager methods and properties...
}

Use Case: Whenever you need a single, globally accessible instance, like a game manager or an audio manager.

Managing Game State

  • Imagine a game manager that controls various aspects of your game, such as scoring, level progression, or player inventory. The Singleton pattern ensures that there’s only one instance of the game manager throughout the entire game. This way, it becomes a centralized hub for handling and updating the game state.

Observer Pattern: Keeping Everyone Informed

The Observer pattern establishes a dependency between objects so that when one changes, others are automatically notified.

Example: Consider a Subject class that maintains a list of observers and notifies them when something changes.

public class Subject : MonoBehaviour
{
private List<IObserver> observers = new List<IObserver>();

public void RegisterObserver(IObserver observer)
{
observers.Add(observer);
}

public void NotifyObservers()
{
foreach (var observer in observers)
{
observer.UpdateData();
}
}
}

public interface IObserver
{
void UpdateData();
}

Use Case: Useful for updating UI elements when game state changes or implementing event systems.

UI Updates

  • Consider a scenario where you have different elements on your game UI that need to update whenever the player’s health or score changes. The Observer pattern allows these UI elements to subscribe as observers to the player’s health or score. When these values change, the UI elements automatically get notified and update accordingly.

Command Pattern: Tell Me What to Do

The Command pattern encapsulates a request as an object, allowing for parameterization, queuing, and logging of requests.

Example: Create a JumpCommand that represents the command to make a player jump.

public interface ICommand
{
void Execute();
}

public class JumpCommand : ICommand
{
private Player player;

public JumpCommand(Player player)
{
this.player = player;
}

public void Execute()
{
player.Jump();
}
}

Use Case: Ideal for input handling or implementing undo/redo systems.

Input Handling

  • In a game, you might have various input sources, such as keyboard, mouse, or controller. The Command pattern helps in encapsulating these input commands (like jumping, shooting, or moving) into separate command classes. This not only makes it easy to manage different input sources but also facilitates the implementation of undo/redo functionality.

Factory Pattern: Building Objects Dynamically

The Factory pattern defines an interface for creating objects but leaves the choice of their type to the subclasses.

Example: Create a GunFactory that produces different types of weapons.

public interface IWeaponFactory
{
Weapon CreateWeapon();
}

public class GunFactory : IWeaponFactory
{
public Weapon CreateWeapon()
{
return new Gun();
}
}

Use Case: When you need to create different types of game objects dynamically.

Spawning Game Objects

  • Let’s say your game involves spawning different types of enemies during gameplay. The Factory pattern can be applied by creating an EnemyFactory with methods to produce specific types of enemies. This abstraction makes it easy to extend your game by adding new enemy types without modifying existing code.

State Pattern: Adapting to Different Situations

The State pattern allows an object to alter its behavior when its internal state changes.

Example: Implement a Player class that can change its behavior based on different states.

public class Player
{
private PlayerState currentState;

public void SetState(PlayerState state)
{
currentState = state;
}

public void Move()
{
currentState.Move();
}
}

public abstract class PlayerState
{
public abstract void Move();
}

public class WalkingState : PlayerState
{
public override void Move()
{
// Implement walking behavior
}
}

Use Case: Useful when a player’s behavior needs to adapt to the current game state.

Player Behavior

  • Imagine a game where a player can be in different states, such as walking, running, or jumping. The State pattern helps organize these behaviors into separate state classes. This way, changing the player’s behavior becomes a matter of transitioning between states, making the code more modular and easier to maintain.

Conclusion

Understanding and applying design patterns in Unity can significantly improve your game development skills. The Singleton, Observer, Command, Factory, and State patterns presented here are just a starting point. Experimenting with these patterns in your Unity projects will give you a solid foundation for writing maintainable, scalable, and efficient code. Happy coding!

--

--

Swapnil More

With 7 years of experience in the mechanical engineering arena, I bring a wealth of knowledge and a passion for innovation to the table.