Finite State Machines (FSMs) are fundamental to game development for managing character behavior, game states, and complex workflows. This comprehensive guide covers FSM patterns, implementations, and advanced techniques for Java games.
FSM Architecture Overview
FSM Core Concepts: ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ States │ │ Transitions │ │ Actions │ │ • Idle │ -> │ • Conditions │ -> │ • On Enter │ │ • Moving │ │ • Triggers │ │ • On Exit │ │ • Attacking │ │ • Guards │ │ • On Update │ └─────────────────┘ └──────────────────┘ └─────────────────┘
Basic FSM Implementation
1. Core FSM Interface
package com.game.fsm; public interface State { void enter(); void update(float deltaTime); void exit(); String getName(); } public interface StateMachine { void addState(State state); void setInitialState(String stateName); void changeState(String stateName); State getCurrentState(); void update(float deltaTime); } 2. Generic State Machine Implementation
package com.game.fsm; import java.util.HashMap; import java.util.Map; public class GenericStateMachine implements StateMachine { private final Map<String, State> states; private State currentState; private State previousState; public GenericStateMachine() { this.states = new HashMap<>(); this.currentState = null; this.previousState = null; } @Override public void addState(State state) { states.put(state.getName(), state); } @Override public void setInitialState(String stateName) { State initialState = states.get(stateName); if (initialState != null) { currentState = initialState; currentState.enter(); } else { throw new IllegalArgumentException("Initial state not found: " + stateName); } } @Override public void changeState(String stateName) { State nextState = states.get(stateName); if (nextState == null) { throw new IllegalArgumentException("State not found: " + stateName); } if (currentState != null) { currentState.exit(); previousState = currentState; } currentState = nextState; currentState.enter(); System.out.println("State changed: " + (previousState != null ? previousState.getName() : "null") + " -> " + currentState.getName()); } @Override public State getCurrentState() { return currentState; } @Override public void update(float deltaTime) { if (currentState != null) { currentState.update(deltaTime); } } public void revertToPreviousState() { if (previousState != null) { changeState(previousState.getName()); } } public boolean isInState(String stateName) { return currentState != null && currentState.getName().equals(stateName); } public Map<String, State> getStates() { return new HashMap<>(states); } } Game Character State Management
1. Enemy AI State Machine
package com.game.fsm.enemy; import com.game.fsm.State; import com.game.fsm.GenericStateMachine; public class EnemyAI { private final GenericStateMachine stateMachine; private final Enemy enemy; private float detectionRange = 10.0f; private float attackRange = 2.0f; public EnemyAI(Enemy enemy) { this.enemy = enemy; this.stateMachine = new GenericStateMachine(); initializeStates(); } private void initializeStates() { // Add all enemy states stateMachine.addState(new IdleState()); stateMachine.addState(new PatrolState()); stateMachine.addState(new ChaseState()); stateMachine.addState(new AttackState()); stateMachine.addState(new FleeState()); stateMachine.addState(new DeadState()); // Set initial state stateMachine.setInitialState("IDLE"); } public void update(float deltaTime) { stateMachine.update(deltaTime); } // Enemy States private class IdleState implements State { private float idleTime = 0; private final float maxIdleTime = 3.0f; @Override public void enter() { idleTime = 0; enemy.setAnimation("idle"); System.out.println(enemy.getName() + " is now idle"); } @Override public void update(float deltaTime) { idleTime += deltaTime; // Check for player detection if (enemy.canSeePlayer(detectionRange)) { stateMachine.changeState("CHASE"); return; } // Transition to patrol after idle time if (idleTime >= maxIdleTime) { stateMachine.changeState("PATROL"); } } @Override public void exit() { idleTime = 0; } @Override public String getName() { return "IDLE"; } } private class PatrolState implements State { private float patrolTimer = 0; private final float patrolPointDuration = 5.0f; @Override public void enter() { patrolTimer = 0; enemy.setAnimation("walk"); enemy.moveToNextPatrolPoint(); System.out.println(enemy.getName() + " started patrolling"); } @Override public void update(float deltaTime) { patrolTimer += deltaTime; // Check for player detection if (enemy.canSeePlayer(detectionRange)) { stateMachine.changeState("CHASE"); return; } // Move to next patrol point if (patrolTimer >= patrolPointDuration || enemy.hasReachedDestination()) { enemy.moveToNextPatrolPoint(); patrolTimer = 0; } enemy.updateMovement(deltaTime); } @Override public void exit() { patrolTimer = 0; } @Override public String getName() { return "PATROL"; } } private class ChaseState implements State { @Override public void enter() { enemy.setAnimation("run"); System.out.println(enemy.getName() + " is chasing player"); } @Override public void update(float deltaTime) { if (!enemy.canSeePlayer(detectionRange)) { stateMachine.changeState("IDLE"); return; } if (enemy.getDistanceToPlayer() <= attackRange) { stateMachine.changeState("ATTACK"); return; } if (enemy.getHealthPercentage() < 0.3f) { stateMachine.changeState("FLEE"); return; } enemy.chasePlayer(deltaTime); } @Override public void exit() { enemy.stopMovement(); } @Override public String getName() { return "CHASE"; } } private class AttackState implements State { private float attackCooldown = 0; private final float attackInterval = 1.5f; @Override public void enter() { attackCooldown = 0; enemy.setAnimation("attack"); System.out.println(enemy.getName() + " is attacking"); } @Override public void update(float deltaTime) { attackCooldown += deltaTime; if (!enemy.canSeePlayer(attackRange)) { stateMachine.changeState("CHASE"); return; } if (enemy.getHealthPercentage() < 0.2f) { stateMachine.changeState("FLEE"); return; } if (attackCooldown >= attackInterval) { enemy.performAttack(); attackCooldown = 0; } enemy.facePlayer(); } @Override public void exit() { attackCooldown = 0; } @Override public String getName() { return "ATTACK"; } } private class FleeState implements State { private float fleeTime = 0; private final float maxFleeTime = 8.0f; @Override public void enter() { fleeTime = 0; enemy.setAnimation("run"); enemy.startFleeing(); System.out.println(enemy.getName() + " is fleeing"); } @Override public void update(float deltaTime) { fleeTime += deltaTime; if (fleeTime >= maxFleeTime || !enemy.canSeePlayer(detectionRange * 1.5f)) { stateMachine.changeState("IDLE"); return; } if (enemy.getHealthPercentage() < 0.1f) { stateMachine.changeState("DEAD"); return; } enemy.updateFleeMovement(deltaTime); } @Override public void exit() { fleeTime = 0; enemy.stopMovement(); } @Override public String getName() { return "FLEE"; } } private class DeadState implements State { @Override public void enter() { enemy.setAnimation("dead"); enemy.onDeath(); System.out.println(enemy.getName() + " has died"); } @Override public void update(float deltaTime) { // No updates in dead state } @Override public void exit() { // Cannot exit dead state } @Override public String getName() { return "DEAD"; } } // Getters public GenericStateMachine getStateMachine() { return stateMachine; } public String getCurrentStateName() { return stateMachine.getCurrentState().getName(); } } 2. Enemy Entity Class
package com.game.fsm.enemy; public class Enemy { private String name; private float health; private float maxHealth; private Vector2 position; private Vector2[] patrolPoints; private int currentPatrolIndex; private Player target; public Enemy(String name, float maxHealth, Vector2 startPosition) { this.name = name; this.maxHealth = maxHealth; this.health = maxHealth; this.position = startPosition; this.patrolPoints = new Vector2[0]; this.currentPatrolIndex = 0; } public boolean canSeePlayer(float range) { if (target == null) return false; return position.distanceTo(target.getPosition()) <= range; } public float getDistanceToPlayer() { return target != null ? position.distanceTo(target.getPosition()) : Float.MAX_VALUE; } public float getHealthPercentage() { return health / maxHealth; } public void setAnimation(String animation) { // Implementation for animation system System.out.println(name + " animation: " + animation); } public void moveToNextPatrolPoint() { if (patrolPoints.length == 0) return; currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.length; // Set movement target to patrolPoints[currentPatrolIndex] } public boolean hasReachedDestination() { // Implementation for movement system return true; // Simplified } public void updateMovement(float deltaTime) { // Implementation for movement system } public void chasePlayer(float deltaTime) { if (target != null) { // Move toward player // position.moveTowards(target.getPosition(), speed * deltaTime); } } public void stopMovement() { // Stop any ongoing movement } public void performAttack() { // Attack logic System.out.println(name + " attacks!"); if (target != null) { // target.takeDamage(attackDamage); } } public void facePlayer() { if (target != null) { // Rotate to face player } } public void startFleeing() { // Set flee destination away from player } public void updateFleeMovement(float deltaTime) { // Move away from player } public void onDeath() { // Death logic - drop loot, play death animation, etc. } public void takeDamage(float damage) { health = Math.max(0, health - damage); if (health <= 0) { // State machine will transition to DEAD state } } // Getters and setters public String getName() { return name; } public Vector2 getPosition() { return position; } public void setTarget(Player target) { this.target = target; } public void setPatrolPoints(Vector2[] points) { this.patrolPoints = points; } } Advanced FSM with Transition System
1. Transition-Based State Machine
package com.game.fsm.advanced; import java.util.*; public class TransitionStateMachine implements StateMachine { private final Map<String, State> states; private final Map<String, List<Transition>> transitions; private State currentState; private final Object context; public TransitionStateMachine(Object context) { this.states = new HashMap<>(); this.transitions = new HashMap<>(); this.currentState = null; this.context = context; } public void addState(State state) { states.put(state.getName(), state); transitions.put(state.getName(), new ArrayList<>()); } public void addTransition(String fromState, String toState, TransitionCondition condition) { List<Transition> stateTransitions = transitions.get(fromState); if (stateTransitions != null) { stateTransitions.add(new Transition(fromState, toState, condition)); } } @Override public void setInitialState(String stateName) { State initialState = states.get(stateName); if (initialState != null) { currentState = initialState; currentState.enter(); } } @Override public void changeState(String stateName) { State nextState = states.get(stateName); if (nextState == null) return; if (currentState != null) { currentState.exit(); } currentState = nextState; currentState.enter(); } @Override public void update(float deltaTime) { if (currentState == null) return; // Check for transitions checkTransitions(); // Update current state currentState.update(deltaTime); } private void checkTransitions() { List<Transition> stateTransitions = transitions.get(currentState.getName()); if (stateTransitions == null) return; for (Transition transition : stateTransitions) { if (transition.condition.isMet(context)) { changeState(transition.toState); break; // Only take first valid transition } } } @Override public State getCurrentState() { return currentState; } // Transition classes public static class Transition { public final String fromState; public final String toState; public final TransitionCondition condition; public Transition(String fromState, String toState, TransitionCondition condition) { this.fromState = fromState; this.toState = toState; this.condition = condition; } } public interface TransitionCondition { boolean isMet(Object context); } } 2. Conditional Transitions
package com.game.fsm.advanced; public class EnemyTransitionConditions { public static class PlayerInRangeCondition implements TransitionStateMachine.TransitionCondition { private final float range; public PlayerInRangeCondition(float range) { this.range = range; } @Override public boolean isMet(Object context) { if (context instanceof Enemy) { Enemy enemy = (Enemy) context; return enemy.canSeePlayer(range); } return false; } } public static class PlayerOutOfRangeCondition implements TransitionStateMachine.TransitionCondition { private final float range; public PlayerOutOfRangeCondition(float range) { this.range = range; } @Override public boolean isMet(Object context) { if (context instanceof Enemy) { Enemy enemy = (Enemy) context; return !enemy.canSeePlayer(range); } return false; } } public static class LowHealthCondition implements TransitionStateMachine.TransitionCondition { private final float threshold; public LowHealthCondition(float threshold) { this.threshold = threshold; } @Override public boolean isMet(Object context) { if (context instanceof Enemy) { Enemy enemy = (Enemy) context; return enemy.getHealthPercentage() < threshold; } return false; } } public static class TimerCondition implements TransitionStateMachine.TransitionCondition { private final float duration; private float elapsedTime; public TimerCondition(float duration) { this.duration = duration; this.elapsedTime = 0; } @Override public boolean isMet(Object context) { // This would need to be updated each frame // Implementation would track time in the state machine update return elapsedTime >= duration; } public void update(float deltaTime) { elapsedTime += deltaTime; } public void reset() { elapsedTime = 0; } } public static class AndCondition implements TransitionStateMachine.TransitionCondition { private final TransitionStateMachine.TransitionCondition[] conditions; public AndCondition(TransitionStateMachine.TransitionCondition... conditions) { this.conditions = conditions; } @Override public boolean isMet(Object context) { for (TransitionStateMachine.TransitionCondition condition : conditions) { if (!condition.isMet(context)) { return false; } } return true; } } public static class OrCondition implements TransitionStateMachine.TransitionCondition { private final TransitionStateMachine.TransitionCondition[] conditions; public OrCondition(TransitionStateMachine.TransitionCondition... conditions) { this.conditions = conditions; } @Override public boolean isMet(Object context) { for (TransitionStateMachine.TransitionCondition condition : conditions) { if (condition.isMet(context)) { return true; } } return false; } } } Game State Management
1. Game-Wide State Machine
package com.game.fsm.gamestates; import com.game.fsm.State; import com.game.fsm.GenericStateMachine; public class GameStateManager { private final GenericStateMachine stateMachine; public GameStateManager() { this.stateMachine = new GenericStateMachine(); initializeGameStates(); } private void initializeGameStates() { stateMachine.addState(new MainMenuState()); stateMachine.addState(new LoadingState()); stateMachine.addState(new PlayingState()); stateMachine.addState(new PausedState()); stateMachine.addState(new GameOverState()); stateMachine.addState(new VictoryState()); stateMachine.setInitialState("MAIN_MENU"); } public void update(float deltaTime) { stateMachine.update(deltaTime); } public void render() { // Delegate rendering to current state GameState currentState = (GameState) stateMachine.getCurrentState(); currentState.render(); } public void handleInput(int keyCode, boolean pressed) { GameState currentState = (GameState) stateMachine.getCurrentState(); currentState.handleInput(keyCode, pressed); } // Game States public abstract class GameState implements State { public abstract void render(); public abstract void handleInput(int keyCode, boolean pressed); } private class MainMenuState extends GameState { @Override public void enter() { System.out.println("Entering Main Menu"); // Initialize menu UI } @Override public void update(float deltaTime) { // Update menu animations, etc. } @Override public void exit() { // Clean up menu resources } @Override public void render() { // Render menu UI } @Override public void handleInput(int keyCode, boolean pressed) { if (pressed && keyCode == java.awt.event.KeyEvent.VK_ENTER) { stateMachine.changeState("LOADING"); } } @Override public String getName() { return "MAIN_MENU"; } } private class LoadingState extends GameState { private float loadTime = 0; @Override public void enter() { System.out.println("Loading game resources..."); loadTime = 0; // Start loading assets } @Override public void update(float deltaTime) { loadTime += deltaTime; // Simulate loading completion if (loadTime >= 2.0f) { // 2 seconds loading time stateMachine.changeState("PLAYING"); } } @Override public void exit() { // Finalize loading } @Override public void render() { // Render loading screen with progress } @Override public void handleInput(int keyCode, boolean pressed) { // Loading state typically doesn't handle input } @Override public String getName() { return "LOADING"; } } private class PlayingState extends GameState { @Override public void enter() { System.out.println("Game started!"); // Initialize game world } @Override public void update(float deltaTime) { // Update game logic // Update entities, physics, etc. } @Override public void exit() { // Pause game world } @Override public void render() { // Render game world } @Override public void handleInput(int keyCode, boolean pressed) { if (pressed && keyCode == java.awt.event.KeyEvent.VK_ESCAPE) { stateMachine.changeState("PAUSED"); } if (pressed && keyCode == java.awt.event.KeyEvent.VK_G) { // Example: player died stateMachine.changeState("GAME_OVER"); } if (pressed && keyCode == java.awt.event.KeyEvent.VK_V) { // Example: player won stateMachine.changeState("VICTORY"); } } @Override public String getName() { return "PLAYING"; } } private class PausedState extends GameState { @Override public void enter() { System.out.println("Game paused"); // Show pause menu } @Override public void update(float deltaTime) { // Update pause menu animations } @Override public void exit() { // Hide pause menu } @Override public void render() { // Render pause menu over game world } @Override public void handleInput(int keyCode, boolean pressed) { if (pressed && keyCode == java.awt.event.KeyEvent.VK_ESCAPE) { stateMachine.changeState("PLAYING"); } if (pressed && keyCode == java.awt.event.KeyEvent.VK_M) { stateMachine.changeState("MAIN_MENU"); } } @Override public String getName() { return "PAUSED"; } } private class GameOverState extends GameState { @Override public void enter() { System.out.println("Game Over"); // Show game over screen } @Override public void update(float deltaTime) { // Update game over screen } @Override public void exit() { // Clean up } @Override public void render() { // Render game over screen } @Override public void handleInput(int keyCode, boolean pressed) { if (pressed && keyCode == java.awt.event.KeyEvent.VK_ENTER) { stateMachine.changeState("MAIN_MENU"); } if (pressed && keyCode == java.awt.event.KeyEvent.VK_R) { stateMachine.changeState("PLAYING"); // Restart } } @Override public String getName() { return "GAME_OVER"; } } private class VictoryState extends GameState { @Override public void enter() { System.out.println("Victory!"); // Show victory screen } @Override public void update(float deltaTime) { // Update victory screen } @Override public void exit() { // Clean up } @Override public void render() { // Render victory screen } @Override public void handleInput(int keyCode, boolean pressed) { if (pressed && keyCode == java.awt.event.KeyEvent.VK_ENTER) { stateMachine.changeState("MAIN_MENU"); } } @Override public String getName() { return "VICTORY"; } } } Hierarchical State Machines
1. Hierarchical FSM Implementation
package com.game.fsm.hierarchical; import java.util.*; public class HierarchicalStateMachine implements State { private final Map<String, State> states; private State currentState; private State parentState; private final String name; public HierarchicalStateMachine(String name) { this.name = name; this.states = new HashMap<>(); this.currentState = null; this.parentState = null; } public void addState(State state) { states.put(state.getName(), state); } public void setInitialState(String stateName) { State initialState = states.get(stateName); if (initialState != null) { currentState = initialState; } } public void setParentState(State parent) { this.parentState = parent; } @Override public void enter() { if (currentState != null) { currentState.enter(); } } @Override public void update(float deltaTime) { if (currentState != null) { currentState.update(deltaTime); } } @Override public void exit() { if (currentState != null) { currentState.exit(); } } public void changeState(String stateName) { State nextState = states.get(stateName); if (nextState == null) return; if (currentState != null) { currentState.exit(); } currentState = nextState; currentState.enter(); } @Override public String getName() { return name; } public State getCurrentState() { return currentState; } // Example usage for character with locomotion and combat hierarchies public static class CharacterHSM { private final HierarchicalStateMachine rootFSM; private final HierarchicalStateMachine locomotionFSM; private final HierarchicalStateMachine combatFSM; public CharacterHSM() { // Root FSM rootFSM = new HierarchicalStateMachine("ROOT"); // Locomotion hierarchy locomotionFSM = new HierarchicalStateMachine("LOCOMOTION"); locomotionFSM.addState(new IdleState()); locomotionFSM.addState(new WalkingState()); locomotionFSM.addState(new RunningState()); locomotionFSM.setInitialState("IDLE"); // Combat hierarchy combatFSM = new HierarchicalStateMachine("COMBAT"); combatFSM.addState(new PeacefulState()); combatFSM.addState(new CombatState()); combatFSM.setInitialState("PEACEFUL"); // Add sub-machines to root rootFSM.addState(locomotionFSM); rootFSM.addState(combatFSM); rootFSM.setInitialState("LOCOMOTION"); } public void update(float deltaTime) { rootFSM.update(deltaTime); } // State implementations for locomotion private class IdleState implements State { @Override public void enter() { System.out.println("Idle"); } @Override public void update(float deltaTime) { } @Override public void exit() { } @Override public String getName() { return "IDLE"; } } private class WalkingState implements State { @Override public void enter() { System.out.println("Walking"); } @Override public void update(float deltaTime) { } @Override public void exit() { } @Override public String getName() { return "WALKING"; } } // State implementations for combat private class PeacefulState implements State { @Override public void enter() { System.out.println("Peaceful"); } @Override public void update(float deltaTime) { } @Override public void exit() { } @Override public String getName() { return "PEACEFUL"; } } private class CombatState implements State { @Override public void enter() { System.out.println("Combat"); } @Override public void update(float deltaTime) { } @Override public void exit() { } @Override public String getName() { return "COMBAT"; } } } } FSM with Event System
1. Event-Driven State Machine
package com.game.fsm.events; import java.util.*; public class EventStateMachine implements State { private final Map<String, State> states; private final Map<String, Map<Class<?>, String>> eventTransitions; private State currentState; private final String name; public EventStateMachine(String name) { this.name = name; this.states = new HashMap<>(); this.eventTransitions = new HashMap<>(); this.currentState = null; } public void addState(State state) { states.put(state.getName(), state); eventTransitions.put(state.getName(), new HashMap<>()); } public void setInitialState(String stateName) { currentState = states.get(stateName); if (currentState != null) { currentState.enter(); } } public void addEventTransition(String fromState, Class<?> eventClass, String toState) { Map<Class<?>, String> transitions = eventTransitions.get(fromState); if (transitions != null) { transitions.put(eventClass, toState); } } public void sendEvent(Object event) { if (currentState == null) return; Map<Class<?>, String> transitions = eventTransitions.get(currentState.getName()); if (transitions != null) { String toState = transitions.get(event.getClass()); if (toState != null) { changeState(toState); } } } private void changeState(String stateName) { State nextState = states.get(stateName); if (nextState == null) return; if (currentState != null) { currentState.exit(); } currentState = nextState; currentState.enter(); } @Override public void enter() { if (currentState != null) { currentState.enter(); } } @Override public void update(float deltaTime) { if (currentState != null) { currentState.update(deltaTime); } } @Override public void exit() { if (currentState != null) { currentState.exit(); } } @Override public String getName() { return name; } // Event classes public static class DamageEvent { public final float amount; public final Object source; public DamageEvent(float amount, Object source) { this.amount = amount; this.source = source; } } public static class HealEvent { public final float amount; public HealEvent(float amount) { this.amount = amount; } } public static class PlayerSpottedEvent { public final Object player; public PlayerSpottedEvent(Object player) { this.player = player; } } public static class PlayerLostEvent { public PlayerLostEvent() {} } } Performance and Debugging
1. FSM Debugger and Profiler
package com.game.fsm.debug; import com.game.fsm.State; import com.game.fsm.StateMachine; import java.util.*; public class FSMDebugger { private final StateMachine stateMachine; private final Map<String, StateStats> stateStats; private long startTime; public FSMDebugger(StateMachine stateMachine) { this.stateMachine = stateMachine; this.stateStats = new HashMap<>(); this.startTime = System.currentTimeMillis(); } public void onStateChange(State oldState, State newState) { if (oldState != null) { StateStats stats = stateStats.get(oldState.getName()); if (stats != null) { stats.recordExit(); } } if (newState != null) { StateStats stats = stateStats.computeIfAbsent(newState.getName(), k -> new StateStats()); stats.recordEnter(); } logStateChange(oldState, newState); } private void logStateChange(State oldState, State newState) { String oldName = oldState != null ? oldState.getName() : "null"; String newName = newState != null ? newState.getName() : "null"; System.out.printf("[FSM] %s -> %s%n", oldName, newName); } public void printStatistics() { long totalTime = System.currentTimeMillis() - startTime; System.out.println("=== FSM Statistics ==="); System.out.printf("Total running time: %.2f seconds%n", totalTime / 1000.0); for (Map.Entry<String, StateStats> entry : stateStats.entrySet()) { StateStats stats = entry.getValue(); double percentage = (stats.totalTime * 100.0) / totalTime; System.out.printf("State: %s%n", entry.getKey()); System.out.printf(" Entries: %d%n", stats.entryCount); System.out.printf(" Total time: %.2fs (%.1f%%)%n", stats.totalTime / 1000.0, percentage); System.out.printf(" Average time: %.2fs%n", stats.getAverageTime() / 1000.0); } } private static class StateStats { public int entryCount = 0; public long totalTime = 0; public long lastEntryTime = 0; public void recordEnter() { entryCount++; lastEntryTime = System.currentTimeMillis(); } public void recordExit() { if (lastEntryTime > 0) { totalTime += System.currentTimeMillis() - lastEntryTime; lastEntryTime = 0; } } public double getAverageTime() { return entryCount > 0 ? (double) totalTime / entryCount : 0; } } } Conclusion
Finite State Machines are essential for game development:
Key Benefits:
- Organized logic - Clean separation of behaviors
- Predictable behavior - Well-defined state transitions
- Maintainability - Easy to modify and extend
- Debugging - Clear state flow for troubleshooting
Common Game Applications:
- Character AI - Enemy behaviors, NPC routines
- Game flow - Menu navigation, level progression
- Animation states - Character movement and actions
- UI systems - Menu navigation, dialog flows
- Game objects - Doors, switches, interactive elements
Best Practices:
- Keep states focused and single-purpose
- Use hierarchical FSMs for complex behaviors
- Implement event-driven transitions for flexibility
- Add debugging and profiling capabilities
- Consider using state machine visualizers for complex systems
FSMs provide a robust foundation for managing game logic that scales from simple character behaviors to complex game-wide state management.