基于TypeScript的丐版分层状态机
主要用于小游戏开发,能应付简单的逻辑控制。本身支持多层嵌套,但考虑到小游戏体量,一般能用到两层足以
状态接口与状态机设计
设计思路参考开源框架Unity3d-Finite-State-Machine,简化其逻辑
1. 定义出状态实例的接口
Enter表示进入状态,Exit表示退出状态。Update和LateUpdate执行本状态的逻辑,对应Unity脚本的Update和LateUpdate函数。
1 2 3 4 5 6 7 8 9 10
| export interface Status<T> { Enter(owner: T, ...param: any): void; Update(owner?: T): void; LateUpdate(owner?: T): void; Exit(owner: T): void; }
|
2.1 定义一个简单的有限状态机
一个简单的有限状态机,仅需维护两个状态实例,上一状态和当前状态。然后实现状态切换的函数即可。
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
| export class Machine<T> { protected readonly owner: T; private _current: Status<T>; private _previous: Status<T>; get current(): Status<T> { return this._current } get previous(): Status<T> { return this._previous }
constructor(owner?: T, state?: Status<T>) { this.owner = owner if (state) { this._current = state } }
protected SetState(state?: Status<T>) { if (this._current) { if (this._current == state) return this._previous = this._current } this._current = state }
protected ChangeState(state?: Status<T>, ...param: any) { if (this._current) { if (this._current == state) return this._previous = this._current this._current.Exit(this.owner) } this._current = state if (this._current) { this._current.Enter(this.owner, ...param) } }
RevertState() { this.ChangeState(this._previous) } }
|
2.2实现分层状态机
现在,将其改造为分层状态机,使其实现Status接口。则Machine类就既可以作为状态机管理子状态,也可以作为一个状态实例,被其它状态机管理。
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
| export class Machine<T> implements Status<T> { Enter(owner: T, ...param: any): void { if (this._current) { this._current.Enter(this.owner, ...param) } }
Update(owner?: T): void { if (this._current) { this._current.Update(this.owner) } }
LateUpdate(owner?: T): void { if (this._current) { this._current.LateUpdate(this.owner) } }
Exit(owner: T): void { if (this._current) { this._current.Exit(this.owner) this._previous = this._current this._current = null } } }
|
状态机的使用
通过以上代码,已经实现了一套基础的分层状态机。但是代码还很抽象,通过接下来的使用方式。才容易理解其逻辑。
使用情景例子:设计一个AI,其拥有Idle(静止)和Move(移动),两个状态。而其Move状态下,区分了两种移动方式,Walk和Run。
1.1 根据情景设计接口
AI需要四个状态,Idle、Move、Walk、Run。其中Idle和Move是一级状态,Walk和Run则是Move下的两个子状态
下面给出四个状态对应的接口
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
| export interface I {
onEnterIdle(...param: any): void onUpdateIdle(): void onLateUpdateIdle(): void onExitIdle(): void
onEnterMove(move: StateMove, ...param: any): void onUpdateMove(move: StateMove): void onLateUpdateMove(move: StateMove): void onExitMove(move: StateMove): void
onEnterMoveWalk(...param: any): void onUpdateMoveWalk(): void onLateUpdateMoveWalk(): void onExitMoveWalk(): void
onEnterMoveRun(...param: any): void onUpdateMoveRun(): void onLateUpdateMoveRun(): void onExitMoveRun(): void
}
|
1.2 创建对应的状态实例
首先是Idle状态,可创建为全局单例
1 2 3 4 5 6 7
| export class StateIdle implements Status<I> { static ins: StateIdle = new StateIdle Enter(owner: I, ...param: any): void { owner.onEnterIdle(...param) } Update(owner: I): void { owner.onUpdateIdle() } LateUpdate(owner: I): void { owner.onLateUpdateIdle() } Exit(owner: I): void { owner.onExitIdle() } }
|
然后是Move和其两个子状态。Move即是状态,又是含有子状态的状态机,无法作为全局单例。而Walk和Run与Idle相同
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
| export class StateMoveWalk implements Status<I> { static ins: StateMoveWalk = new StateMoveWalk Enter(owner: I, ...param: any): void { owner.onEnterMoveWalk(...param) } Update(owner: I): void { owner.onUpdateMoveWalk() } LateUpdate(owner: I): void { owner.onLateUpdateMoveWalk() } Exit(owner: I): void { owner.onExitMoveWalk() } }
export class StateMoveRun implements Status<I> { static ins: StateMoveRun = new StateMoveRun Enter(owner: I, ...param: any): void { owner.onEnterMoveRun(...param) } Update(owner: I): void { owner.onUpdateMoveRun() } LateUpdate(owner: I): void { owner.onLateUpdateMoveRun() } Exit(owner: I): void { owner.onExitMoveRun() } }
export class StateMove extends Machine<I> {
Enter(owner: I, ...param: any): void { owner.onEnterMove(this, ...param), super.Enter(owner, ...param) } Update(owner: I): void { owner.onUpdateMove(this), super.Update(owner) } LateUpdate(owner: I): void { owner.onLateUpdateMove(this), super.LateUpdate(owner) } Exit(owner: I): void { super.Exit(owner), owner.onExitMove(this) }
readonly state_walk: StateMoveWalk = StateMoveWalk.ins get isWalk(): boolean { return this.current == this.state_walk } get isPreWalk(): boolean { return this.previous == this.state_walk } goWalk(...param: any) { this.ChangeState(this.state_walk, ...param) } setWalk() { this.SetState(this.state_walk) }
readonly state_run: StateMoveRun = StateMoveRun.ins get isRun(): boolean { return this.current == this.state_run } get isPreRun(): boolean { return this.previous == this.state_run } goRun(...param: any) { this.ChangeState(this.state_run, ...param) } setRun() { this.SetState(this.state_run) }
}
|
1.3 创建对应的状态机
最后是实现管理Idle和Move的状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export class Machine extends Machine<I> {
readonly state_idle: StateIdle = StateIdle.ins get isIdle(): boolean { return this.current == this.state_idle } get isPreIdle(): boolean { return this.previous == this.state_idle } goIdle(...param: any) { this.ChangeState(this.state_idle, ...param) } setIdle() { this.SetState(this.state_idle) }
readonly state_move: StateMove = new StateMove(this.owner) get isMove(): boolean { return this.current == this.state_move } get isPreMove(): boolean { return this.previous == this.state_move } goMove(...param: any) { this.ChangeState(this.state_move, ...param) } setMove() { this.SetState(this.state_move) }
}
|
1.4 创建脚本,使用状态机
最后,在对应的脚本上,使用状态机即可
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
| export class AIScript extends Laya.Script3D implements I {
hfsm: Machine = new Machine(this)
onUpdate(): void { this.hfsm.Update() }
onLateUpdate(): void { this.hfsm.LateUpdate() }
onEnable() { if () { this.hfsm.goIdle() } else { this.hfsm.goMove() } }
onEnterIdle(...param: any): void {} onUpdateIdle(): void { if () { this.hfsm.goMove() } } onLateUpdateIdle(): void {} onExitIdle(): void {}
onEnterMove(move: StateMove, ...param: any): void { if () { move.setWalk() } else { move.setRun() } } onUpdateMove(move: StateMove): void { if () { this.hfsm.goIdle() } } onLateUpdateMove(move: StateMove): void {} onExitMove(move: StateMove): void {}
onEnterMoveWalk(...param: any): void {} onUpdateMoveWalk(): void { if () { this.hfsm.state_move.goRun() } } onLateUpdateMoveWalk(): void {} onExitMoveWalk(): void {}
onEnterMoveRun(...param: any): void {} onUpdateMoveRun(): void { if () { this.hfsm.state_move.goWalk() } } onLateUpdateMoveRun(): void {} onExitMoveRun(): void {}
}
|
通过以上示例,可以看到Idle和move,通过逻辑判断互相切换。而Move状态内部,也通过逻辑判断自行切换Walk与Run。
对于外部,只需关心AI当前是Idle还是Move。而移动的方式,封装进了Move内部。不干扰Idle和Move的切换
总结
分层状态机本身,主要是Status接口和Machine类两部分代码。
状态机的使用一节中的1.1~1.3的代码,都是根据实际情景生成的代码,且代码格式固定,只要知道需要哪些状态,可以直接用脚本生成。