BetterGenshinImpact/GameTask/Common/StateMachine/README.md
StateMachineBase<TState, TContext> 是一个通用的有限状态机基类,专为游戏自动化场景设计。
| 概念 | 说明 |
|---|---|
| State | 游戏界面的抽象(如 MainWorld、EventMenu) |
| Transition | 状态之间的有向边,定义从一个状态可能到达的下一个状态 |
| Handler | 状态处理器,执行当前状态的操作(如点击按钮),返回 StateHandlerResult |
| Detector | 状态检测器,根据截图判断当前是否处于某个状态 |
Handler 必须返回 StateHandlerResult,状态机根据返回值决定下一步行为:
| 返回值 | 语义 | 状态机行为 | 使用场景 |
|---|---|---|---|
Success | 操作成功 | 等待邻接状态转换 | 点击按钮后等待界面切换 |
Wait | 可预期的等待 | 继续循环,重新检测 | 动画播放中、已到达目标状态 |
Retry | 意外失败 | 重试计数+1,超限后异常 | 找不到按钮、OCR 识别失败 |
Fail | 无法恢复 | 立即抛出异常 | 严重错误需要停止 |
public enum MyState
{
Unknown,
MainWorld,
EventMenu,
BattleArena
}
public class MyTask : StateMachineBase<MyState, BvPage>
{
protected override ILogger Logger => _logger;
private readonly ILogger<MyTask> _logger;
}
RegisterStateHandlers(
(MyState.MainWorld, HandleMainWorld),
(MyState.EventMenu, HandleEventMenu)
);
RegisterUnknownStateHandler(HandleUnknown);
RegisterStateDetectors(
(MyState.MainWorld, DetectMainWorld),
(MyState.EventMenu, DetectEventMenu),
(MyState.BattleArena, DetectBattleArena)
);
RegisterStateTransitions(
(MyState.MainWorld, [MyState.EventMenu]),
(MyState.EventMenu, [MyState.BattleArena])
);
private async Task<StateHandlerResult> HandleMainWorld(BvPage page)
{
Simulation.SendInput.SimulateAction(GIActions.OpenTheEventsMenu);
await Delay(500, _ct);
return StateHandlerResult.Success; // 等待转换到 EventMenu
}
private async Task<StateHandlerResult> HandleEventMenu(BvPage page)
{
var button = page.GetByText("开始").FindAll().FirstOrDefault();
if (button == null)
{
return StateHandlerResult.Retry; // 找不到按钮,重试
}
button.Click();
await Delay(300, _ct);
return StateHandlerResult.Success;
}
private Task<StateHandlerResult> HandleBattleArena(BvPage page)
{
// 已到达目标状态
return Task.FromResult(StateHandlerResult.Wait);
}
private async Task<StateHandlerResult> HandleUnknown(BvPage page)
{
await new ReturnMainUiTask().Start(_ct);
return StateHandlerResult.Wait; // 尝试恢复后重新检测
}
private bool DetectMainWorld(ImageRegion ra)
{
return ra.Find(MainWorldAssets.Minimap).IsExist();
}
private bool DetectEventMenu(ImageRegion ra)
{
return ra.FindMulti(RecognitionObject.Ocr(100, 50, 200, 50))
.Any(t => t.Text.Contains("活动"));
}
public async Task Start(CancellationToken ct)
{
Initialize(ct, MyState.MainWorld);
using var page = Bv.Page();
await RunStateMachineUntil(page, MyState.BattleArena);
// 状态机退出后,已到达 BattleArena
await ExecuteBattle();
}
| 方法 | 说明 |
|---|---|
Initialize(ct, initialState) | 初始化状态机 |
RunStateMachineUntil(context, targetStates) | 运行直到达到目标状态 |
RunStateMachineUntil(context, targetState) | 运行直到达到单个目标状态 |
EnsureNextStateTransition(timeout) | 等待邻接状态转换(供特殊场景使用) |
| 方法 | 说明 |
|---|---|
RegisterStateHandler(state, handler) | 注册单个状态处理器 |
RegisterStateHandlers(...) | 批量注册状态处理器 |
RegisterUnknownStateHandler(handler) | 注册未知状态处理器 |
RegisterStateDetector(state, detector) | 注册单个状态检测器 |
RegisterStateDetectors(...) | 批量注册状态检测器 |
RegisterStateTransitions(...) | 注册状态转换关系 |
| 属性 | 默认值 | 说明 |
|---|---|---|
DefaultDetectionInterval | 300ms | 状态检测间隔 |
DefaultTransitionTimeout | 10000ms | 状态转换超时 |
DefaultMaxRetries | 3 | 最大重试次数 |
StateMachineLoopInterval | 200ms | 状态机循环间隔 |
┌─────────────────────────────────────────────────────┐
│ RunStateMachineUntil │
└─────────────────────────────────────────────────────┘
│
▼
┌──────────────────────┐
│ RefreshCurrentState │ ◄─────────────┐
│ (检测当前状态) │ │
└──────────────────────┘ │
│ │
▼ │
┌──────────────────────┐ │
│ 是否到达目标状态? │ │
└──────────────────────┘ │
│ │ │
Yes No │
│ │ │
▼ ▼ │
┌────────┐ ┌──────────────────────┐ │
│ 退出 │ │ 执行 Handler │ │
└────────┘ └──────────────────────┘ │
│ │
▼ │
┌─────────────────────────────┐ │
│ Handler 返回值 │ │
└─────────────────────────────┘ │
│ │ │ │ │
┌──────────────┤ │ │ │ │
│ │ │ │ │ │
▼ ▼ ▼ ▼ │ │
┌─────────┐ ┌─────┐ ┌───┐ ┌────┐│ │
│ Success │ │Wait │ │Rty│ │Fail││ │
└─────────┘ └─────┘ └───┘ └────┘│ │
│ │ │ │ │ │
▼ │ │ ▼ │ │
┌───────────────┐ │ │ 抛出 │ │
│EnsureNextState│ │ │ 异常 │ │
│ Transition │ │ │ │ │
└───────────────┘ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────┐ │ │
│ │ │重试计数│ │ │
│ │ │ +1 │ │ │
│ │ └────────┘ │ │
│ │ │ │ │
└──────────────┴────┴────────┴────────────┘
注册检测器时按速度排序(先快后慢):
RegisterStateTransitions 中候选状态的顺序影响检测优先级,更具体的状态应放在前面:
// 正确:更具体的状态放前面
(TeleportMap, [DomainEntrance, MainWorld])
// 错误:通用状态会被优先检测
(TeleportMap, [MainWorld, DomainEntrance])
完整示例见 GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs
状态图:
MainWorld ─► EventMenu ─► StygianOnslaughtPage ─► TeleportMap
│
▼
DomainLobby ◄── DifficultySelect ◄── DomainEntrance ◄─┘
│
▼
BossSelect ─► BattleArena ─► BattleResult ─► DomainLobby