Game Programming Patterns

Software Patterns in Games
DR. ROBERT ZUBEK, SOMASIM LLC
EECS-395/495: GAME DEVELOPMENT STUDIO
WINTER QUARTER 2016
NORTHWESTERN UNIVERSITY
Today we’ll talk about software patterns
Every software engineer should know about patterns in general
We’ll talk about games specifically:
- Which patterns tend to get used
- How they get used
We’ll use a fictional Tower Defense game to illustrate
Quick overview
UI
Data Layout
Communication
Behavior
Modularity
Part 1: UI
Two important patterns:
- Game state stack for managing large scale transitions
- MVC (model-view-controller) for in-game display
Let’s start from the beginning
Splash Screen!
(loading, please wait…)
Settings
Splash
Main
Screen!
Menu
Play Campaign
Load Game
Pick Scenario
Splash Screen
(loading, please wait…)
Settings
Splash
Main
Screen!
Menu
Play Campaign
Load Game
Pick Scenario
PLAY THE GAME!
Splash Screen
FINALLY!
Game Screens, option 1: FSM
Splash Screen!
Splash Screen
Game Screens, option 1: FSM
Splash Screen!
Load scenario definitions
Load graphical assets
Initialize UI
Pick Scenario
Deinitialize UI
Unload graphical assets
Splash
Screen
Unload
scenario definitions
Hand control over to next state
Game Screens, option 1: FSM
Splash Screen!
Pick Scenario
Deinitialize UI
Unload everything
(these init / deinit operations
are symmetrical)
Re-initialize UI again
Reload graphical assets
Splash
Screen
Reload
scenario definitions
Game Screens, option 1: FSM
Splash Screen!
?
?
Player hits “back to
main
Splash
menu”.
ScreenWhich
way do we go?
Game Screens, option 2: Stack
Play the
Game!
Settings
Main
Menu
t
Main
Menu
Main
Menu
Pick
Scenario
Pick
Scenario
Pick
Scenario
Play
Campaign
Play
Campaign
Play
Campaign
Play
Campaign
Play
Campaign
Main
Menu
Main
Menu
Main
Menu
Main
Menu
Main
Menu
Main
Menu
Example:
Each stack node will want to know:
◦
◦
◦
◦
◦
public interface StateStackElement {
When I’ve been pushed on stack
When I’m activated (topmost after pushing)
When I’m deactivated (no longer topmost)
When I’m reactivated (topmost again)
When I’ve been popped off stack
◦ OnPushed (StateStack stack)
◦ OnActivated (bool firstTime)
◦ OnDeactivated ()
◦ OnPopped ()
}
Example: Settings menu
Initialize UI assets
public class SettingsMenu : StateStackElement {
◦ OnPushed (StateStack stack)
◦ OnActivated (bool firstTime)
◦ OnDeactivated ()
Show UI
Hide UI
◦ OnPopped ()
Free UI assets
}
Benefits: this encapsulates large UI pieces and interactions,
inside objects that can be added and removed as needed.
Bob
In-game UI
100%
Ask
Joe
Inside the engine
100%
Attack
NPC
Name
Joe
Health
100
NPC
Name
Ann
Health
100
NPC
Name
Bob
Health
100
Challenge
Game systems shouldn’t know anything about UI
And yet, they communicate all the time:
 When player loses health, gains money, finds items, etc.
UI needs to update
 When the player clicks the “attack” button (interacts with the UI)
combat system should perform the attack
which will probably change player’s health
which needs to be reflected in UI
MVC (model/view/controller)
Model
Enemy
Health
100
Joe
100%
View
Controller
Combat
Manager
Attack
MVC
Enemy
Health
Listens for
changes
100
Joe
Game Systems
UI
100%
Attack
Combat
Manager
Translates user actions
into game actions
MVC
Enemy
Health
50100
updates propagate via
event bus or observer callback
Joe
50%
100%
combat resolution
changes health
Attack
Combat
Manager
mouse click calls
CombatManager.Attack()
Part 1: UI
Summary
- MVC (model-view-controller) helps separate out UI from the data it’s displaying
◦ UI should know about game data, but game data should not know about UI!
- Game state stack encapsulates large scale UI transitions
Part 2: Data layout patterns
Flyweight
◦ Share data across instances instead of duplicating
◦ Good for shared immutable definitions
Memento
◦ Put important internal state in a separate object
◦ Simplifies implementation of save files
Data sharing
Example: crowd of enemies
◦ They have different stats
◦ But they all look the same
“Flyweight” pattern
◦ Extract common data and share it
Unit ID#
52
Name
Joe
Health
100%
Mesh
etc.
Unit ID#
98
Name
Bob
Health
92%
Unit ID#
182
Mesh
Name
Jim
etc.
Health
8%
Mesh
etc.
Unity example:
◦ Renderer.sharedMaterial
Unity Gotcha #1
Components are not shared data
◦ new class RobsComponent extends MonoBehaviour
◦ If you add a new RobsComponent to five game objects,
each gets a new instance!
Unity has a magic class for this: ScriptableObject
Unit ID#
98
Name
Bob
Health
92%
Unit ID#
182
Mesh
Name
Jim
Config
Health
8%
Mesh
Config
To share data across multiple game objects:
◦ 1. subclass from ScriptableObject
◦ 2. make an instance with shared data
◦ 3. give your components a reference to this object
Scriptable
Object
Unity Gotcha #2
Unity example: Renderer.material
vs Renderer.sharedMaterial
public class ExampleClass : MonoBehaviour {
void Awake() {
renderer.material.color = Color.red;
}
}
public class ExampleClass : MonoBehaviour {
void Awake() {
renderer.sharedMaterial.color = Color.red;
}
}
Makes a copy of the material
and changes the color of just this copy
Modifies the material shared with others
 This game object is red, others are unchanged
 There are now two materials in memory!
 This object and all game objects that share this
material are now red
Data model separation
We’ll need game save/load functionality
We don’t want to save the whole thing
◦ Just data that’s unique per instance
Unit ID#
52
Name
Joe
Health
100%
Unit ID#
98
Name
Bob
Health
92%
Mesh
etc.
Mesh
etc.
Unit ID#
182
Name
Jim
Health
8%
Mesh
etc.
Data model separation
Unit
We’ll need game save/load functionality
We don’t want to save the whole thing
◦ Just data that’s unique per instance
“Memento” pattern
◦ Separate out unique data
◦ Only save/load that data
Unit
ID#
52
Data
Name
Joe
Mesh
Health
100%
Data
ID#
98
Mesh
Name
Bob
etc.
Health
92%
etc.
Unit
Data
ID#
182
Mesh
Name
Jim
etc.
Health
8%
Compare with Unity components
Typical Unity component:
Separated out version:
public class MyComponent : MonoBehaviour {
public class MyComponent : MonoBehaviour {
public Vector2 position;
public string name;
public float health;
public MyData data;
// ... code
// ... code
public void DoAttack (...) { ... }
}
public void DoAttack (...) { ... }
}
public class MyData {
public Vector2 position;
public string name;
public float health;
}
// or struct?!
Part 2: Data layout patterns
Summary
Flyweight
◦ Share data across instances instead of duplicating
◦ Use UnityEngine.ScriptableObject
Memento
◦ Put important internal state in a separate object
◦ This is not the “default” way in Unity, but it makes it much easier to implement save files
Part 3: Communication patterns
We’ve loaded up our TD game…
Tower wants to know when a
monster is near so it can attack
Monster will just keep walking
until it reaches its endpoint
Points: 10
Scoreboard wants to know
when each monster gets killed
Communication patterns
A lot of gameplay code has to do with observing game state updates and reacting to them:
- from entities to entities
“a tower noticed a monster getting near”
- from entities to systems
“you kill all monsters, which gives points and ends a quest”
- from systems to systems
“when the quest is done, unlock a new map + achievement”
- from systems to entities
“when the quest is done, all units play a victory dance”
UI is a pervasive kind of an “uber-system”
◦ Internal state of other systems usually needs to be reflected in UI
◦ Player actions in UI usually end up affecting other systems
One example
How do we implement this:
“a tower noticed a monster getting near”
Some options:
◦ Tower checks distance to all entities on each frame
◦ Baddie tells all other entities, “I moved to (x, y)”
◦ Higher-level optimized system for distance checks
that the tower + baddie can subscribe to
No perfect answer,
just a bunch of trade-offs
Communication patterns
Goal: decouple senders and receivers
◦ Senders shouldn’t know who they need to inform
Two common ways
◦ Observer pattern
◦ Event bus pattern
Notice this hooks right into our MVC pattern from before! :)
Observer pattern
public class Player {
public class UIManager {
public int Level { get; private set; }
public void InitializeUI () {
var player = ... // get a reference
player.OnLevelUpdate += UpdateUI;
}
public event Action OnLevelUpdate;
public void UpdateLevel (int newlevel) {
this.Level = newlevel;
OnLevelUpdate();
}
private void UpdateUI() {
var player = ... // get a reference
int newLevel = player.Level;
// update the UI
}
}
}
UpdateLevel()
Player
Event callback
UIManager
Other listeners
Other listeners
Observer pattern
Observer
◦ Each observer needs to 1. find potential sender,
2. subscribe (and unsubscribe) to updates
Best for:
◦ System  Entity updates
◦ Eg. Level finished  all entities run away
◦ System  System updates
◦ Eg. Player score increment  UI update
Event Bus / Command pattern
AddEvent (GameEvent ge)
public class GameEvent {
// some fields: event type ID, reference to sender, etc
}
public class GameEventBus : MonoBehaviour {
GameEvent
private Queue<GameEvent> _events;
public event Action<GameEvent> OnGameEvent;
GameEvent
..
.
public void AddEvent (... event data) {
GameEvent evt = new GameEvent(...);
_events.Enqueue(evt);
}
GameEvent
public void Update () {
while (_events.Count > 0) {
GameEvent evt = _events.Dequeue();
OnGameEvent(evt);
}
}
On update(), send all
events to all subscribers
Entity
Entity
Entity
}
Observer vs Event Bus
Observer
◦ Each observer needs to 1. find potential sender,
2. subscribe (and unsubscribe) to updates
Event Bus
◦ One global bus for everyone to subscribe to
◦ Each event goes out to all subscribers!
◦ Obvious optimization: different busses for different events
Best for:
◦ System  Entity updates
◦ Eg. Level finished  all entities run away
◦ System  System updates
◦ Eg. Player score increment  UI update
Best for:
◦ Entity  Entity updates
◦ Eg. Player dies  all monsters do a happy dance
◦ Entity  System updates
◦ Eg. Monster dies  Player score increment
Part 3: Communication patterns
Observing game state updates and reacting to them:
- from entities to entities
- from entities to systems
many different kinds of senders/receivers: event bus
- from systems to systems
- from systems to entities
few senders / few receivers: observer
Part 4: Behaviors
Implement different ways of eg. pathfinding
◦ Strategy pattern
◦ Separate algorithm from data
Making sure they do everything in order
◦ Command pattern
◦ Eg. “action queue” in RTS games
Let’s go back to our units
Maybe we have two monsters with two
different behaviors:
1. Walking monster that
a. spawns
b. follows a path to the end point
c. then attacks
2. Flying monster that
a. spawns
b. flies straight to the end point
c. waits for five seconds
d. then attacks
Pathfinding
Let’s say we want different pathfinding for
walkers vs fliers vs other animals…
InitializeGrid(mGrid);
if (walker) {
findWalkingPath();
} else if (flier) {
findBeelinePath();
}
return mGrid;
… that’s not very modular or extensible!
… but it’s so tempting :)
Strategy pattern
class GameBoardContext {
1. Keep all shared state in one object
2. Encapsulate algorithm in other objects,
that can be switched in and out
}
Strategy pattern
class GameBoardContext {
IPathAlgorithm
FindPath(context);
AStarPathing
BeelinePathing
… and then the entity can pick an algorithm,
and pass it the current game context
}
Strategy pattern
InitializeGrid(mGrid);
InitializeGrid(mGrid);
if (walker) {
findWalkingPath();
} else if (flier) {
findBeelinePath();
}
return mGrid;
IPathfindingAlgorithm algo = GetAlgorithm();
algo.findPath(mGrid);
return mGrid;
Yey!
Back to our example
Flying monster found the path
Now we want it to perform this sequence:
a. flying monster spawns
b. flies straight to the end point
c. waits for five seconds
d. then attacks
We’ll use this to introduce the
command pattern
Bad version – hardcode all the things
On each update:
if (! _spawned) {
SpawnMonster();
_spawned = true;
} else if (! NearGoal()) {
FlyToGoal();
} else if (! TimerStarted()) {
StartTimer();
} else if (TimerFinished()) {
Attack();
}
This is some freakin’ ugly code
Also, this is just a handcrafted state machine
Okay version – finite state machine
🐦
Spawn
spawning
done
Wait
Fly beeline
to goal
near goal
timer finished
Attack
+ FSMs are easy to implement
- Messy once you scale up,
or add different variants
Better version – action sequence
My action queue:
🐦
Spawn at (x, y)
Fly beeline to (x’, y’)
Each of these is a
data object inside
a Queue<> that
fully encapsulates
that behavior
Wait 5 seconds
Attack
DONE
Better version
My action queue:
🐦
Spawn at (x, y)
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Better version
My action queue:
🐦
Spawn at (x, y)
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Wait 5 seconds
Attack
DONE
Better version
My action queue:
🐦
Spawn at (x, y)
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Wait 5 seconds
Attack
DONE
Attack
DONE
Better version
My action queue:
🐦
Spawn at (x, y)
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Fly beeline to (x’, y’)
Wait 5 seconds
Attack
DONE
Wait 5 seconds
Attack
DONE
Attack
DONE
DONE
Now figure out what to do next
& refill action queue with more actions
Action sequence flexibility
🐦
Flying monster action queue:
🚶
Walking monster action queue:
Spawn at (x, y)
Spawn at (x, y)
Fly beeline to (x’, y’)
Run A*
Wait 5 seconds
Follow path computed by A*
Attack
DONE
Attack
DONE
Mix and match functionality
Easy to implement
Used in a lot of games (RTSes, The Sims, etc)
Command pattern
1. Split up behavior logic into steps
◦ Each step is its own class
2. Create a queue of commands
3. On each frame:
◦ Let only the first command run its logic
◦ Ask the first command if it’s done
◦ If so, dequeue it and start the next one
This also shows up everywhere you have
◦ a sequence of things
◦ that need to take a while
◦ but need to run in sequence
Part 4: Behaviors
Summary
Command
◦ Encapsulate small behaviors inside classes
◦ Instead of hard-coding a state machine, use a Queue<> of those commands
◦ The head of the queue is the only one that actually runs
◦ Then when it’s done, pop and move to the next one
Strategy
◦ When you have multiple variants of the same behavior, don’t use if/else statements
◦ Encapsulate each variant in a class, and switch them at runtime
Part 5: Modularity
Modularity example
A game system might need to have multiple implementations, and switch between them:
◦
◦
◦
◦
◦
To match player’s hardware
To match the platform
To improve performance
To aid in debugging
etc.
(eg. read from gamepad controller, or from keyboard and mouse)
(eg. different filesystem APIs on iOS vs Android)
(eg. replace a 3D audio system with a less expensive 2D one)
(eg. in debug mode show all errors, in release mode log to text file)
Game logic shouldn’t know anything about any of this!
Modularity
Example 1:
◦ Switch behaviors at runtime
based on game or player preferences
Examples: Error logging,
switching input mappings
Example 2:
◦ Quarantine platform-specific details
without affecting the rest of the code
Example: High scores table
Logging service example
We want alerts during development,
if something doesn’t look right:
But when doing a demo, this would look bad!
Instead, log errors quietly to a log file:
var enemies = LoadDefinitions("enemy");
var enemies = LoadDefinitions("enemy");
if (enemies.count == 0) {
UIManager.ShowError("Failed to load!");
}
if (enemies.count == 0) {
#if UNITY_EDITOR
UIManager.ShowError("Failed to load!");
#else
TextLogger.LogError("Failed to load!");
#if
}
This is ugly. Game code shouldn’t care.
Logging service
abstract class Logger
static ILogger instance;
abstract void LogError(string)
TextFileLogger
ErrorDialogLogger
void LogError(string)
void LogError(string)
At initialization:
Logger.instance =
new TextFileLogger("c:\log.txt");
In game code:
var enemies = LoadDefinitions("enemy");
(write to text file)
(display a popup window)
if (enemies.count == 0) {
Logger.instance.LogError("Failed to load!");
}
State pattern
1. Encapsulate functionality in interchangeable
objects that share the same interface
2. Switch objects at runtime, to make it look
like system’s behavior changes
◦ a. Either a single “currently active” state
b. Or maybe a stack of states
This pattern shows up everywhere
If you’re doing a lot of
if-then checks based on game state,
this is for you!
State pattern: input handlers
IInputHandler
Walking
Handler
Standard controls
IInputHandler currentHandler;
Flying
Handler
Standard controls
+ move up/down in air
Swimming
Handler
Sluggish controls +
+ move up/down under water
High scores example
Second example: we have platform-dependent
parts of the game
Example: posting high score to a leaderboard
◦ iOS, Android – provided by the OS
◦ Windows – requires 3rd party SDK like Steam
◦ etc.
BADNESS:
void PostHighScore () {
int score = DoSomeComputation();
#ifdef IOS_BUILD
IOSService.PostScore(board, score);
#endif
#ifdef WINDOWS_BUILD
InitializeSteamLibrary();
SteamService.PostScore(score);
#endif
…
This is ugly. Game code shouldn’t care.
High scores example
Pattern: “Bridge”
HighScoreManager
Solution:
void PostScore(int)
◦ We use the state pattern for actual functionality
◦ A parent service contains and hides
one of those implementations
void PostHighScore () {
int score = DoSomeComputation();
HighScoreManager.instance.PostScore(score);
}
private IHighScore impl;
Interface IHighScore
void PostScore(int)
SteamHighScoreImpl
iOSHighScoreImpl
void PostScore(int)
void PostScore(int)
(initialize Steam SDK)
(call the SDK)
(call the iOS SDK)
Some variants
Problem: how do we initialize this service?
abstract public class Logger {
private static Logger _instance;
Option 1: Singleton – initializes itself
public static Logger getInstance () {
if (_instance == null) {
_instance = new TextFileLogger("c:\log.txt");
}
return _instance;
}
◦ Very convenient (“pay for what you use”)
◦ But what if we want to switch
implementations?
}
Game code:
Logger.getInstance().LogError("blah");
Some variants
Problem: how do we initialize this service?
Option 1: Singleton – initializes itself
abstract public class Logger {
public static Logger instance;
public void LogError (string text);
}
Option 2: Static service locator
◦ Better! Now we control implementation
◦ Needs an initialization call
Initialization code:
Logger.instance = new TextFileLogger("c:\log.txt");
Game code:
Logger.instance.LogError("blah");
Some variants
Problem: how do we initialize this service?
public class Services {
public static Logger logger;
public static void InitializeServices () {
logger = new TextFileLogger(...);
}
Option 1: Singleton – initializes itself
Option 2: Static service locator
public static void InitializeServicesDebug () {
logger = new ErrorDialogLogger();
}
Option 3: “God object” service locator
◦ Groups entire families of services
}
Initialization code:
ServiceLocator.InitializeServices();
Game code:
Services.logger.LogError("blah");
Part 5: Modularity
Summary
State pattern – encapsulate different variants into classes
◦ Game code doesn’t have to know which platform it’s running on, etc.
◦ No if/else or #ifdefs! :)
Bridge pattern – hides different behavioral states inside it
Quick overview
UI
game state stacks, model-view-controller
Data Layout
shared read-only config data, externalized saved data
Communication
event callbacks, global event bus
Behavior
command queues, strategy objects
Modularity
state pattern, hiding implementation details
Decouple dependencies between game systems
Make it easier to mix and match behavior
Speed up development and iteration
Resources for further reference
“Design Patterns: Elements of Reusable
Object-Oriented Software”, Gamma et al.
“Game Programming Patterns”, Nystrom
http://gameprogrammingpatterns.com/
Qs?