mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 19:03:29 +08:00
- 将所有小写的命名空间导入更正为首字母大写格式 - 统一 GFramework 框架的命名空间引用规范 - 修复 core、ecs、godot 等模块的命名空间导入错误 - 标准化文档示例代码中的 using 语句格式 - 确保所有文档中的命名空间引用保持一致性 - 更新 global using 语句以匹配正确的命名空间格式
20 KiB
20 KiB
title, description
| title | description |
|---|---|
| 实现状态机 | 学习如何使用状态机系统管理游戏状态和场景切换 |
实现状态机
学习目标
完成本教程后,你将能够:
- 理解状态机的概念和应用场景
- 创建自定义游戏状态
- 实现状态之间的转换和验证
- 使用异步状态处理加载操作
- 在状态中访问架构组件
- 实现完整的游戏流程控制
前置条件
步骤 1:定义游戏状态
首先,让我们为一个简单的游戏定义几个基本状态:主菜单、加载、游戏中、暂停和游戏结束。
using GFramework.Core.Abstractions.State;
using GFramework.Core.State;
namespace MyGame.States
{
/// <summary>
/// 主菜单状态
/// </summary>
public class MenuState : ContextAwareStateBase
{
public override void OnEnter(IState? from)
{
Console.WriteLine("=== 进入主菜单 ===");
// 显示菜单 UI
ShowMenuUI();
// 播放菜单音乐
PlayMenuMusic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("退出主菜单");
// 隐藏菜单 UI
HideMenuUI();
}
public override bool CanTransitionTo(IState target)
{
// 菜单只能切换到加载状态
return target is LoadingState;
}
private void ShowMenuUI()
{
Console.WriteLine("显示菜单界面");
}
pd HideMenuUI()
{
Console.WriteLine("隐藏菜单界面");
}
private void PlayMenuMusic()
{
Console.WriteLine("播放菜单音乐");
}
}
/// <summary>
/// 游戏中状态
/// </summary>
public class GameplayState : ContextAwareStateBase
{
public override void OnEnter(IState? from)
{
Console.WriteLine("=== 开始游戏 ===");
// 初始化游戏场景
InitializeGameScene();
// 重置玩家数据
ResetPlayerData();
// 播放游戏音乐
PlayGameMusic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("结束游戏");
// 保存游戏进度(如果不是游戏结束)
if (to is not GameOverState)
{
SaveGameProgress();
}
}
public override bool CanTransitionTo(IState target)
{
// 游戏中可以切换到暂停或游戏结束状态
return target is PauseState or GameOverState;
}
private void InitializeGameScene()
{
Console.WriteLine("初始化游戏场景");
}
private void ResetPlayerData()
{
Console.WriteLine("重置玩家数据");
}
private void PlayGameMusic()
{
Console.WriteLine("播放游戏音乐");
}
private void SaveGameProgress()
{
Console.WriteLine("保存游戏进度");
}
}
/// <summary>
/// 暂停状态
/// </summary>
public class PauseState : ContextAwareStateBase
{
public override void OnEnter(IState? from)
{
Console.WriteLine("=== 游戏暂停 ===");
// 显示暂停菜单
ShowPauseMenu();
// 暂停游戏逻辑
PauseGameLogic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("取消暂停");
// 隐藏暂停菜单
HidePauseMenu();
// 恢复游戏逻辑
ResumeGameLogic();
}
public override bool CanTransitionTo(IState target)
{
// 暂停状态可以返回游戏或退出到菜单
return target is GameplayState or MenuState;
}
private void ShowPauseMenu()
{
Console.WriteLine("显示暂停菜单");
}
private void HidePauseMenu()
{
Console.WriteLine("隐藏暂停菜单");
}
private void PauseGameLogic()
{
Console.WriteLine("暂停游戏逻辑");
}
private void ResumeGameLogic()
{
Console.WriteLine("恢复游戏逻辑");
}
}
/// <summary>
/// 游戏结束状态
/// </summary>
public class GameOverState : ContextAwareStateBase
{
public bool IsVictory { get; set; }
public override void OnEnter(IState? from)
{
Console.WriteLine(IsVictory
? "=== 游戏胜利 ==="
: "=== 游戏失败 ===");
// 显示结算界面
ShowGameOverUI();
// 播放结算音乐
PlayGameOverMusic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("退出结算界面");
// 隐藏结算界面
HideGameOverUI();
}
public override bool CanTransitionTo(IState target)
{
// 游戏结束只能返回菜单
return target is MenuState;
}
private void ShowGameOverUI()
{
Console.WriteLine("显示结算界面");
}
private void HideGameOverUI()
{
Console.WriteLine("隐藏结算界面");
}
private void PlayGameOverMusic()
{
Console.WriteLine("播放结算音乐");
}
}
}
代码说明:
- 继承
ContextAwareStateBase创建状态 OnEnter在进入状态时调用,用于初始化OnExit在退出状态时调用,用于清理CanTransitionTo定义允许的状态转换规则- 每个状态职责单一,逻辑清晰
步骤 2:创建异步加载状态
实现一个异步加载状态,用于加载游戏资源。
using GFramework.Core.State;
using System.Threading.Tasks;
namespace MyGame.States
{
/// <summary>
/// 加载状态(异步)
/// </summary>
public class LoadingState : AsyncContextAwareStateBase
{
public int TargetLevel { get; set; } = 1;
public override async Task OnEnterAsync(IState? from)
{
Console.WriteLine($"=== 开始加载关卡 {TargetLevel} ===");
// 显示加载界面
ShowLoadingUI();
// 异步加载资源
await LoadResourcesAsync();
// 加载完成后自动切换到游戏状态
Console.WriteLine("加载完成,进入游戏");
var stateMachine = this.GetSystem<IStateMachineSystem>();
await stateMachine.ChangeToAsync<GameplayState>();
}
public override async Task OnExitAsync(IState? to)
{
Console.WriteLine("退出加载状态");
// 隐藏加载界面
HideLoadingUI();
await Task.CompletedTask;
}
public override bool CanTransitionTo(IState target)
{
// 加载状态只能切换到游戏状态
return target is GameplayState;
}
/// <summary>
/// 异步加载资源
/// </summary>
private async Task LoadResourcesAsync()
{
// 加载纹理
Console.WriteLine("加载纹理资源...");
await Task.Delay(500);
Console.WriteLine("纹理加载完成 (33%)");
// 加载音频
Console.WriteLine("加载音频资源...");
await Task.Delay(500);
Console.WriteLine("音频加载完成 (66%)");
// 加载关卡数据
Console.WriteLine("加载关卡数据...");
await Task.Delay(500);
Console.WriteLine("关卡数据加载完成 (100%)");
}
private void ShowLoadingUI()
{
Console.WriteLine("显示加载界面");
}
private void HideLoadingUI()
{
Console.WriteLine("隐藏加载界面");
}
}
}
代码说明:
- 继承
AsyncContextAwareStateBase支持异步操作 OnEnterAsync和OnExitAsync是异步方法- 可以在状态中使用
await等待异步操作 - 加载完成后自动切换到下一个状态
步骤 3:注册状态机系统
在架构中注册状态机系统和所有状态。
using GFramework.Core.Architecture;
using GFramework.Core.Abstractions.State;
using GFramework.Core.State;
using MyGame.States;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 创建状态机系统
var stateMachine = new StateMachineSystem();
// 注册所有状态
stateMachine
.Register(new MenuState())
.Register(new LoadingState())
.Register(new GameplayState())
.Register(new PauseState())
.Register(new GameOverState());
// 注册状态机系统到架构
RegisterSystem<IStateMachineSystem>(stateMachine);
Console.WriteLine("状态机系统初始化完成");
}
}
}
代码说明:
- 创建
StateMachineSystem实例 - 使用链式调用注册所有状态
- 将状态机注册为
IStateMachineSystem服务 - 状态会自动获得架构上下文
步骤 4:创建游戏控制器
创建控制器来管理游戏流程和状态切换。
using GFramework.Core.Abstractions.Architecture;
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.Abstractions.State;
using GFramework.Core.Extensions;
using MyGame.States;
using System.Threading.Tasks;
using GFramework.SourceGenerators.Abstractions.Rule;
namespace MyGame.Controllers
{
[ContextAware]
public partial class GameFlowController : IController
{
/// <summary>
/// 开始游戏
/// </summary>
public async Task StartGame(int level = 1)
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 设置加载状态的目标关卡
var loadingState = stateMachine.GetState<LoadingState>();
if (loadingState != null)
{
loadingState.TargetLevel = level;
}
// 切换到加载状态
var success = await stateMachine.ChangeToAsync<LoadingState>();
if (!success)
{
Console.WriteLine("无法开始游戏:状态切换失败");
}
}
/// <summary>
/// 暂停游戏
/// </summary>
public async Task PauseGame()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 检查当前是否在游戏状态
if (stateMachine.Current is not GameplayState)
{
Console.WriteLine("当前不在游戏中,无法暂停");
return;
}
// 切换到暂停状态
await stateMachine.ChangeToAsync<PauseState>();
}
/// <summary>
/// 恢复游戏
/// </summary>
public async Task ResumeGame()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 检查当前是否在暂停状态
if (stateMachine.Current is not PauseState)
{
Console.WriteLine("当前不在暂停状态");
return;
}
// 返回游戏状态
await stateMachine.ChangeToAsync<GameplayState>();
}
/// <summary>
/// 游戏结束
/// </summary>
public async Task EndGame(bool isVictory)
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 设置游戏结束状态的数据
var gameOverState = stateMachine.GetState<GameOverState>();
if (gameOverState != null)
{
gameOverState.IsVictory = isVictory;
}
// 切换到游戏结束状态
await stateMachine.ChangeToAsync<GameOverState>();
}
/// <summary>
/// 返回主菜单
/// </summary>
public async Task ReturnToMenu()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 切换到菜单状态
var success = await stateMachine.ChangeToAsync<MenuState>();
if (!success)
{
Console.WriteLine("无法返回菜单:状态切换被拒绝");
}
}
/// <summary>
/// 显示当前状态
/// </summary>
public void ShowCurrentState()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
var currentState = stateMachine.Current;
if (currentState != null)
{
Console.WriteLine($"当前状态: {currentState.GetType().Name}");
}
else
{
Console.WriteLine("当前没有活动状态");
}
}
/// <summary>
/// 显示状态历史
/// </summary>
public void ShowStateHistory()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
var history = stateMachine.GetStateHistory();
Console.WriteLine("状态历史:");
foreach (var state in history)
{
Console.WriteLine($" - {state.GetType().Name}");
}
}
}
}
代码说明:
- 实现
IController接口 - 提供各种游戏流程控制方法
- 使用
GetState<T>()获取状态实例并设置数据 - 使用
ChangeToAsync<T>()切换状态 - 检查切换结果并处理失败情况
步骤 5:测试状态机
编写测试代码验证状态机功能。
using MyGame;
using MyGame.Controllers;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== 状态机系统测试 ===\n");
// 1. 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 2. 创建游戏流程控制器
var gameFlow = new GameFlowController();
// 3. 切换到主菜单
Console.WriteLine("\n--- 测试 1: 进入主菜单 ---");
await gameFlow.ReturnToMenu();
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 4. 开始游戏(会经过加载状态)
Console.WriteLine("\n--- 测试 2: 开始游戏 ---");
await gameFlow.StartGame(level: 1);
await Task.Delay(2000); // 等待加载完成
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 5. 暂停游戏
Console.WriteLine("\n--- 测试 3: 暂停游戏 ---");
await gameFlow.PauseGame();
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 6. 恢复游戏
Console.WriteLine("\n--- 测试 4: 恢复游戏 ---");
await gameFlow.ResumeGame();
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 7. 游戏胜利
Console.WriteLine("\n--- 测试 5: 游戏胜利 ---");
await gameFlow.EndGame(isVictory: true);
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 8. 返回菜单
Console.WriteLine("\n--- 测试 6: 返回菜单 ---");
await gameFlow.ReturnToMenu();
gameFlow.ShowCurrentState();
// 9. 显示状态历史
Console.WriteLine("\n--- 状态历史 ---");
gameFlow.ShowStateHistory();
Console.WriteLine("\n=== 测试完成 ===");
}
}
代码说明:
- 初始化架构并等待就绪
- 按顺序测试各种状态切换
- 使用
Task.Delay模拟游戏运行 - 验证状态转换规则和历史记录
完整代码
所有代码文件已在上述步骤中提供。项目结构如下:
MyGame/
├── States/
│ ├── MenuState.cs
│ ├── LoadingState.cs
│ ├── GameplayState.cs
│ ├── PauseState.cs
│ └── GameOverState.cs
├── Controllers/
│ └── GameFlowController.cs
├── GameArchitecture.cs
└── Program.cs
运行结果
运行程序后,你将看到类似以下的输出:
=== 状态机系统测试 ===
状态机系统初始化完成
--- 测试 1: 进入主菜单 ---
=== 进入主菜单 ===
显示菜单界面
播放菜单音乐
当前状态: MenuState
--- 测试 2: 开始游戏 ---
退出主菜单
隐藏菜单界面
=== 开始加载关卡 1 ===
显示加载界面
加载纹理资源...
纹理加载完成 (33%)
加载音频资源...
音频加载完成 (66%)
加载关卡数据...
关卡数据加载完成 (100%)
加载完成,进入游戏
退出加载状态
隐藏加载界面
=== 开始游戏 ===
初始化游戏场景
重置玩家数据
播放游戏音乐
当前状态: GameplayState
--- 测试 3: 暂停游戏 ---
结束游戏
保存游戏进度
=== 游戏暂停 ===
显示暂停菜单
暂停游戏逻辑
当前状态: PauseState
--- 测试 4: 恢复游戏 ---
取消暂停
隐藏暂停菜单
恢复游戏逻辑
=== 开始游戏 ===
初始化游戏场景
重置玩家数据
播放游戏音乐
当前状态: GameplayState
--- 测试 5: 游戏胜利 ---
结束游戏
=== 游戏胜利 ===
显示结算界面
播放结算音乐
当前状态: GameOverState
--- 测试 6: 返回菜单 ---
退出结算界面
隐藏结算界面
=== 进入主菜单 ===
显示菜单界面
播放菜单音乐
当前状态: MenuState
--- 状态历史 ---
状态历史:
- MenuState
- LoadingState
- GameplayState
- PauseState
- GameplayState
- GameOverState
- MenuState
=== 测试完成 ===
验证步骤:
- 所有状态切换成功执行
- 状态转换规则正确生效
- 异步加载状态正常工作
- 状态历史记录完整
- 状态数据传递正确
下一步
恭喜!你已经掌握了状态机系统的使用。接下来可以学习: