Spaces:
Running on Zero
Running on Zero
File size: 3,380 Bytes
c670567 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | /**
* StateMachine - A simple, reusable finite state machine
*
* This is a core utility that can be used by any game type.
* For player-specific FSM with animation support, see PlayerFSM in modules.
*
* Usage:
* const fsm = new StateMachine(owner, 'idle');
* fsm.addState('idle', {
* onEnter: () => console.log('entering idle'),
* onUpdate: () => { ... },
* onExit: () => console.log('exiting idle'),
* });
* fsm.addState('moving', { ... });
* fsm.setState('moving');
* fsm.update(); // call every frame
*/
export interface IStateConfig {
onEnter?: () => void;
onUpdate?: () => void;
onExit?: () => void;
}
export class StateMachine {
private owner: any;
private states: Map<string, IStateConfig> = new Map();
private currentStateName: string | null = null;
private currentState: IStateConfig | null = null;
private isChangingState: boolean = false;
/**
* Create a new StateMachine
* @param owner - The object that owns this state machine
* @param initialState - Optional initial state name (will call onEnter if state exists)
*/
constructor(owner: any, initialState?: string) {
this.owner = owner;
if (initialState) {
this.currentStateName = initialState;
}
}
/**
* Add a state to the state machine
* @param name - Unique state name
* @param config - State configuration with onEnter, onUpdate, onExit callbacks
*/
addState(name: string, config: IStateConfig): this {
this.states.set(name, config);
// If this is the initial state and we haven't entered yet, enter it now
if (this.currentStateName === name && !this.currentState) {
this.currentState = config;
config.onEnter?.call(this.owner);
}
return this;
}
/**
* Transition to a new state
* @param name - Name of the state to transition to
* @returns true if transition was successful
*/
setState(name: string): boolean {
// Prevent recursive state changes
if (this.isChangingState) {
console.warn(
`StateMachine: Cannot change state while already changing state`,
);
return false;
}
// Check if state exists
const newState = this.states.get(name);
if (!newState) {
console.warn(`StateMachine: State '${name}' does not exist`);
return false;
}
// Skip if already in this state
if (this.currentStateName === name) {
return false;
}
this.isChangingState = true;
// Exit current state
if (this.currentState) {
this.currentState.onExit?.call(this.owner);
}
// Update state references
this.currentStateName = name;
this.currentState = newState;
// Enter new state
this.currentState.onEnter?.call(this.owner);
this.isChangingState = false;
return true;
}
/**
* Update the current state (call every frame)
*/
update(): void {
if (this.currentState && !this.isChangingState) {
this.currentState.onUpdate?.call(this.owner);
}
}
/**
* Get the current state name
*/
get state(): string | null {
return this.currentStateName;
}
/**
* Check if currently in a specific state
*/
isState(name: string): boolean {
return this.currentStateName === name;
}
/**
* Check if a state exists
*/
hasState(name: string): boolean {
return this.states.has(name);
}
}
|