gibbon.js
Version:
Actor/Component system for use with pixi.js.
184 lines • 5.04 kB
JavaScript
import { Component } from './component';
import { State } from '../data/state';
export var StateEvent;
(function (StateEvent) {
StateEvent["enter"] = "enterState";
StateEvent["exit"] = "exitState";
})(StateEvent || (StateEvent = {}));
/**
* Basis State Machine for adding/removing components on state changes.
*/
export class FSM extends Component {
_startState;
_states = new Map();
get current() { return this._current; }
_current;
/**
* State being transitioned to.
*/
_changeState = null;
/**
* Current transition for timed transitions.
*/
curTransition = null;
/**
* Timer on current transition.
*/
transTimer = 0;
constructor(start) {
super();
this._startState = start instanceof State ? start : new State(start);
this.addState(this._startState);
this._current = this._startState;
}
init() {
this.enterState(this._current);
}
/**
* Trigger transition on current state.
* @param trigger
* @returns new State or false on error.
*/
trigger(trigger) {
const next = this._current.getNextState(trigger);
if (next) {
return this.switchState(next);
}
return false;
}
/**
* Switch to new state, triggering exit and enter transitions
* from current and next states respectively.
* @param newState
* @throws Error if state change already in progress, or attempting to change state
* on an FSM not initialized with an Actor.
* @returns new State<TKey> or false on failure.
*/
switchState(stateName) {
if (stateName === this._current.name) {
// No state change.
return false;
}
else if (!this.actor) {
throw new Error(`Attempting to change state with no actor: ${stateName}`);
}
const newState = this.getState(stateName);
if (!newState) {
return false;
}
if (this._changeState) {
if (newState.priority <= this._changeState.priority) {
return;
}
}
this.curTransition = null;
if (newState) {
this._current.onExit?.apply(this.actor);
this.actor?.emit(StateEvent.exit, this._current);
this.enterState(newState);
}
this._changeState = null;
return newState ?? false;
}
enterState(newState) {
this._current = newState;
newState.onEnter?.apply(this.actor);
this.actor.emit(StateEvent.enter, newState);
if (newState.autoNext) {
this.curTransition = newState.autoNext;
this.transTimer = newState.autoNext.enterTime;
}
}
update(delta) {
if (this.curTransition) {
this.transTimer -= delta;
if (this.transTimer <= 0) {
this.switchState(this.curTransition.dest);
}
}
}
getState(name) {
return this._states.get(name);
}
/**
* Returns true if the current state responds
* to trigger.
* @param trigger
*/
canTrigger(trigger) {
return this.current.canTrigger(trigger);
}
/**
* Set current state without triggering transitions.
* @param name
*/
setState(name) {
const state = this._states.get(name);
if (state) {
this.enterState(state);
}
}
/**
* Create and return new state of FSM.
* @param name
* @returns
*/
createState(name, opts) {
if (this._states.has(name)) {
return false;
}
else {
const st = new State(name, opts);
this._states.set(name, st);
return st;
}
}
/**
* Set FSM Start State.
* @param state
*/
setStart(state) {
if (state instanceof State) {
this.addState(state);
this._startState = state;
}
else {
const st = this._states.get(state);
if (st) {
this._startState = st;
}
}
}
addState(state) {
this._states.set(state.name, state);
}
/**
* Add a component to enable when entering a state.
* @param state
* @param enable
*/
addStateEnable(state, enable) {
const st = this._states.get(state);
if (st) {
st.addEnterEnable(enable);
}
else {
console.warn(`FSm.addStateEnable() unexpected missing state: ${state}`);
}
}
/**
* Add component to disable when entering a state.
* @param state
* @param disable
*/
addStateDisable(state, disable) {
const st = this._states.get(state);
if (st) {
st.addEnterDisable(disable);
}
else {
console.warn(`FSm.addStateDisable() unexpected missing state: ${state}`);
}
}
}
//# sourceMappingURL=fsm.js.map