In yesterday’s post, I wrote a summary of the first three patterns mentioned in this book. Now, I’m going to move on to the next three; Prototype, Singleton, and State.
Book Summary: Game Programming Patterns
- Patterns Revisited, Part 1
- Patterns Revisited, Part 2
- Sequencing Patterns
- Behavioural Patterns
- Decoupling Patterns
- Optimisation Patterns
Patterns Revisited, Part 2
Prototype
A Prototype object is an object that is used to create other objects similar to themselves. In other words, a Prototype object is a template to create clones. All the data in the fields of the Prototype object are copied into the new one.
Unity Prefab System is using this pattern. Any GameObject can be fed into a GameObject.Instantiate() function to retrieve its clone. The system answers the complexity of the Prototype pattern such as hierarchical object cloning and copying fields, although the system doesn’t clone the state of a running AnimatorController Component.
The book also mentions the Prototype Language Paradigm. Self is using this paradigm. Self doesn’t have a class definition, it has objects that can be cloned instead. To support inheritance, an object can delegate messages to another object. Those objects handling delegations can be added or removed at run time, hence Self supports dynamic multiple inheritance. JavaScript also supports Prototype; we can add functions/fields to the Prototype to make the instances share them.
Prototype paradigm is good for data modelling. Have an entity inherit the same fields from another entity definition to avoid data duplication.
Singleton
Singleton ensures a class has only one instance that is globally accessible. It’s nice for working on fast game prototypes alone but terrible for anything more serious than a game jam.
The positives:
- We can defer the initialization of Singletons until we need it.
- Singletons can have sub-classes.
- We can use this to implement a multi-platform input system. Have a Singleton base class defines the API. Then, we define multiple sub-classes for each platform. At run time, a chosen Singleton instance is created after checking the platform we are on.
The negatives:
- Singletons are a global variable.
- It can be coupled with anything because it can be accessed from anywhere.
- Its state is also global, hence not concurrent friendly.
- Singleton answers the need for both global access and the only instance, while we might only need global access.
What do we do if we thought we need Singletons?
- Think again. We might need to design better classes.
- Ensure a class can only have one instance only but make it not globally accessible. If it is instantiated, return
null
. - For convenient access to an object:
- Just pass around the reference into the functions that need it.
- Have a base class owns the object. Use Service Locator to have the base class retrieves the object and then use Subclass Sandbox to add a layer of abstraction between the located Service object and the sub-classes that require its services.
State
Delegate function calls to an internal State object so an object appears to “change class” by having its internal State object altered. There are other patterns with similar approach–that is the delegation of function calls into an internal object; with different goals:
- To have the main class change its behaviour in run-time by adding a ‘context’ parameter that is used to decide which subclass to call, use Strategy pattern.
- To make some instances with the same “type” share a reference to an object and call the same functions, use Type Object pattern.
- To change a class behaviour by changing the reference of its internal state object that may or may not be shared, use State object.
Let’s start with the Finite State Machine. It has a fixed number of state that the machine–our entity or character, can be in. The machine can only be in one state at a time. Inputs are sent to the machine which triggers transitions into other states.
Hierarchical State Machine allows a parent State object to have another set of States objects inside it. One of the children states may or may not be assigned as the default state. The parent State object can delegate function calls to the current child State object if any.
If we need to remember the previous states so we can go back, we need to extend our state machine into a Pushdown Automata. Use a stack and push the entered State objects into the stack and pop it if we need to go back, according to the inputs.
The State pattern implementations mentioned so far are good for a simple AI, input handling for controlling characters movements, UI navigation, and simple text parsing. For more complex state transitions for AI behaviour, we might need a Turing Machine or a Behaviour Tree.
The next patterns to be discussed in the book are Sequencing Patterns. The summary of that section is here.