Introduction to Finite State Programming
In a game, the player can be in a finite number of states. The player can move from one state to another based on the player's actions. This is the essence of finite state programming.
Introduction to State Machines
State machines, also known as finite state machines (FSMs) or finite state automata (FSAs), are mathematical models of computation used in computer science, linguistics, and related fields. They are abstract machines that can be in exactly one of a finite number of states at any given time.
States
In the context of a game, a state could represent the status or condition of a game character at a given time. For example, state.animation
could represent the current animation state of a character, such as ‘idle’, ‘walk’, ‘run’, or ‘jump’. Similarly, state.collision
could represent the current collision state of a character, such as ‘floor’, ‘wall’, or ‘jumpPlatform’.
Transitions
Transitions are the rules or conditions that dictate when and how a state machine can move from one state to another. In a game, a transition could be triggered by a user action (like pressing a key), a timer, or a change in game conditions (like a collision with another object).
Actions
Actions are the behaviors associated with a particular state. When a state machine enters a state, it might perform an action associated with that state. For example, if a character enters the ‘walk’ state, the action might be to start the walking animation.
In the context of a game, state machines can be used to manage game logic, character behaviors, animations, and more. They provide a structured way to handle the complex, dynamic conditions that can arise in a game.
The role of states in game logic
Based on various factors, some synchronous and some asynchronous, the game states will change. JavaScript objects have been created to track these specific states. Finite state game logic is used to process these states as the game loop proceeds.
Here is an example of collision finite state logic. This logic handles collisions between the player and other game objects.
switch (this.state.collision) {
// 1. Player is on a jump platform
case "jumpPlatform":
// Player is on top of the jump platform
// ... code
break;
// 2. Player is on or touching a wall
case "wall":
// Player is on top of the wall
// ... code
break;
// 3. Player is in default state
case "floor":
// Player is on the floor
// ... code
break;
}
Here is an example of animation finite state logic. This logic sets the sprite animation frame based on the player’s current state.
switch (this.state.animation) {
case 'idle':
this.setSpriteAnimation(this.playerData.idle[this.state.direction]);
break;
case 'walk':
this.setSpriteAnimation(this.playerData.walk[this.state.direction]);
break;
case 'run':
this.setSpriteAnimation(this.playerData.run[this.state.direction]);
break;
case 'jump':
this.setSpriteAnimation(this.playerData.jump[this.state.direction]);
break;
default:
console.error(`Invalid state: ${this.state.animation}`);
}
Finite state change
The code below shows how objects can be changed to create a different state in the game. Be sure to review PlayerBase.js for the actual finite state game logic.
%%js
class PlayerBase {
/**
* Initial environment of the player.
* @property {string} collision - Name of the current object the player is interacting with (e.g., 'floor', 'wall', 'platform').
* @property {Array} collisions - An array that holds a collection of player collisions.
* @property {string} animation - Name of the current animation state of the player (e.g., 'idle', 'walk', 'run', 'jump').
* @property {string} direction - The direction the player is facing (e.g., 'left', 'right').
* @property {Object} movement - The directions in which the player can move.
* @property {boolean} movement.up - Whether the player can move up.
* @property {boolean} movement.down - Whether the player can move down.
* @property {boolean} movement.left - Whether the player can move left.
* @property {boolean} movement.right - Whether the player can move right.
* @property {boolean} movement.falling - Whether the player is falling.
* @property {boolean} isDying - Whether the player is dying.
*/
// This object represents the initial state of the player when the game starts.
initEnvironmentState = {
// environment
collision: 'floor',
collisions: [],
// player
animation: 'idle',
direction: 'right',
movement: {up: false, down: false, left: true, right: true, falling: false},
isDying: false,
};
/** GameObject: Constructor for Player object
*/
constructor() {
this.state = {...this.initEnvironmentState}; // Player and environment states
}
toStringStates() {
var msg = "Animation: " + this.state.animation;
msg += ", Collision: " + this.state.collision;
msg += ", Direction: " + this.state.direction;
msg += ",";
return msg;
}
toStringAction() {
var msg = "Movement: ";
for (const [key, value] of Object.entries(this.state.movement)) {
msg += key + ": " + value + ", ";
}
return msg;
}
toString() {
return this.toStringStates() + "\n" + this.toStringAction();
}
}
// Create a new PlayerBase object
var player = new PlayerBase();
element.append(player.toString()); // 'element.append' only works in Jupyter. Use 'console.log()' in a web environment.
// Spacing for the next output
element.append('<br><br>');
// Change the collision state of the player
player.state.animation = 'jump';
player.state.collision = 'jumpPlatform';
player.state.direction = 'left';
player.state.movement = {up: false, down: false, left: true, right: true, falling: false};
element.append(player.toString());
<IPython.core.display.Javascript object>