Handling input via the command pattern for a Javascript game

- 5 mins
Hello1

Command pattern for a Javascript / Typescript game

In this tutorial we are going to pause on writing code so that we can study the command pattern. Often in software development the same problem crops up again and again and eventually an agreed upon best practice design pattern emerges for solving the problem. The command pattern is a generic software programming design pattern to help decouple the configuration of some service from the way certain actions or events are triggered when calling it.

The Gang of Four are the authors of the book, "Design Patterns: Elements of Reusable Object-Oriented Software" who are known for helping define and popularise some of the most commonly used software design patterns today including the command design pattern. You can read more about the gang of four design patterns here at the gang of four pattern website.

The command design pattern has gained popularity in the game development community because of it's problem overlap with handling game input and the simple solution it provides. We will be implementing our own version of the game command design pattern. For our purposes we will simplify the pattern and go over how we can apply it to creating our generic input handling framework below.

Game input handling and updating game state

Below is a high level example of how a game handles input and interprets that input correctly in order to update the game state.

Game input handling process.

Lets go over the main phases below.

Listen to operating system low level input events

In this phase we need to listen and capture all the low level input events for all the devices our game system supports. The term low level event just means events that are closer to the hardware and operating system or in our case the browser, think html key press event, mouse button event, gamepad button tap, joystick etc. How a browser listens for and handles keyboad events is going to be distinct from how it handles mouse or gamepad events.

Transform low level input events into general high level action events

In this phase the low level events are read and transformed into generalised game action events for each player. In order to achieve this the individual player control input mappings are required.

In this phase we need an understanding of the different low level events and how they map to the core supported game actions, think horizontal (x), vertical (y) direction to represent a given inputs direction axis or primary and secondary actions. It can translate and combine all the low level input events into a standard input that represents high level game actions. After this phase we no longer care if the f key is pressed but rather that the primaryAction is enabled.

Calculate game entities new state

In this phase we take the standard game action input and apply it to all the player game entities. Here player refers to any game entity that is directly controlled by an input. Perhaps when the primary action is enabled the player should attempt to jump. Note the attempt keyword is there on purpose as there could be situations where the player is unable to jump. For example if they are in mid air (and the game doesn't allow mid air jumping) or if they have run out of their jump ability or are currently taking damage. The point here is that all these conditions of when a player can and can't jump are unique to the game we are creating.

The actions may even be different depending on the state of the game, ie the primary action could trigger a different event depending on if the game is on the inventory screen vs the interactive play state for an RPG game as an example. In this phase we just need to trigger events to attempt the required actions and let the next GameState resolution phase take care of the actual effects on the game state.

Gamestate resolution

Based on the new game entities proposed state we may have produced an invalid game state. In this phase we must check if the game state is valid and use game logic to resolve any invalid game state. Examples of invalid game state is an object's position is out of bounds or an object is overlapping with another object when it should collide instead.

The game resolution logic is as simple or as complicated as it needs to be to adhere to the game rules.

Update game state

In this phase we update the game state based on the resulting resolved valid game state in the previous phase.

Game input command design pattern

In the above diagram the green boxes represent the phases that can be modelled with the help of the command design pattern. Looking at these phases in more detail will help in understanding the next tutorial where we will implement this pattern.

The first two phases have been expanded in the diagram below.

Game input command pattern.

The left of the diagram shows the different input devices connected to the compatible and provided input ports. The devices emmit low level input events through the port which are mapped to the device specific input API state. The InputStateManager listens to the various low level input events and captures these events in memory. This happens progressively, as more low level events occur inbetween game frames the input is updated to accomodate the new events.

The InputManager retrieves the low level input events as well as each player's individual player input mappings. Then based on each invidual players selected input port and input control mappings the input manager is able to resolve each player's StandardGameInput (high level input actions). We'll talk more about exactly what this StandardGameInput looks like in the next tutorial. For now you can think of it as a representation of actions the game supports that are decoupled from a specific input device.

Each player now has an associated StandardGameInput and these are used to calculate the new players state during the rest of the game input handling process phases mentioned above.

Summary

Hopefully from the explanations above you are starting to see how and why it is important to be able to decouple and abstract the low level hardware / operating system / browser input events from the high level game logic actions. By doing this we are making it easier for our game to not be limited to one input device and we are also defining a standard game action representation that is distinct from the input devices it supports.

Later in the course we'll see that the effort required to do this will be justified by how much easier it will be to extend our input system to support gamepad controllers.

Indeed once we have our game input framework in place we will be able to reuse it for many different games and shift our focus to what the different StandardGameInput's mean in relation to the context and state of the unique game.

We'll start modelling some of these ideas in the next tutorial!