😱 像多线程那样去轮询多个状态,不同的状态满足后去执行不同的定时任务。 项目地址

该状态机基本是为写游戏自动脚本量身定做,它就是整个脚本的”调度中心”。即使是基于 Node.js 的单线程,你也能够实现”同时”检测角色血条,掉落物品,游戏状态等等各种来触发不同的操作,搭配 dm.dll 食用更佳!如下图是本人之前做的流放之路脚本的主框架部分代码:


清晰明了,简洁优雅!👏

安装

1
npm install wow-state-machine

使用

推荐先阅读一下 用 JS 写游戏自动脚本是什么体验?

如下代码基本涵盖了本状态机所有的用例

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
// const { default: StateMachine } = require('state-machine')
import StateMachine from 'state-machine'

let num = 0
setInterval(() => {
// 模拟事件发生器 —— 随机生成数字 1-10
num = Math.ceil(Math.random() * 10)
}, 1000)

// 这是一个用于测试的始终返回 state1 的状态机
const otherStateMachine = new StateMachine(() => 'state1').on('state1', () =>
console.log('otherStateMachine', 'state1'),
)

const stateMachine = new StateMachine(() => {
// 模拟十分之一概率的错误
if (num === 1) throw Error('发生错误了')
// 模拟十分之二的概率返回 state1
else if (num < 4) return 'state1'
// 模拟十分之二的概率返回 state2
else if (num < 6) return 'state2'
// 模拟十分之二的概率返回 state3
else if (num < 8) return 'state3'
// 模拟十分之一的概率返回 state4
else if (num === 8) return 'state4'
// 模拟剩下十分之二的概率返回 unknown
else return 'unknown'
})
// 任意状态发生的时候都会触发 onTick
.onTick((state, lastState, isFirstTick) =>
console.log(state, lastState, isFirstTick),
)
// 当 state1 发生的时候,启动一个每 100 毫秒输出一次 'state1' 的定时任务(如果发生了其他事情,该定时任务会停止
.on('state1', () => console.log('state1'), 0, 100)
// 当 state2 发生的时候,启动一个每 200 毫秒(tick 默认 200)输出一次 'state2' 的定时任务。如果 state2 持续了超过 10 秒钟,则触发超时
.on('state2', () => console.log('state2'), 10 * 1000)
// 当 state3 发生的时候启动 otherStateMachine 状态机(如果没发生,该状态机会被自动停止
.on('state3', otherStateMachine, 0, 500)
// 当 state4 发生的时候,"阻塞"整个状态机(tick 被设置成了 -Infinity),直到 state4 的任务执行完毕后,状态机才会继续工作
.on(
'state4',
() => {
console.log('state4 开始了')
return new Promise((resolve) => {
setTimeout(() => resolve(console.log('state4 结束了')), 3 * 1000)
})
},
0,
-Infinity,
)
// 如果超过了 2 秒钟啥事情都没有发生(unknown 状态),则触发超时
.on('unknown', () => console.log('没有事情发生...'), 2 * 1000)
// 捕获状态机执行期间的所有超时,当超时发生时状态机会终止
.onTimeout((state) => console.log(state, '超时了'))
// 捕获状态机执行期间的所有异常,当异常发生时状态机会终止
.onError((e) => console.log(e))

// 启动状态机,让其每 500 毫秒检测一次状态(tick 默认 200)
stateMachine.start(500)
// stateMachine.stop() // 终止状态机

API

constructor

传入一个用于检测状态的函数(支持异步),确保任何情况都始终返回一个 string | number 的值来代表不同的状态。

onTick

任意状态发生的时候都会触发它。它的回调函数会收到如下三个参数:

  • state 当前的状态
  • lastState 上次的状态
  • isFirstTick 是否是状态机被启动后首次触发,一般你可以通过判断该变量来做一些初始化工作

on

监听具体的 state,启动相应的定时任务。该函数接受如下三个参数:

  • state 你要监听的状态
  • task 该状态发生后要启动的定时任务。它可以是同步/异步方法,也可以是一个状态机
  • timeout 该状态的超时时间(单位:毫秒,传 0 代表不做超时检测),超过该时间会触发状态机的onTimeout
  • tick 该状态发生后定时任务的轮询间隔。单位:毫秒,默认:200,传 -Infinity 代表该状态发生后会”阻塞”整个状态机,直到 task 执行完毕后,状态机才会继续工作(一般用于在某些特殊的状态发生后,做一些一次性耗时任务,而不是启动一个定时任务。

onTimeout

状态机超时的时候会触发它。它的回调函数会接收到当前超时的 state

超时后,状态机会停止工作。如果没有调用过该函数,状态机的超时的时候会抛出异常

onError

状态机发生的任何错误都会触发它。它的回调函数会接收到 Error

发生错误后,状态机会停止工作。如果没有调用过该函数,状态机的超时的时候会抛出异常

start

启动状态机。该函数接收一个用于设置状态机轮询间隔的 tick 参数。单位:毫秒,默认:200

stop

停止状态机

其他

对于大部分游戏自动脚本之类的场景,状态机内定时任务的“帧率”一般来说是足够用的了。

不过单线程毕竟是单线程,如果你在 tick 中写了大量的耗时任务,状态机就没法保障固定的调用间隔了,后面如果觉得有此必要可能会考虑支持多线程。


 评论