GFramework/docs/zh-CN/core/state-machine.md
GeWuYou 25f7779b4e refactor(docs): 将 Context 属性访问替换为扩展方法访问
- 将 Context.GetModel<T>() 调用替换为 this.GetModel<T>()
- 将 Context.GetSystem<T>() 调用替换为 this.GetSystem<T>()
- 将 Context.GetUtility<T>() 调用替换为 this.GetUtility<T>()
- 将 Context.SendCommand() 调用替换为 this.SendCommand()
- 将 Context.SendQuery() 调用替换为 this.SendQuery()
- 将 Context.SendEvent() 调用替换为 this.SendEvent()
- 将 Context.RegisterEvent<T>() 调用替换为 this.RegisterEvent<T>()
2026-03-08 11:37:31 +08:00

14 KiB
Raw Blame History

title, description
title description
状态机系统 状态机系统提供了灵活的状态管理机制,支持状态转换、历史记录和异步操作。

状态机系统

概述

状态机系统是 GFramework 中用于管理游戏状态的核心组件。通过状态机,你可以清晰地定义游戏的各种状态(如菜单、游戏中、暂停、游戏结束等),以及状态之间的转换规则,使游戏逻辑更加结构化和易于维护。

状态机系统支持同步和异步状态操作,提供状态历史记录,并与架构系统深度集成,让你可以在状态中访问所有架构组件。

主要特性

  • 类型安全的状态管理
  • 支持同步和异步状态
  • 状态转换验证
  • 状态历史记录和回退
  • 与架构系统集成
  • 线程安全操作

核心概念

状态接口

IState 定义了状态的基本行为:

public interface IState
{
    void OnEnter(IState? from);      // 进入状态
    void OnExit(IState? to);         // 退出状态
    bool CanTransitionTo(IState target); // 转换验证
}

状态机

IStateMachine 管理状态的注册和切换:

public interface IStateMachine
{
    IState? Current { get; }                    // 当前状态
    IStateMachine Register(IState state);       // 注册状态
    Task<bool> ChangeToAsync<T>() where T : IState; // 切换状态
}

状态机系统

IStateMachineSystem 结合了状态机和系统的能力:

public interface IStateMachineSystem : ISystem, IStateMachine
{
    // 继承 ISystem 和 IStateMachine 的所有功能
}

基本用法

定义状态

继承 ContextAwareStateBase 创建状态:

using GFramework.Core.state;

// 菜单状态
public class MenuState : ContextAwareStateBase
{
    public override void OnEnter(IState? from)
    {
        Console.WriteLine("进入菜单");
        // 显示菜单 UI
    }

    public override void OnExit(IState? to)
    {
        Console.WriteLine("退出菜单");
        // 隐藏菜单 UI
    }
}

// 游戏状态
public class GameplayState : ContextAwareStateBase
{
    public override void OnEnter(IState? from)
    {
        Console.WriteLine("开始游戏");
        // 初始化游戏场景
    }

    public override void OnExit(IState? to)
    {
        Console.WriteLine("结束游戏");
        // 清理游戏场景
    }
}

注册和使用状态机

using GFramework.Core.state;

public class GameArchitecture : Architecture
{
    protected override void Init()
    {
        // 创建状态机系统
        var stateMachine = new StateMachineSystem();

        // 注册状态
        stateMachine
            .Register(new MenuState())
            .Register(new GameplayState())
            .Register(new PauseState());

        // 注册到架构
        RegisterSystem<IStateMachineSystem>(stateMachine);
    }
}

切换状态

using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;

[ContextAware]
public partial class GameController : IController
{
    public async Task StartGame()
    {
        var stateMachine = this.GetSystem<IStateMachineSystem>();

        // 切换到游戏状态
        var success = await stateMachine.ChangeToAsync<GameplayState>();

        if (success)
        {
            Console.WriteLine("成功进入游戏状态");
        }
    }
}

高级用法

状态转换验证

控制状态之间的转换规则:

public class GameplayState : ContextAwareStateBase
{
    public override bool CanTransitionTo(IState target)
    {
        // 只能从游戏状态转换到暂停或游戏结束状态
        return target is PauseState or GameOverState;
    }

    public override void OnEnter(IState? from)
    {
        Console.WriteLine($"从 {from?.GetType().Name ?? "初始"} 进入游戏");
    }
}

public class PauseState : ContextAwareStateBase
{
    public override bool CanTransitionTo(IState target)
    {
        // 暂停状态只能返回游戏状态
        return target is GameplayState;
    }
}

异步状态

处理需要异步操作的状态:

using GFramework.Core.Abstractions.state;

public class LoadingState : AsyncContextAwareStateBase
{
    public override async Task OnEnterAsync(IState? from)
    {
        Console.WriteLine("开始加载...");

        // 异步加载资源
        await LoadResourcesAsync();

        Console.WriteLine("加载完成");

        // 自动切换到下一个状态
        var stateMachine = this.GetSystem<IStateMachineSystem>();
        await stateMachine.ChangeToAsync<GameplayState>();
    }

    private async Task LoadResourcesAsync()
    {
        // 模拟异步加载
        await Task.Delay(2000);
    }

    public override async Task OnExitAsync(IState? to)
    {
        Console.WriteLine("退出加载状态");
        await Task.CompletedTask;
    }
}

状态历史和回退

using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;

[ContextAware]
public partial class GameController : IController
{
    public async Task NavigateBack()
    {
        var stateMachine = this.GetSystem<IStateMachineSystem>();

        // 回退到上一个状态
        var success = await stateMachine.GoBackAsync();

        if (success)
        {
            Console.WriteLine("已返回上一个状态");
        }
    }

    public void ShowHistory()
    {
        var stateMachine = this.GetSystem<IStateMachineSystem>();

        // 获取状态历史
        var history = stateMachine.GetStateHistory();

        Console.WriteLine("状态历史:");
        foreach (var state in history)
        {
            Console.WriteLine($"- {state.GetType().Name}");
        }
    }
}

在状态中访问架构组件

public class GameplayState : ContextAwareStateBase
{
    public override void OnEnter(IState? from)
    {
        // 访问 Model
        var playerModel = this.GetModel<PlayerModel>();
        playerModel.Reset();

        // 访问 System
        var audioSystem = this.GetSystem<AudioSystem>();
        audioSystem.PlayBGM("gameplay");

        // 发送事件
        this.SendEvent(new GameStartedEvent());
    }

    public override void OnExit(IState? to)
    {
        // 停止音乐
        var audioSystem = this.GetSystem<AudioSystem>();
        audioSystem.StopBGM();

        // 发送事件
        this.SendEvent(new GameEndedEvent());
    }
}

状态数据传递

// 定义带数据的状态
public class GameplayState : ContextAwareStateBase
{
    public int Level { get; set; }
    public string Difficulty { get; set; } = "Normal";

    public override void OnEnter(IState? from)
    {
        Console.WriteLine($"开始关卡 {Level},难度: {Difficulty}");
    }
}

// 切换状态并设置数据
public async Task StartLevel(int level, string difficulty)
{
    var stateMachine = this.GetSystem<IStateMachineSystem>();

    // 获取状态实例并设置数据
    var gameplayState = stateMachine.GetState<GameplayState>();
    if (gameplayState != null)
    {
        gameplayState.Level = level;
        gameplayState.Difficulty = difficulty;
    }

    // 切换状态
    await stateMachine.ChangeToAsync<GameplayState>();
}

状态事件通知

// 定义状态变更事件
public class StateChangedEvent
{
    public IState? From { get; set; }
    public IState To { get; set; }
}

// 自定义状态机系统
public class CustomStateMachineSystem : StateMachineSystem
{
    protected override async Task OnStateChangedAsync(IState? from, IState to)
    {
        // 发送状态变更事件
        this.SendEvent(new StateChangedEvent
        {
            From = from,
            To = to
        });

        await base.OnStateChangedAsync(from, to);
    }
}

条件状态转换

public class BattleState : ContextAwareStateBase
{
    public override bool CanTransitionTo(IState target)
    {
        // 战斗中不能直接退出,必须先结束战斗
        if (target is MenuState)
        {
            var battleModel = this.GetModel<BattleModel>();
            return battleModel.IsBattleEnded;
        }

        return true;
    }
}

// 尝试切换状态
public async Task TryExitBattle()
{
    var stateMachine = this.GetSystem<IStateMachineSystem>();

    // 检查是否可以切换
    var canChange = await stateMachine.CanChangeToAsync<MenuState>();

    if (canChange)
    {
        await stateMachine.ChangeToAsync<MenuState>();
    }
    else
    {
        Console.WriteLine("战斗尚未结束,无法退出");
    }
}

最佳实践

  1. 使用基类创建状态:继承 ContextAwareStateBaseAsyncContextAwareStateBase

     public class MyState : ContextAwareStateBase { }
     public class MyState : IState { } // 需要手动实现所有接口
    
  2. 在 OnEnter 中初始化,在 OnExit 中清理:保持状态的独立性

    public override void OnEnter(IState? from)
    {
        // 初始化状态相关资源
        LoadUI();
        StartBackgroundMusic();
    }
    
    public override void OnExit(IState? to)
    {
        // 清理状态相关资源
        UnloadUI();
        StopBackgroundMusic();
    }
    
  3. 使用转换验证控制状态流:避免非法状态转换

    public override bool CanTransitionTo(IState target)
    {
        // 定义明确的转换规则
        return target is AllowedState1 or AllowedState2;
    }
    
  4. 异步操作使用异步状态:避免阻塞主线程

     public class LoadingState : AsyncContextAwareStateBase
    {
        public override async Task OnEnterAsync(IState? from)
        {
            await LoadDataAsync();
        }
    }
    
     public class LoadingState : ContextAwareStateBase
    {
        public override void OnEnter(IState? from)
        {
            LoadDataAsync().Wait(); // 阻塞主线程
        }
    }
    
  5. 合理使用状态历史:避免历史记录过大

    // 创建状态机时设置历史大小
    var stateMachine = new StateMachineSystem(maxHistorySize: 10);
    
  6. 状态保持单一职责:每个状态只负责一个场景或功能

     MenuState, GameplayState, PauseState // 职责清晰
     GameState // 职责不明确,包含太多逻辑
    

常见问题

问题:状态切换失败怎么办?

解答 ChangeToAsync 返回 false 表示切换失败,通常是因为 CanTransitionTo 返回 false

var success = await stateMachine.ChangeToAsync<TargetState>();
if (!success)
{
    Console.WriteLine("状态切换被拒绝");
    // 检查转换规则
}

问题:如何在状态之间传递数据?

解答 有几种方式:

  1. 通过状态属性
var state = stateMachine.GetState<GameplayState>();
state.Level = 5;
await stateMachine.ChangeToAsync<GameplayState>();
  1. 通过 Model
// 在切换前设置 Model
var gameModel = this.GetModel<GameModel>();
gameModel.CurrentLevel = 5;

// 在状态中读取
public override void OnEnter(IState? from)
{
    var gameModel = this.GetModel<GameModel>();
    var level = gameModel.CurrentLevel;
}
  1. 通过事件
this.SendEvent(new LevelSelectedEvent { Level = 5 });
await stateMachine.ChangeToAsync<GameplayState>();

问题:状态机系统和普通状态机有什么区别?

解答

  • StateMachine:纯状态机,不依赖架构
  • StateMachineSystem:集成到架构中,状态可以访问所有架构组件
// 使用 StateMachineSystem推荐
RegisterSystem<IStateMachineSystem>(new StateMachineSystem());

// 使用 StateMachine独立使用
var stateMachine = new StateMachine();

问题:如何处理状态切换动画?

解答OnExitOnEnter 中使用协程:

public class MenuState : AsyncContextAwareStateBase
{
    public override async Task OnExitAsync(IState? to)
    {
        // 播放淡出动画
        await PlayFadeOutAnimation();
    }
}

public class GameplayState : AsyncContextAwareStateBase
{
    public override async Task OnEnterAsync(IState? from)
    {
        // 播放淡入动画
        await PlayFadeInAnimation();
    }
}

问题:可以在状态中切换到其他状态吗?

解答 可以,但要注意避免递归切换:

public override async void OnEnter(IState? from)
{
    // 检查条件后自动切换
    if (ShouldSkip())
    {
        var stateMachine = this.GetSystem<IStateMachineSystem>();
        await stateMachine.ChangeToAsync<NextState>();
    }
}

问题:状态机是线程安全的吗?

解答 是的,状态机的所有操作都是线程安全的,使用了内部锁机制。

问题:如何实现状态栈(多层状态)?

解答 使用状态历史功能:

// 进入子状态
await stateMachine.ChangeToAsync<SubMenuState>();

// 返回上一层
await stateMachine.GoBackAsync();

相关文档