0%

TypeScript 丐版分层状态机设计

基于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再执行子状态机的Enter */
Enter(owner: T, ...param: any): void;
/** 每帧调用,多层情况下先执行自身再执行子状态机的 */
Update(owner?: T): void;
/** 每帧调用,多层情况下先执行自身再执行子状态机的 */
LateUpdate(owner?: T): void;
/** 退出状态,多层情况下先执行子状态机的Exit再执行自身Exit */
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> {
/** owner 当前状态机的拥有者(使用者) */
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 }

/**
* 分层状态机
* @param owner 使用者
* @param state 初始状态
*/
constructor(owner?: T, state?: Status<T>) {
this.owner = owner
if (state) {
this._current = state
}
}

/**
* 切换为某一状态,不触发状态切换回调
* @param state 新状态
*/
protected SetState(state?: Status<T>) {
if (this._current) {
if (this._current == state) return
this._previous = this._current
}
this._current = state
}

/**
* 切换状态
* @param state 新状态
* @param param 传递的参数
*/
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
// 实现Status接口
export class Machine<T> implements Status<T> {
// ---skip---
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
}
}
// ---skip---
}

状态机的使用

通过以上代码,已经实现了一套基础的分层状态机。但是代码还很抽象,通过接下来的使用方式。才容易理解其逻辑。

使用情景例子:设计一个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 {

//#region /////////////////////// Idle ////////////////////////
onEnterIdle(...param: any): void
onUpdateIdle(): void
onLateUpdateIdle(): void
onExitIdle(): void
//#endregion //////////////////// Idle-END ////////////////////

//#region /////////////////////// Move ////////////////////////
onEnterMove(move: StateMove, ...param: any): void
onUpdateMove(move: StateMove): void
onLateUpdateMove(move: StateMove): void
onExitMove(move: StateMove): void
//#endregion //////////////////// Move-END ////////////////////

//#region /////////////////////// MoveWalk ////////////////////////
onEnterMoveWalk(...param: any): void
onUpdateMoveWalk(): void
onLateUpdateMoveWalk(): void
onExitMoveWalk(): void
//#endregion //////////////////// MoveWalk-END ////////////////////

//#region /////////////////////// MoveRun ////////////////////////
onEnterMoveRun(...param: any): void
onUpdateMoveRun(): void
onLateUpdateMoveRun(): void
onExitMoveRun(): void
//#endregion //////////////////// MoveRun-END ////////////////////

}

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) }


//#region /////////////////////// Walk ////////////////////////
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) }
//#endregion //////////////////// Walk-END ////////////////////

//#region /////////////////////// Run ////////////////////////
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) }
//#endregion //////////////////// Run-END ////////////////////

}

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> {

//#region /////////////////////// Idle ////////////////////////
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) }
//#endregion //////////////////// Idle-END ////////////////////

//#region /////////////////////// Move ////////////////////////
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) }
//#endregion //////////////////// Move-END ////////////////////

}

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 {

//#region /////////////////////// 创建使用状态的固定代码 ////////////////////////
hfsm: Machine = new Machine(this)

onUpdate(): void {
this.hfsm.Update()
}

onLateUpdate(): void {
this.hfsm.LateUpdate()
}
//#endregion //////////////////// 创建使用状态的固定代码-END ////////////////////


//#region /////////////////////// 使用示例 ////////////////////////
onEnable() {
if (/** 静止 */) {
this.hfsm.goIdle()
} else {
this.hfsm.goMove()
}
}
//#endregion //////////////////// 使用示例-END ////////////////////


//#region /////////////////////// 各个子状态的接口 ////////////////////////
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 {}
//#endregion //////////////////// 各个子状态的接口-END ////////////////////

}

通过以上示例,可以看到Idle和move,通过逻辑判断互相切换。而Move状态内部,也通过逻辑判断自行切换Walk与Run。
对于外部,只需关心AI当前是Idle还是Move。而移动的方式,封装进了Move内部。不干扰Idle和Move的切换

总结

分层状态机本身,主要是Status接口和Machine类两部分代码。

状态机的使用一节中的1.1~1.3的代码,都是根据实际情景生成的代码,且代码格式固定,只要知道需要哪些状态,可以直接用脚本生成。