mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(architecture): 添加架构核心组件与命令模式实现
- 新增 Architecture 基类与 IArchitecture 接口,实现单例模式与组件注册管理 - 集成 IOC 容器支持系统、模型、工具的依赖注入与生命周期管理 - 实现命令模式基础类 AbstractCommand 与接口 ICommand,支持带返回值命令 - 提供事件系统集成,支持事件的发布与订阅机制 - 添加控制器接口 IController,整合命令发送、事件注册与模型获取能力 - 创建详细的 README 文档说明各组件使用方式与设计模式应用 - 支持命令、查询、事件的统一调度与解耦通信机制
This commit is contained in:
commit
5aa11ddc41
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
9
GFramework.csproj
Normal file
9
GFramework.csproj
Normal file
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
16
GFramework.sln
Normal file
16
GFramework.sln
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# 项目介绍
|
||||
本项目参考(CV)自[QFramework](https://github.com/liangxiegame/QFramework)
|
||||
# 为什么要有这个项目
|
||||
- 原来的项目是单文件框架,我把框架拆成多个文件,方便管理
|
||||
1108
framework/README.md
Normal file
1108
framework/README.md
Normal file
File diff suppressed because it is too large
Load Diff
268
framework/architecture/Architecture.cs
Normal file
268
framework/architecture/Architecture.cs
Normal file
@ -0,0 +1,268 @@
|
||||
|
||||
using GFramework.framework.command;
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.ioc;
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.query;
|
||||
using GFramework.framework.system;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
|
||||
namespace GFramework.framework.architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 架构基类,提供系统、模型、工具等组件的注册与管理功能。
|
||||
/// 使用单例模式确保全局唯一实例,并支持命令、查询和事件机制。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">派生类类型,用于实现单例</typeparam>
|
||||
public abstract class Architecture<T> : IArchitecture where T : Architecture<T>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记架构是否已初始化完成
|
||||
/// </summary>
|
||||
private bool _mInited;
|
||||
|
||||
/// <summary>
|
||||
/// 存储尚未初始化的系统集合,在初始化阶段统一调用Init方法
|
||||
/// </summary>
|
||||
private readonly HashSet<ISystem> _mSystems = [];
|
||||
|
||||
/// <summary>
|
||||
/// 存储尚未初始化的模型集合,在初始化阶段统一调用Init方法
|
||||
/// </summary>
|
||||
private readonly HashSet<IModel> _mModels = [];
|
||||
|
||||
/// <summary>
|
||||
/// 注册补丁委托,允许在架构创建后执行额外逻辑
|
||||
/// </summary>
|
||||
public static Action<T> OnRegisterPatch { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// 静态只读字段,用于延迟初始化架构实例
|
||||
/// 使用Lazy确保线程安全的单例模式实现
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 初始化过程包括:
|
||||
/// 1. 创建T类型的实例
|
||||
/// 2. 调用用户自定义的Init方法
|
||||
/// 3. 执行注册的补丁逻辑
|
||||
/// 4. 初始化所有已注册的模型和系统
|
||||
/// 5. 清理临时集合并标记初始化完成
|
||||
/// </remarks>
|
||||
/// <returns>T类型的架构实例</returns>
|
||||
private static readonly Lazy<T> MArchitectureLazy = new(() =>
|
||||
{
|
||||
var arch = new T();
|
||||
// 调用用户实现的初始化
|
||||
arch.Init();
|
||||
|
||||
// 执行注册的补丁逻辑
|
||||
OnRegisterPatch?.Invoke(arch);
|
||||
|
||||
// 初始化所有已注册但尚未初始化的模型
|
||||
foreach (var model in arch._mModels)
|
||||
{
|
||||
model.Init();
|
||||
}
|
||||
|
||||
arch._mModels.Clear();
|
||||
|
||||
// 初始化所有已注册但尚未初始化的系统
|
||||
foreach (var system in arch._mSystems)
|
||||
{
|
||||
system.Init();
|
||||
}
|
||||
|
||||
arch._mSystems.Clear();
|
||||
|
||||
arch._mInited = true;
|
||||
return arch;
|
||||
}, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
/// <summary>
|
||||
/// 获取架构实例的受保护静态属性
|
||||
/// 通过Lazy初始化确保只创建一个实例
|
||||
/// </summary>
|
||||
/// <returns>T类型的架构实例</returns>
|
||||
protected static T MArchitecture => MArchitectureLazy.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 获取架构实例的公共静态属性,以接口形式暴露
|
||||
/// 提供对外访问架构功能的标准接口
|
||||
/// </summary>
|
||||
/// <returns>IArchitecture接口类型的架构实例</returns>
|
||||
public static IArchitecture Interface => MArchitectureLazy.Value;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 抽象初始化方法,由子类重写以进行自定义初始化操作
|
||||
/// </summary>
|
||||
protected abstract void Init();
|
||||
|
||||
/// <summary>
|
||||
/// 控制反转容器,用于存储和获取各种服务(如系统、模型、工具)
|
||||
/// </summary>
|
||||
private readonly IocContainer _mContainer = new();
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个系统到架构中。
|
||||
/// 若当前未初始化,则暂存至待初始化列表;否则立即初始化该系统。
|
||||
/// </summary>
|
||||
/// <typeparam name="TSystem">要注册的系统类型</typeparam>
|
||||
/// <param name="system">要注册的系统实例</param>
|
||||
public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
|
||||
{
|
||||
system.SetArchitecture(this);
|
||||
_mContainer.Register(system);
|
||||
|
||||
if (!_mInited)
|
||||
{
|
||||
_mSystems.Add(system);
|
||||
}
|
||||
else
|
||||
{
|
||||
system.Init();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个模型到架构中。
|
||||
/// 若当前未初始化,则暂存至待初始化列表;否则立即初始化该模型。
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">要注册的模型类型</typeparam>
|
||||
/// <param name="model">要注册的模型实例</param>
|
||||
public void RegisterModel<TModel>(TModel model) where TModel : IModel
|
||||
{
|
||||
model.SetArchitecture(this);
|
||||
_mContainer.Register(model);
|
||||
|
||||
if (!_mInited)
|
||||
{
|
||||
_mModels.Add(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Init();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个工具到架构中。
|
||||
/// 工具不会被延迟初始化,直接放入IOC容器供后续使用。
|
||||
/// </summary>
|
||||
/// <typeparam name="TUtility">要注册的工具类型</typeparam>
|
||||
/// <param name="utility">要注册的工具实例</param>
|
||||
public void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility =>
|
||||
_mContainer.Register(utility);
|
||||
|
||||
/// <summary>
|
||||
/// 从IOC容器中获取指定类型的系统实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TSystem">目标系统类型</typeparam>
|
||||
/// <returns>对应的系统实例</returns>
|
||||
public TSystem GetSystem<TSystem>() where TSystem : class, ISystem => _mContainer.Get<TSystem>();
|
||||
|
||||
/// <summary>
|
||||
/// 从IOC容器中获取指定类型的模型实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">目标模型类型</typeparam>
|
||||
/// <returns>对应的模型实例</returns>
|
||||
public TModel GetModel<TModel>() where TModel : class, IModel => _mContainer.Get<TModel>();
|
||||
|
||||
/// <summary>
|
||||
/// 从IOC容器中获取指定类型的工具实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TUtility">目标工具类型</typeparam>
|
||||
/// <returns>对应的工具实例</returns>
|
||||
public TUtility GetUtility<TUtility>() where TUtility : class, IUtility => _mContainer.Get<TUtility>();
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个带返回结果的命令请求
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令执行后的返回值类型</typeparam>
|
||||
/// <param name="command">要发送的命令对象</param>
|
||||
/// <returns>命令执行的结果</returns>
|
||||
public TResult SendCommand<TResult>(ICommand<TResult> command) => ExecuteCommand(command);
|
||||
|
||||
/// <summary>
|
||||
/// 发送一个无返回结果的命令请求
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">命令的具体类型</typeparam>
|
||||
/// <param name="command">要发送的命令对象</param>
|
||||
public void SendCommand<TCommand>(TCommand command) where TCommand : ICommand => ExecuteCommand(command);
|
||||
|
||||
/// <summary>
|
||||
/// 执行一个带返回结果的命令
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令执行后的返回值类型</typeparam>
|
||||
/// <param name="command">要执行的命令对象</param>
|
||||
/// <returns>命令执行的结果</returns>
|
||||
protected virtual TResult ExecuteCommand<TResult>(ICommand<TResult> command)
|
||||
{
|
||||
command.SetArchitecture(this);
|
||||
return command.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行一个无返回结果的命令
|
||||
/// </summary>
|
||||
/// <param name="command">要执行的命令对象</param>
|
||||
protected virtual void ExecuteCommand(ICommand command)
|
||||
{
|
||||
command.SetArchitecture(this);
|
||||
command.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发起一次查询请求并获得其结果
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果的数据类型</typeparam>
|
||||
/// <param name="query">要发起的查询对象</param>
|
||||
/// <returns>查询得到的结果数据</returns>
|
||||
public TResult SendQuery<TResult>(IQuery<TResult> query) => DoQuery(query);
|
||||
|
||||
/// <summary>
|
||||
/// 实际执行查询逻辑的方法
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果的数据类型</typeparam>
|
||||
/// <param name="query">要处理的查询对象</param>
|
||||
/// <returns>查询结果</returns>
|
||||
protected virtual TResult DoQuery<TResult>(IQuery<TResult> query)
|
||||
{
|
||||
query.SetArchitecture(this);
|
||||
return query.Do();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型化事件系统,负责事件的发布与订阅管理
|
||||
/// </summary>
|
||||
private readonly TypeEventSystem _mTypeEventSystem = new();
|
||||
|
||||
/// <summary>
|
||||
/// 发布一个默认构造的新事件对象
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">事件类型</typeparam>
|
||||
public void SendEvent<TEvent>() where TEvent : new() => _mTypeEventSystem.Send<TEvent>();
|
||||
|
||||
/// <summary>
|
||||
/// 发布一个具体的事件对象
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">事件类型</typeparam>
|
||||
/// <param name="e">要发布的事件实例</param>
|
||||
public void SendEvent<TEvent>(TEvent e) => _mTypeEventSystem.Send(e);
|
||||
|
||||
/// <summary>
|
||||
/// 订阅某个特定类型的事件
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">事件类型</typeparam>
|
||||
/// <param name="onEvent">当事件发生时触发的动作</param>
|
||||
/// <returns>可用于取消订阅的对象</returns>
|
||||
public IUnRegister RegisterEvent<TEvent>(Action<TEvent> onEvent) => _mTypeEventSystem.Register(onEvent);
|
||||
|
||||
/// <summary>
|
||||
/// 取消对某类型事件的监听
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">事件类型</typeparam>
|
||||
/// <param name="onEvent">之前绑定的事件处理器</param>
|
||||
public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent) => _mTypeEventSystem.UnRegister(onEvent);
|
||||
}
|
||||
110
framework/architecture/IArchitecture.cs
Normal file
110
framework/architecture/IArchitecture.cs
Normal file
@ -0,0 +1,110 @@
|
||||
|
||||
|
||||
using GFramework.framework.command;
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.query;
|
||||
using GFramework.framework.system;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
namespace GFramework.framework.architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 架构接口,定义了应用程序架构的核心功能,包括系统、模型、工具的注册和获取,
|
||||
/// 以及命令、查询、事件的发送和处理机制
|
||||
/// </summary>
|
||||
public interface IArchitecture
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册系统实例到架构中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">系统类型,必须实现ISystem接口</typeparam>
|
||||
/// <param name="system">要注册的系统实例</param>
|
||||
void RegisterSystem<T>(T system) where T : ISystem;
|
||||
|
||||
/// <summary>
|
||||
/// 注册模型实例到架构中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">模型类型,必须实现IModel接口</typeparam>
|
||||
/// <param name="model">要注册的模型实例</param>
|
||||
void RegisterModel<T>(T model) where T : IModel;
|
||||
|
||||
/// <summary>
|
||||
/// 注册工具实例到架构中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">工具类型,必须实现IUtility接口</typeparam>
|
||||
/// <param name="utility">要注册的工具实例</param>
|
||||
void RegisterUtility<T>(T utility) where T : IUtility;
|
||||
|
||||
/// <summary>
|
||||
/// 从架构中获取指定类型的系统实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">系统类型,必须是class且实现ISystem接口</typeparam>
|
||||
/// <returns>指定类型的系统实例</returns>
|
||||
T GetSystem<T>() where T : class, ISystem;
|
||||
|
||||
/// <summary>
|
||||
/// 从架构中获取指定类型的模型实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">模型类型,必须是class且实现IModel接口</typeparam>
|
||||
/// <returns>指定类型的模型实例</returns>
|
||||
T GetModel<T>() where T : class, IModel;
|
||||
|
||||
/// <summary>
|
||||
/// 从架构中获取指定类型的工具实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">工具类型,必须是class且实现IUtility接口</typeparam>
|
||||
/// <returns>指定类型的工具实例</returns>
|
||||
T GetUtility<T>() where T : class, IUtility;
|
||||
|
||||
/// <summary>
|
||||
/// 发送并执行指定的命令
|
||||
/// </summary>
|
||||
/// <typeparam name="T">命令类型,必须实现ICommand接口</typeparam>
|
||||
/// <param name="command">要执行的命令实例</param>
|
||||
void SendCommand<T>(T command) where T : ICommand;
|
||||
|
||||
/// <summary>
|
||||
/// 发送并执行带有返回值的命令
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令执行结果的类型</typeparam>
|
||||
/// <param name="command">要执行的命令实例</param>
|
||||
/// <returns>命令执行的结果</returns>
|
||||
TResult SendCommand<TResult>(ICommand<TResult> command);
|
||||
|
||||
/// <summary>
|
||||
/// 发送并执行查询操作
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果的类型</typeparam>
|
||||
/// <param name="query">要执行的查询实例</param>
|
||||
/// <returns>查询的结果</returns>
|
||||
TResult SendQuery<TResult>(IQuery<TResult> query);
|
||||
|
||||
/// <summary>
|
||||
/// 发送无参事件
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须具有无参构造函数</typeparam>
|
||||
void SendEvent<T>() where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 发送指定的事件实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="e">要发送的事件实例</param>
|
||||
void SendEvent<T>(T e);
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件监听器
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="onEvent">事件触发时的回调方法</param>
|
||||
/// <returns>用于取消注册的句柄</returns>
|
||||
IUnRegister RegisterEvent<T>(Action<T> onEvent);
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册事件监听器
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="onEvent">要取消注册的事件回调方法</param>
|
||||
void UnRegisterEvent<T>(Action<T> onEvent);
|
||||
}
|
||||
169
framework/architecture/README.md
Normal file
169
framework/architecture/README.md
Normal file
@ -0,0 +1,169 @@
|
||||
# Architecture 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Architecture 包是整个框架的核心,提供了基于 MVC 架构模式的应用程序架构基础。它实现了依赖注入(IoC)容器、组件生命周期管理,以及命令、查询、事件的统一调度机制。
|
||||
|
||||
## 核心类
|
||||
|
||||
### 1. [`IArchitecture`](IArchitecture.cs)
|
||||
|
||||
架构接口,定义了框架的核心功能契约。
|
||||
|
||||
**主要职责:**
|
||||
- 组件注册:注册 System、Model、Utility
|
||||
- 组件获取:从容器中获取已注册的组件
|
||||
- 命令处理:发送并执行命令
|
||||
- 查询处理:发送并执行查询
|
||||
- 事件管理:发送、注册、注销事件
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
// 注册组件
|
||||
void RegisterSystem<T>(T system) where T : ISystem;
|
||||
void RegisterModel<T>(T model) where T : IModel;
|
||||
void RegisterUtility<T>(T utility) where T : IUtility;
|
||||
|
||||
// 获取组件
|
||||
T GetSystem<T>() where T : class, ISystem;
|
||||
T GetModel<T>() where T : class, IModel;
|
||||
T GetUtility<T>() where T : class, IUtility;
|
||||
|
||||
// 命令处理
|
||||
void SendCommand<T>(T command) where T : ICommand;
|
||||
TResult SendCommand<TResult>(ICommand<TResult> command);
|
||||
|
||||
// 查询处理
|
||||
TResult SendQuery<TResult>(IQuery<TResult> query);
|
||||
|
||||
// 事件管理
|
||||
void SendEvent<T>() where T : new();
|
||||
void SendEvent<T>(T e);
|
||||
IUnRegister RegisterEvent<T>(Action<T> onEvent);
|
||||
void UnRegisterEvent<T>(Action<T> onEvent);
|
||||
```
|
||||
|
||||
### 2. [`Architecture<T>`](Architecture.cs)
|
||||
|
||||
架构基类,实现了 [`IArchitecture`](IArchitecture.cs) 接口,提供完整的架构功能实现。
|
||||
|
||||
**特性:**
|
||||
- **单例模式**:使用泛型和 `Lazy<T>` 确保全局唯一实例
|
||||
- **线程安全**:采用 `LazyThreadSafetyMode.ExecutionAndPublication` 保证多线程安全
|
||||
- **生命周期管理**:自动管理 System 和 Model 的初始化顺序
|
||||
- **IoC 容器**:内置依赖注入容器,管理所有组件实例
|
||||
- **事件系统**:集成类型化事件系统,支持类型安全的事件通信
|
||||
|
||||
**初始化流程:**
|
||||
1. 创建架构实例
|
||||
2. 调用用户自定义的 `Init()` 方法
|
||||
3. 执行注册的补丁逻辑(`OnRegisterPatch`)
|
||||
4. 初始化所有已注册的 Model
|
||||
5. 初始化所有已注册的 System
|
||||
6. 标记初始化完成
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 1. 定义你的架构
|
||||
public class GameArchitecture : Architecture<GameArchitecture>
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Model
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterModel(new InventoryModel());
|
||||
|
||||
// 注册 System
|
||||
RegisterSystem(new GameplaySystem());
|
||||
RegisterSystem(new SaveSystem());
|
||||
|
||||
// 注册 Utility
|
||||
RegisterUtility(new StorageUtility());
|
||||
RegisterUtility(new TimeUtility());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 在其他地方使用架构
|
||||
public class GameController : IController
|
||||
{
|
||||
private IArchitecture _architecture;
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// 获取 Model
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 发送命令
|
||||
this.SendCommand<StartGameCommand>();
|
||||
|
||||
// 发送查询
|
||||
var score = this.SendQuery(new GetScoreQuery());
|
||||
|
||||
// 注册事件
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
// 处理玩家死亡事件
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**高级特性:**
|
||||
|
||||
```csharp
|
||||
// 动态扩展架构(补丁系统)
|
||||
Architecture<GameArchitecture>.OnRegisterPatch += arch =>
|
||||
{
|
||||
// 在架构初始化完成前注入额外逻辑
|
||||
arch.RegisterSystem(new DebugSystem());
|
||||
};
|
||||
|
||||
// 在运行时动态注册组件(初始化后)
|
||||
var newSystem = new DynamicSystem();
|
||||
GameArchitecture.Interface.RegisterSystem(newSystem);
|
||||
// newSystem.Init() 会被立即调用
|
||||
```
|
||||
|
||||
## 设计模式
|
||||
|
||||
### 单例模式
|
||||
通过泛型约束和 `Lazy<T>` 实现类型安全的单例。
|
||||
|
||||
### 依赖注入(IoC)
|
||||
使用内置 IoC 容器管理组件生命周期和依赖关系。
|
||||
|
||||
### 命令模式
|
||||
通过 [`ICommand`](../command/ICommand.cs) 封装所有用户操作。
|
||||
|
||||
### 查询模式(CQRS)
|
||||
通过 [`IQuery<T>`](../query/IQuery.cs) 分离查询和命令操作。
|
||||
|
||||
### 观察者模式
|
||||
通过事件系统实现组件间的松耦合通信。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑
|
||||
2. **合理划分职责**:
|
||||
- Model:数据和状态
|
||||
- System:业务逻辑和规则
|
||||
- Utility:无状态的工具方法
|
||||
3. **使用接口访问**:通过 `Interface` 属性访问架构,便于测试
|
||||
4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent`
|
||||
5. **避免循环依赖**:System 不应直接引用 System,应通过事件通信
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`command`](../command/README.md) - 命令模式实现
|
||||
- [`query`](../query/README.md) - 查询模式实现
|
||||
- [`events`](../events/README.md) - 事件系统
|
||||
- [`ioc`](../ioc/README.md) - IoC 容器
|
||||
- [`model`](../model/README.md) - 数据模型
|
||||
- [`system`](../system/README.md) - 业务系统
|
||||
- [`utility`](../utility/README.md) - 工具类
|
||||
68
framework/command/AbstractCommand.cs
Normal file
68
framework/command/AbstractCommand.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using GFramework.framework.architecture;
|
||||
using GFramework.framework.rule;
|
||||
|
||||
|
||||
namespace GFramework.framework.command;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象命令类,实现 ICommand 接口,为具体命令提供基础架构支持
|
||||
/// </summary>
|
||||
public abstract class AbstractCommand : ICommand
|
||||
{
|
||||
private IArchitecture _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 获取命令所属的架构实例
|
||||
/// </summary>
|
||||
/// <returns>IArchitecture 架构接口实例</returns>
|
||||
IArchitecture IBelongToArchitecture.GetArchitecture() => _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 设置命令所属的架构实例
|
||||
/// </summary>
|
||||
/// <param name="architecture">要设置的架构实例</param>
|
||||
void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => _mArchitecture = architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 执行命令,调用抽象方法 OnExecute 来实现具体逻辑
|
||||
/// </summary>
|
||||
void ICommand.Execute() => OnExecute();
|
||||
|
||||
/// <summary>
|
||||
/// 抽象方法,由子类实现具体的命令执行逻辑
|
||||
/// </summary>
|
||||
protected abstract void OnExecute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带返回值的抽象命令类,实现 ICommand{TResult} 接口,为需要返回结果的命令提供基础架构支持
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令执行后返回的结果类型</typeparam>
|
||||
public abstract class AbstractCommand<TResult> : ICommand<TResult>
|
||||
{
|
||||
private IArchitecture _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 获取命令所属的架构实例
|
||||
/// </summary>
|
||||
/// <returns>IArchitecture 架构接口实例</returns>
|
||||
IArchitecture IBelongToArchitecture.GetArchitecture() => _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 设置命令所属的架构实例
|
||||
/// </summary>
|
||||
/// <param name="architecture">要设置的架构实例</param>
|
||||
void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => _mArchitecture = architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 执行命令,调用抽象方法 OnExecute 来实现具体逻辑并返回结果
|
||||
/// </summary>
|
||||
/// <returns>TResult 类型的执行结果</returns>
|
||||
TResult ICommand<TResult>.Execute() => OnExecute();
|
||||
|
||||
/// <summary>
|
||||
/// 抽象方法,由子类实现具体的命令执行逻辑
|
||||
/// </summary>
|
||||
/// <returns>TResult 类型的执行结果</returns>
|
||||
protected abstract TResult OnExecute();
|
||||
}
|
||||
9
framework/command/ICanSendCommand.cs
Normal file
9
framework/command/ICanSendCommand.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.command;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个可以发送命令的接口,继承自IBelongToArchitecture接口。
|
||||
/// 该接口用于标识那些具备发送命令能力的架构组件。
|
||||
/// </summary>
|
||||
public interface ICanSendCommand : IBelongToArchitecture;
|
||||
39
framework/command/ICommand.cs
Normal file
39
framework/command/ICommand.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.query;
|
||||
using GFramework.framework.rule;
|
||||
using GFramework.framework.system;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
namespace GFramework.framework.command;
|
||||
|
||||
/// <summary>
|
||||
/// 命令接口,定义了无返回值命令的基本契约
|
||||
/// 该接口继承了多个框架能力接口,使命令可以访问架构、系统、模型、工具,并能够发送事件、命令和查询
|
||||
/// </summary>
|
||||
public interface ICommand : ICanSetArchitecture, ICanGetSystem, ICanGetModel, ICanGetUtility,
|
||||
ICanSendEvent, ICanSendCommand, ICanSendQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行命令的核心方法
|
||||
/// 该方法不接受参数且无返回值,具体实现由派生类完成
|
||||
/// </summary>
|
||||
void Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带返回值的命令接口,定义了有返回值命令的基本契约
|
||||
/// 该接口继承了多个框架能力接口,使命令可以访问架构、系统、模型、工具,并能够发送事件、命令和查询
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令执行后返回的结果类型</typeparam>
|
||||
public interface ICommand<out TResult> : ICanSetArchitecture, ICanGetSystem, ICanGetModel,
|
||||
ICanGetUtility,
|
||||
ICanSendEvent, ICanSendCommand, ICanSendQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行命令的核心方法
|
||||
/// 该方法不接受参数,但会返回指定类型的结果
|
||||
/// </summary>
|
||||
/// <returns>命令执行后的结果,类型为 TResult</returns>
|
||||
TResult Execute();
|
||||
}
|
||||
293
framework/command/README.md
Normal file
293
framework/command/README.md
Normal file
@ -0,0 +1,293 @@
|
||||
# Command 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Command 包实现了命令模式(Command Pattern),用于封装用户操作和业务逻辑。通过命令模式,可以将请求封装为对象,实现操作的参数化、队列化、日志记录、撤销等功能。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### 1. [`ICommand`](ICommand.cs)
|
||||
|
||||
无返回值命令接口,定义了命令的基本契约。
|
||||
|
||||
**继承的能力接口:**
|
||||
- [`ICanSetArchitecture`](../rule/ICanSetArchitecture.cs) - 可设置架构
|
||||
- [`ICanGetSystem`](../system/ICanGetSystem.cs) - 可获取系统
|
||||
- [`ICanGetModel`](../model/ICanGetModel.cs) - 可获取模型
|
||||
- [`ICanGetUtility`](../utility/ICanGetUtility.cs) - 可获取工具
|
||||
- [`ICanSendEvent`](../events/ICanSendEvent.cs) - 可发送事件
|
||||
- [`ICanSendCommand`](ICanSendCommand.cs) - 可发送命令
|
||||
- [`ICanSendQuery`](../query/ICanSendQuery.cs) - 可发送查询
|
||||
|
||||
**核心方法:**
|
||||
```csharp
|
||||
void Execute(); // 执行命令
|
||||
```
|
||||
|
||||
### 2. [`ICommand<TResult>`](ICommand.cs)
|
||||
|
||||
带返回值的命令接口,用于需要返回执行结果的命令。
|
||||
|
||||
**核心方法:**
|
||||
```csharp
|
||||
TResult Execute(); // 执行命令并返回结果
|
||||
```
|
||||
|
||||
### 3. [`ICanSendCommand`](ICanSendCommand.cs)
|
||||
|
||||
标记接口,表示实现者可以发送命令。继承自 [`IBelongToArchitecture`](../rule/IBelongToArchitecture.cs)。
|
||||
|
||||
## 核心类
|
||||
|
||||
### 1. [`AbstractCommand`](AbstractCommand.cs)
|
||||
|
||||
无返回值命令的抽象基类,提供了命令的基础实现。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 定义一个开始游戏的命令
|
||||
public class StartGameCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 获取需要的模型
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameModel = this.GetModel<GameModel>();
|
||||
|
||||
// 执行业务逻辑
|
||||
playerModel.Health.Value = 100;
|
||||
gameModel.GameState.Value = GameState.Playing;
|
||||
|
||||
// 发送事件通知其他模块
|
||||
this.SendEvent(new GameStartedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
// 使用命令
|
||||
public class GameController : IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void OnStartButtonClicked()
|
||||
{
|
||||
// 方式1:发送命令实例
|
||||
this.SendCommand(new StartGameCommand());
|
||||
|
||||
// 方式2:通过泛型发送(需要无参构造函数)
|
||||
this.SendCommand<StartGameCommand>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. [`AbstractCommand<TResult>`](AbstractCommand.cs)
|
||||
|
||||
带返回值命令的抽象基类。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 定义一个计算伤害的命令
|
||||
public class CalculateDamageCommand : AbstractCommand<int>
|
||||
{
|
||||
private readonly int _attackPower;
|
||||
private readonly int _defense;
|
||||
|
||||
public CalculateDamageCommand(int attackPower, int defense)
|
||||
{
|
||||
_attackPower = attackPower;
|
||||
_defense = defense;
|
||||
}
|
||||
|
||||
protected override int OnExecute()
|
||||
{
|
||||
// 获取游戏配置
|
||||
var config = this.GetModel<GameConfigModel>();
|
||||
|
||||
// 计算最终伤害
|
||||
var baseDamage = _attackPower - _defense;
|
||||
var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier);
|
||||
|
||||
return (int)finalDamage;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用带返回值的命令
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit() { }
|
||||
|
||||
public void Attack(Character attacker, Character defender)
|
||||
{
|
||||
// 发送命令并获取返回值
|
||||
var damage = this.SendCommand(
|
||||
new CalculateDamageCommand(attacker.AttackPower, defender.Defense)
|
||||
);
|
||||
|
||||
// 应用伤害
|
||||
defender.Health -= damage;
|
||||
|
||||
// 发送伤害事件
|
||||
this.SendEvent(new DamageDealtEvent(attacker, defender, damage));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 命令的生命周期
|
||||
|
||||
1. **创建命令**:实例化命令对象,传入必要的参数
|
||||
2. **设置架构**:框架自动调用 `SetArchitecture()` 设置架构引用
|
||||
3. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()`
|
||||
4. **返回结果**:对于带返回值的命令,返回执行结果
|
||||
5. **命令销毁**:命令执行完毕后可以被垃圾回收
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 用户交互操作
|
||||
|
||||
```csharp
|
||||
// UI 按钮点击
|
||||
public class SaveGameCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var saveSystem = this.GetSystem<SaveSystem>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
saveSystem.SavePlayerData(playerModel);
|
||||
this.SendEvent(new GameSavedEvent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 业务流程控制
|
||||
|
||||
```csharp
|
||||
// 关卡切换
|
||||
public class LoadLevelCommand : AbstractCommand
|
||||
{
|
||||
private readonly int _levelId;
|
||||
|
||||
public LoadLevelCommand(int levelId)
|
||||
{
|
||||
_levelId = levelId;
|
||||
}
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var levelSystem = this.GetSystem<LevelSystem>();
|
||||
var uiSystem = this.GetSystem<UISystem>();
|
||||
|
||||
// 显示加载界面
|
||||
uiSystem.ShowLoadingScreen();
|
||||
|
||||
// 加载关卡
|
||||
levelSystem.LoadLevel(_levelId);
|
||||
|
||||
// 发送事件
|
||||
this.SendEvent(new LevelLoadedEvent(_levelId));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 网络请求封装
|
||||
|
||||
```csharp
|
||||
// 登录命令
|
||||
public class LoginCommand : AbstractCommand<bool>
|
||||
{
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
|
||||
public LoginCommand(string username, string password)
|
||||
{
|
||||
_username = username;
|
||||
_password = password;
|
||||
}
|
||||
|
||||
protected override bool OnExecute()
|
||||
{
|
||||
var networkSystem = this.GetSystem<NetworkSystem>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 发送登录请求
|
||||
var result = networkSystem.Login(_username, _password);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
playerModel.UserId = result.UserId;
|
||||
playerModel.Username = _username;
|
||||
this.SendEvent(new LoginSuccessEvent());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SendEvent(new LoginFailedEvent(result.ErrorMessage));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 命令 vs 系统方法
|
||||
|
||||
**何时使用命令:**
|
||||
- 需要参数化操作
|
||||
- 需要记录操作历史(用于撤销/重做)
|
||||
- 操作需要跨多个系统协调
|
||||
- 用户触发的离散操作
|
||||
|
||||
**何时使用系统方法:**
|
||||
- 持续运行的逻辑(如每帧更新)
|
||||
- 系统内部的私有逻辑
|
||||
- 不需要外部调用的功能
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持命令原子性**:一个命令应该完成一个完整的业务操作
|
||||
2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃
|
||||
3. **参数通过构造函数传递**:命令需要的参数应在创建时传入
|
||||
4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信
|
||||
5. **合理使用返回值**:只在确实需要返回结果时使用 `ICommand<TResult>`
|
||||
6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand`、`SavePlayerCommand`
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 命令撤销/重做(可扩展)
|
||||
|
||||
```csharp
|
||||
// 可撤销命令接口
|
||||
public interface IUndoableCommand : ICommand
|
||||
{
|
||||
void Undo();
|
||||
}
|
||||
|
||||
// 实现可撤销命令
|
||||
public class MoveCommand : AbstractCommand, IUndoableCommand
|
||||
{
|
||||
private Vector3 _oldPosition;
|
||||
private Vector3 _newPosition;
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var player = this.GetModel<PlayerModel>();
|
||||
_oldPosition = player.Position;
|
||||
player.Position = _newPosition;
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
var player = this.GetModel<PlayerModel>();
|
||||
player.Position = _oldPosition;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 架构核心,负责命令的分发和执行
|
||||
- [`extensions`](../extensions/README.md) - 提供 `SendCommand()` 扩展方法
|
||||
- [`query`](../query/README.md) - 查询模式,用于数据查询
|
||||
- [`events`](../events/README.md) - 事件系统,命令执行后的通知机制
|
||||
- [`system`](../system/README.md) - 业务系统,命令的主要执行者
|
||||
- [`model`](../model/README.md) - 数据模型,命令操作的数据
|
||||
16
framework/controller/IController.cs
Normal file
16
framework/controller/IController.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using GFramework.framework.command;
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.query;
|
||||
using GFramework.framework.system;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
namespace GFramework.framework.controller;
|
||||
|
||||
/// <summary>
|
||||
/// 控制器接口,定义了控制器需要实现的所有功能契约
|
||||
/// 该接口继承了多个框架核心接口,用于支持控制器的各种能力
|
||||
/// 包括架构归属、命令发送、系统获取、模型获取、事件注册、查询发送和工具获取等功能
|
||||
/// </summary>
|
||||
public interface IController : ICanSendCommand, ICanGetSystem, ICanGetModel,
|
||||
ICanRegisterEvent, ICanSendQuery, ICanGetUtility;
|
||||
409
framework/controller/README.md
Normal file
409
framework/controller/README.md
Normal file
@ -0,0 +1,409 @@
|
||||
# Controller 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Controller 包定义了控制器(Controller)的接口规范。控制器是 MVC 架构中的 C 层,负责处理用户交互、协调视图和模型,是连接表现层和业务层的桥梁。在本框架中,Controller 通常对应 Godot 的节点脚本。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IController`](IController.cs)
|
||||
|
||||
控制器接口,定义了控制器需要实现的所有功能契约。
|
||||
|
||||
**继承的能力接口:**
|
||||
- [`ICanSendCommand`](../command/ICanSendCommand.cs) - 可发送命令
|
||||
- [`ICanGetSystem`](../system/ICanGetSystem.cs) - 可获取系统
|
||||
- [`ICanGetModel`](../model/ICanGetModel.cs) - 可获取模型
|
||||
- [`ICanRegisterEvent`](../events/ICanRegisterEvent.cs) - 可注册事件
|
||||
- [`ICanSendQuery`](../query/ICanSendQuery.cs) - 可发送查询
|
||||
- [`ICanGetUtility`](../utility/ICanGetUtility.cs) - 可获取工具
|
||||
|
||||
**能力说明:**
|
||||
|
||||
控制器拥有框架中最全面的能力集合,可以:
|
||||
1. 发送命令执行业务逻辑
|
||||
2. 获取系统调用服务
|
||||
3. 获取模型读写数据
|
||||
4. 注册事件监听变化
|
||||
5. 发送查询获取信息
|
||||
6. 获取工具使用辅助功能
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础控制器实现
|
||||
|
||||
```csharp
|
||||
using Godot;
|
||||
using GFramework.framework.controller;
|
||||
using GFramework.framework.architecture;
|
||||
|
||||
// Godot 节点控制器
|
||||
public partial class PlayerController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
// 实现架构获取
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 获取模型
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 监听模型变化
|
||||
playerModel.Health.RegisterWithInitValue(OnHealthChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 注册事件
|
||||
this.RegisterEvent<PlayerLevelUpEvent>(OnPlayerLevelUp)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
// 处理用户输入
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (Input.IsActionJustPressed("attack"))
|
||||
{
|
||||
// 发送命令
|
||||
this.SendCommand(new AttackCommand());
|
||||
}
|
||||
|
||||
if (Input.IsActionJustPressed("use_item"))
|
||||
{
|
||||
// 发送查询
|
||||
var inventory = this.SendQuery(new GetInventoryQuery());
|
||||
if (inventory.HasItem("potion"))
|
||||
{
|
||||
this.SendCommand(new UseItemCommand("potion"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHealthChanged(int newHealth)
|
||||
{
|
||||
// 更新 UI 显示
|
||||
UpdateHealthBar(newHealth);
|
||||
}
|
||||
|
||||
private void OnPlayerLevelUp(PlayerLevelUpEvent e)
|
||||
{
|
||||
// 显示升级特效
|
||||
ShowLevelUpEffect();
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// 清理事件注册
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
|
||||
private void UpdateHealthBar(int health) { /* UI 更新逻辑 */ }
|
||||
private void ShowLevelUpEffect() { /* 特效逻辑 */ }
|
||||
}
|
||||
```
|
||||
|
||||
### UI 控制器示例
|
||||
|
||||
```csharp
|
||||
// UI 面板控制器
|
||||
public partial class MainMenuController : Control, IController
|
||||
{
|
||||
[Export] private Button _startButton;
|
||||
[Export] private Button _settingsButton;
|
||||
[Export] private Button _quitButton;
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 绑定按钮事件
|
||||
_startButton.Pressed += OnStartButtonPressed;
|
||||
_settingsButton.Pressed += OnSettingsButtonPressed;
|
||||
_quitButton.Pressed += OnQuitButtonPressed;
|
||||
|
||||
// 获取模型更新 UI
|
||||
var gameModel = this.GetModel<GameModel>();
|
||||
UpdateUI(gameModel);
|
||||
}
|
||||
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
// 通过命令启动游戏
|
||||
this.SendCommand<StartGameCommand>();
|
||||
}
|
||||
|
||||
private void OnSettingsButtonPressed()
|
||||
{
|
||||
// 查询当前设置
|
||||
var settings = this.SendQuery(new GetSettingsQuery());
|
||||
|
||||
// 打开设置面板
|
||||
var uiSystem = this.GetSystem<UISystem>();
|
||||
uiSystem.OpenSettingsPanel(settings);
|
||||
}
|
||||
|
||||
private void OnQuitButtonPressed()
|
||||
{
|
||||
// 发送退出命令
|
||||
this.SendCommand<QuitGameCommand>();
|
||||
}
|
||||
|
||||
private void UpdateUI(GameModel model) { /* UI 更新逻辑 */ }
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂交互控制器
|
||||
|
||||
```csharp
|
||||
// 战斗控制器
|
||||
public partial class CombatController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
private PlayerModel _playerModel;
|
||||
private CombatSystem _combatSystem;
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 缓存常用引用
|
||||
_playerModel = this.GetModel<PlayerModel>();
|
||||
_combatSystem = this.GetSystem<CombatSystem>();
|
||||
|
||||
// 注册多个事件
|
||||
this.RegisterEvent<EnemySpawnedEvent>(OnEnemySpawned)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<CombatEndedEvent>(OnCombatEnded)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 监听模型状态
|
||||
_playerModel.CombatState.Register(OnCombatStateChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnEnemySpawned(EnemySpawnedEvent e)
|
||||
{
|
||||
// 进入战斗状态
|
||||
this.SendCommand(new EnterCombatCommand(e.Enemy));
|
||||
}
|
||||
|
||||
private void OnCombatEnded(CombatEndedEvent e)
|
||||
{
|
||||
if (e.Victory)
|
||||
{
|
||||
// 查询奖励
|
||||
var rewards = this.SendQuery(new CalculateRewardsQuery(e.Enemy));
|
||||
|
||||
// 发放奖励
|
||||
this.SendCommand(new GiveRewardsCommand(rewards));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理失败
|
||||
this.SendCommand<GameOverCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCombatStateChanged(CombatState state)
|
||||
{
|
||||
// 根据战斗状态更新 UI
|
||||
var uiSystem = this.GetSystem<UISystem>();
|
||||
uiSystem.UpdateCombatUI(state);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 控制器职责
|
||||
|
||||
### ✅ 应该做的事
|
||||
|
||||
1. **处理用户输入**
|
||||
- 键盘、鼠标、触摸输入
|
||||
- UI 按钮点击
|
||||
- 手势识别
|
||||
|
||||
2. **协调视图和模型**
|
||||
- 监听模型变化更新视图
|
||||
- 将用户操作转换为命令
|
||||
|
||||
3. **管理界面逻辑**
|
||||
- UI 元素的显示/隐藏
|
||||
- 动画播放控制
|
||||
- 视觉反馈
|
||||
|
||||
4. **事件监听**
|
||||
- 注册关心的事件
|
||||
- 响应事件更新界面
|
||||
|
||||
### ❌ 不应该做的事
|
||||
|
||||
1. **不包含业务逻辑**
|
||||
- 业务逻辑应该在 System 中
|
||||
- 控制器只负责调用和协调
|
||||
|
||||
2. **不直接修改模型**
|
||||
- 应该通过 Command 修改模型
|
||||
- 避免直接访问 Model 的 setter
|
||||
|
||||
3. **不处理复杂计算**
|
||||
- 复杂计算应该在 Query 或 System 中
|
||||
- 控制器保持简洁
|
||||
|
||||
4. **不保存核心状态**
|
||||
- 核心状态应该在 Model 中
|
||||
- 控制器可以保存临时 UI 状态
|
||||
|
||||
## 生命周期管理
|
||||
|
||||
### 事件注销
|
||||
|
||||
```csharp
|
||||
public partial class MyController : Node, IController
|
||||
{
|
||||
// 使用 UnRegisterList 统一管理
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 所有事件注册都添加到列表
|
||||
this.RegisterEvent<GameEvent>(OnGameEvent)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.GetModel<PlayerModel>().Health.Register(OnHealthChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// 节点销毁时统一注销所有事件
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Godot 特定的生命周期
|
||||
|
||||
```csharp
|
||||
public partial class GameController : Node, IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
// 节点进入场景树
|
||||
public override void _Ready()
|
||||
{
|
||||
// 初始化控制器
|
||||
InitializeController();
|
||||
}
|
||||
|
||||
// 每帧更新
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
// 处理实时输入
|
||||
HandleInput();
|
||||
}
|
||||
|
||||
// 物理帧更新
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// 处理物理相关输入
|
||||
}
|
||||
|
||||
// 节点即将退出场景树
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// 清理资源
|
||||
CleanupController();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **一个控制器对应一个视图**
|
||||
- 每个 Godot 场景/节点有对应的控制器
|
||||
- 避免一个控制器管理多个不相关的视图
|
||||
|
||||
2. **使用依赖注入获取依赖**
|
||||
- 通过 `GetModel()`、`GetSystem()` 获取依赖
|
||||
- 不要在构造函数中获取,应在 `_Ready()` 中
|
||||
|
||||
3. **保持控制器轻量**
|
||||
- 复杂逻辑放在 Command、Query、System 中
|
||||
- 控制器只做协调和转发
|
||||
|
||||
4. **合理使用缓存**
|
||||
- 频繁使用的 Model、System 可以缓存引用
|
||||
- 平衡性能和内存占用
|
||||
|
||||
5. **统一管理事件注销**
|
||||
- 使用 `IUnRegisterList` 统一管理
|
||||
- 在 `_ExitTree()` 中统一注销
|
||||
|
||||
6. **命名规范**
|
||||
- 控制器类名:`XxxController`
|
||||
- 继承 Godot 节点:`Node`、`Control`、`Node2D` 等
|
||||
|
||||
## 常见模式
|
||||
|
||||
### 数据绑定模式
|
||||
|
||||
```csharp
|
||||
public partial class ScoreController : Label, IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 绑定模型数据到 UI
|
||||
this.GetModel<GameModel>()
|
||||
.Score
|
||||
.RegisterWithInitValue(score => Text = $"Score: {score}")
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态机模式
|
||||
|
||||
```csharp
|
||||
public partial class PlayerStateController : Node, IController
|
||||
{
|
||||
private Dictionary<PlayerState, Action> _stateHandlers;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_stateHandlers = new Dictionary<PlayerState, Action>
|
||||
{
|
||||
{ PlayerState.Idle, HandleIdleState },
|
||||
{ PlayerState.Moving, HandleMovingState },
|
||||
{ PlayerState.Attacking, HandleAttackingState }
|
||||
};
|
||||
|
||||
this.GetModel<PlayerModel>()
|
||||
.State
|
||||
.Register(OnStateChanged)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
|
||||
private void OnStateChanged(PlayerState state)
|
||||
{
|
||||
_stateHandlers[state]?.Invoke();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 提供架构访问能力
|
||||
- [`command`](../command/README.md) - 控制器发送命令执行业务逻辑
|
||||
- [`query`](../query/README.md) - 控制器发送查询获取数据
|
||||
- [`events`](../events/README.md) - 控制器注册事件监听变化
|
||||
- [`model`](../model/README.md) - 控制器读取模型数据
|
||||
- [`system`](../system/README.md) - 控制器调用系统服务
|
||||
- [`extensions`](../extensions/README.md) - 提供便捷的扩展方法
|
||||
21
framework/events/DefaultUnRegister.cs
Normal file
21
framework/events/DefaultUnRegister.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 默认注销器类,用于执行注销操作
|
||||
/// </summary>
|
||||
/// <param name="onUnRegister">注销时要执行的回调函数</param>
|
||||
public class DefaultUnRegister(Action onUnRegister): IUnRegister
|
||||
{
|
||||
private Action _mOnUnRegister = onUnRegister;
|
||||
|
||||
/// <summary>
|
||||
/// 执行注销操作,调用注册的回调函数并清理引用
|
||||
/// </summary>
|
||||
public void UnRegister()
|
||||
{
|
||||
// 调用注销回调函数并清理引用
|
||||
_mOnUnRegister.Invoke();
|
||||
_mOnUnRegister = null;
|
||||
}
|
||||
}
|
||||
31
framework/events/EasyEvent.cs
Normal file
31
framework/events/EasyEvent.cs
Normal file
@ -0,0 +1,31 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 简单事件类,用于注册、注销和触发无参事件回调
|
||||
/// </summary>
|
||||
public class EasyEvent
|
||||
{
|
||||
private Action _mOnEvent = () => { };
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件回调函数
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件回调函数</param>
|
||||
/// <returns>用于注销事件的 unregister 对象</returns>
|
||||
public IUnRegister Register(Action onEvent)
|
||||
{
|
||||
_mOnEvent += onEvent;
|
||||
return new DefaultUnRegister(() => { UnRegister(onEvent); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销已注册的事件回调函数
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注销的事件回调函数</param>
|
||||
public void UnRegister(Action onEvent) => _mOnEvent -= onEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 触发所有已注册的事件回调函数
|
||||
/// </summary>
|
||||
public void Trigger() => _mOnEvent?.Invoke();
|
||||
}
|
||||
154
framework/events/EasyEventGeneric.cs
Normal file
154
framework/events/EasyEventGeneric.cs
Normal file
@ -0,0 +1,154 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 泛型事件类,支持一个泛型参数 T 的事件注册、注销与触发。
|
||||
/// 实现了 IEasyEvent 接口以提供统一的事件操作接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件回调函数的第一个参数类型。</typeparam>
|
||||
public class EasyEvent<T> : IEasyEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储已注册的事件处理委托。
|
||||
/// 默认为空操作(no-op)委托,避免 null 检查。
|
||||
/// </summary>
|
||||
private Action<T> _mOnEvent = e => { };
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个事件监听器,并返回可用于取消注册的对象。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件处理方法。</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器。</returns>
|
||||
public IUnRegister Register(Action<T> onEvent)
|
||||
{
|
||||
_mOnEvent += onEvent;
|
||||
return new DefaultUnRegister(() => { UnRegister(onEvent); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消指定的事件监听器。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">需要被注销的事件处理方法。</param>
|
||||
public void UnRegister(Action<T> onEvent) => _mOnEvent -= onEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 触发所有已注册的事件处理程序,并传递参数 t。
|
||||
/// </summary>
|
||||
/// <param name="t">传递给事件处理程序的参数。</param>
|
||||
public void Trigger(T t) => _mOnEvent?.Invoke(t);
|
||||
|
||||
/// <summary>
|
||||
/// 显式实现 IEasyEvent 接口中的 Register 方法。
|
||||
/// 允许使用无参 Action 来订阅当前带参事件。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">无参事件处理方法。</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器。</returns>
|
||||
IUnRegister IEasyEvent.Register(Action onEvent)
|
||||
{
|
||||
return Register(Action);
|
||||
void Action(T _) => onEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支持两个泛型参数 T 和 TK 的事件类。
|
||||
/// 提供事件注册、注销和触发功能。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">第一个参数类型。</typeparam>
|
||||
/// <typeparam name="TK">第二个参数类型。</typeparam>
|
||||
public class EasyEvent<T, TK> : IEasyEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储已注册的双参数事件处理委托。
|
||||
/// 默认为空操作(no-op)委托。
|
||||
/// </summary>
|
||||
private Action<T, TK> _mOnEvent = (_, _) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个接受两个参数的事件监听器,并返回可用于取消注册的对象。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件处理方法。</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器。</returns>
|
||||
public IUnRegister Register(Action<T, TK> onEvent)
|
||||
{
|
||||
_mOnEvent += onEvent;
|
||||
return new DefaultUnRegister(() => { UnRegister(onEvent); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消指定的双参数事件监听器。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">需要被注销的事件处理方法。</param>
|
||||
public void UnRegister(Action<T, TK> onEvent) => _mOnEvent -= onEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 触发所有已注册的事件处理程序,并传递参数 t 和 k。
|
||||
/// </summary>
|
||||
/// <param name="t">第一个参数。</param>
|
||||
/// <param name="k">第二个参数。</param>
|
||||
public void Trigger(T t, TK k) => _mOnEvent?.Invoke(t, k);
|
||||
|
||||
/// <summary>
|
||||
/// 显式实现 IEasyEvent 接口中的 Register 方法。
|
||||
/// 允许使用无参 Action 来订阅当前带参事件。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">无参事件处理方法。</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器。</returns>
|
||||
IUnRegister IEasyEvent.Register(Action onEvent)
|
||||
{
|
||||
return Register(Action);
|
||||
void Action(T _, TK __) => onEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支持三个泛型参数 T、TK 和 TS 的事件类。
|
||||
/// 提供事件注册、注销和触发功能。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">第一个参数类型。</typeparam>
|
||||
/// <typeparam name="TK">第二个参数类型。</typeparam>
|
||||
/// <typeparam name="TS">第三个参数类型。</typeparam>
|
||||
public class EasyEvent<T, TK, TS> : IEasyEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储已注册的三参数事件处理委托。
|
||||
/// 默认为空操作(no-op)委托。
|
||||
/// </summary>
|
||||
private Action<T, TK, TS> _mOnEvent = (_, _, _) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个接受三个参数的事件监听器,并返回可用于取消注册的对象。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件处理方法。</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器。</returns>
|
||||
public IUnRegister Register(Action<T, TK, TS> onEvent)
|
||||
{
|
||||
_mOnEvent += onEvent;
|
||||
return new DefaultUnRegister(() => { UnRegister(onEvent); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消指定的三参数事件监听器。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">需要被注销的事件处理方法。</param>
|
||||
public void UnRegister(Action<T, TK, TS> onEvent) => _mOnEvent -= onEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 触发所有已注册的事件处理程序,并传递参数 t、k 和 s。
|
||||
/// </summary>
|
||||
/// <param name="t">第一个参数。</param>
|
||||
/// <param name="k">第二个参数。</param>
|
||||
/// <param name="s">第三个参数。</param>
|
||||
public void Trigger(T t, TK k, TS s) => _mOnEvent?.Invoke(t, k, s);
|
||||
|
||||
/// <summary>
|
||||
/// 显式实现 IEasyEvent 接口中的 Register 方法。
|
||||
/// 允许使用无参 Action 来订阅当前带参事件。
|
||||
/// </summary>
|
||||
/// <param name="onEvent">无参事件处理方法。</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器。</returns>
|
||||
IUnRegister IEasyEvent.Register(Action onEvent)
|
||||
{
|
||||
return Register(Action);
|
||||
void Action(T _, TK __, TS ___) => onEvent();
|
||||
}
|
||||
}
|
||||
67
framework/events/EasyEvents.cs
Normal file
67
framework/events/EasyEvents.cs
Normal file
@ -0,0 +1,67 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// EasyEvents事件管理器类,用于全局事件的注册、获取和管理
|
||||
/// 提供了类型安全的事件系统,支持泛型事件的自动创建和检索
|
||||
/// </summary>
|
||||
public class EasyEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局单例事件管理器实例
|
||||
/// </summary>
|
||||
private static readonly EasyEvents MGlobalEvents = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的全局事件实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须实现IEasyEvent接口</typeparam>
|
||||
/// <returns>指定类型的事件实例,如果未注册则返回默认值</returns>
|
||||
public static T Get<T>() where T : IEasyEvent => MGlobalEvents.GetEvent<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 注册指定类型的全局事件
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须实现IEasyEvent接口且具有无参构造函数</typeparam>
|
||||
public static void Register<T>() where T : IEasyEvent, new() => MGlobalEvents.AddEvent<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 存储事件类型与事件实例映射关系的字典
|
||||
/// </summary>
|
||||
private readonly Dictionary<Type, IEasyEvent> _mTypeEvents = new();
|
||||
|
||||
/// <summary>
|
||||
/// 添加指定类型的事件到事件字典中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须实现IEasyEvent接口且具有无参构造函数</typeparam>
|
||||
public void AddEvent<T>() where T : IEasyEvent, new() => _mTypeEvents.Add(typeof(T), new T());
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的事件实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须实现IEasyEvent接口</typeparam>
|
||||
/// <returns>指定类型的事件实例,如果不存在则返回默认值</returns>
|
||||
public T GetEvent<T>() where T : IEasyEvent
|
||||
{
|
||||
return _mTypeEvents.TryGetValue(typeof(T), out var e) ? (T)e : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的事件实例,如果不存在则创建并添加到事件字典中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须实现IEasyEvent接口且具有无参构造函数</typeparam>
|
||||
/// <returns>指定类型的事件实例</returns>
|
||||
public T GetOrAddEvent<T>() where T : IEasyEvent, new()
|
||||
{
|
||||
var eType = typeof(T);
|
||||
// 尝试从字典中获取事件实例
|
||||
if (_mTypeEvents.TryGetValue(eType, out var e))
|
||||
{
|
||||
return (T)e;
|
||||
}
|
||||
|
||||
// 如果不存在则创建新实例并添加到字典中
|
||||
var t = new T();
|
||||
_mTypeEvents.Add(eType, t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
10
framework/events/ICanRegisterEvent.cs
Normal file
10
framework/events/ICanRegisterEvent.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个可以注册事件的接口,继承自IBelongToArchitecture接口。
|
||||
/// 该接口用于标识那些能够注册事件的对象,通常在框架的事件系统中使用。
|
||||
/// 实现此接口的类型表明它属于某个架构组件,并具备事件注册的能力。
|
||||
/// </summary>
|
||||
public interface ICanRegisterEvent:IBelongToArchitecture;
|
||||
9
framework/events/ICanSendEvent.cs
Normal file
9
framework/events/ICanSendEvent.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个可以发送事件的接口,继承自IBelongToArchitecture接口。
|
||||
/// 该接口用于标识那些具备发送事件能力的类型,通常作为架构中事件发送功能的基础接口。
|
||||
/// </summary>
|
||||
public interface ICanSendEvent : IBelongToArchitecture;
|
||||
14
framework/events/IEasyEvent.cs
Normal file
14
framework/events/IEasyEvent.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 事件接口,定义了事件注册的基本功能
|
||||
/// </summary>
|
||||
public interface IEasyEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册事件处理函数
|
||||
/// </summary>
|
||||
/// <param name="onEvent">事件触发时要执行的回调函数</param>
|
||||
/// <returns>用于取消注册的句柄对象</returns>
|
||||
IUnRegister Register(Action onEvent);
|
||||
}
|
||||
12
framework/events/IUnRegister.cs
Normal file
12
framework/events/IUnRegister.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 提供注销功能的接口
|
||||
/// </summary>
|
||||
public interface IUnRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行注销操作
|
||||
/// </summary>
|
||||
void UnRegister();
|
||||
}
|
||||
12
framework/events/IUnRegisterList.cs
Normal file
12
framework/events/IUnRegisterList.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// 提供统一注销功能的接口,用于管理需要注销的对象列表
|
||||
/// </summary>
|
||||
public interface IUnRegisterList
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取需要注销的对象列表
|
||||
/// </summary>
|
||||
List<IUnRegister> UnregisterList { get; }
|
||||
}
|
||||
53
framework/events/OrEvent.cs
Normal file
53
framework/events/OrEvent.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using GFramework.framework.extensions;
|
||||
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// OrEvent类用于实现事件的或逻辑组合,当任意一个注册的事件触发时,都会触发OrEvent本身
|
||||
/// </summary>
|
||||
public class OrEvent : IUnRegisterList
|
||||
{
|
||||
/// <summary>
|
||||
/// 将指定的事件与当前OrEvent进行或逻辑组合
|
||||
/// </summary>
|
||||
/// <param name="easyEvent">要组合的事件对象</param>
|
||||
/// <returns>返回当前OrEvent实例,支持链式调用</returns>
|
||||
public OrEvent Or(IEasyEvent easyEvent)
|
||||
{
|
||||
easyEvent.Register(Trigger).AddToUnregisterList(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
private Action _mOnEvent = () => { };
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理函数
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件处理函数</param>
|
||||
/// <returns>返回一个可取消注册的对象</returns>
|
||||
public IUnRegister Register(Action onEvent)
|
||||
{
|
||||
_mOnEvent += onEvent;
|
||||
return new DefaultUnRegister(() => { UnRegister(onEvent); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册指定的事件处理函数
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要取消注册的事件处理函数</param>
|
||||
public void UnRegister(Action onEvent)
|
||||
{
|
||||
_mOnEvent -= onEvent;
|
||||
this.UnRegisterAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发所有已注册的事件处理函数
|
||||
/// </summary>
|
||||
private void Trigger() => _mOnEvent?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// 获取取消注册列表
|
||||
/// </summary>
|
||||
public List<IUnRegister> UnregisterList { get; } = new List<IUnRegister>();
|
||||
}
|
||||
503
framework/events/README.md
Normal file
503
framework/events/README.md
Normal file
@ -0,0 +1,503 @@
|
||||
# Events 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Events 包提供了一套完整的事件系统,实现了观察者模式(Observer Pattern)。通过事件系统,可以实现组件间的松耦合通信,支持无参和带参事件、事件注册/注销、以及灵活的事件组合。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### 1. [`IEasyEvent`](IEasyEvent.cs)
|
||||
|
||||
基础事件接口,定义了事件注册的基本功能。
|
||||
|
||||
**核心方法:**
|
||||
```csharp
|
||||
IUnRegister Register(Action onEvent); // 注册事件处理函数
|
||||
```
|
||||
|
||||
### 2. [`IUnRegister`](IUnRegister.cs)
|
||||
|
||||
注销接口,用于取消事件注册。
|
||||
|
||||
**核心方法:**
|
||||
```csharp
|
||||
void UnRegister(); // 执行注销操作
|
||||
```
|
||||
|
||||
### 3. [`IUnRegisterList`](IUnRegisterList.cs)
|
||||
|
||||
注销列表接口,用于批量管理注销对象。
|
||||
|
||||
**属性:**
|
||||
```csharp
|
||||
List<IUnRegister> UnregisterList { get; } // 获取注销列表
|
||||
```
|
||||
|
||||
### 4. 能力接口
|
||||
|
||||
- [`ICanRegisterEvent`](ICanRegisterEvent.cs) - 标记可以注册事件的组件
|
||||
- [`ICanSendEvent`](ICanSendEvent.cs) - 标记可以发送事件的组件
|
||||
|
||||
## 核心类
|
||||
|
||||
### 1. [`EasyEvent`](EasyEvent.cs)
|
||||
|
||||
无参事件类,支持注册、注销和触发无参事件。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建事件
|
||||
var onClicked = new EasyEvent();
|
||||
|
||||
// 注册监听
|
||||
var unregister = onClicked.Register(() =>
|
||||
{
|
||||
GD.Print("Button clicked!");
|
||||
});
|
||||
|
||||
// 触发事件
|
||||
onClicked.Trigger();
|
||||
|
||||
// 取消注册
|
||||
unregister.UnRegister();
|
||||
```
|
||||
|
||||
### 2. [`EasyEvent<T>`](EasyEventGeneric.cs)
|
||||
|
||||
单参数泛型事件类,支持一个参数的事件。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建带参数的事件
|
||||
var onScoreChanged = new EasyEvent<int>();
|
||||
|
||||
// 注册监听
|
||||
onScoreChanged.Register(newScore =>
|
||||
{
|
||||
GD.Print($"Score changed to: {newScore}");
|
||||
});
|
||||
|
||||
// 触发事件并传递参数
|
||||
onScoreChanged.Trigger(100);
|
||||
```
|
||||
|
||||
### 3. [`EasyEvent<T, TK>`](EasyEventGeneric.cs)
|
||||
|
||||
双参数泛型事件类。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 伤害事件:攻击者、伤害值
|
||||
var onDamageDealt = new EasyEvent<string, int>();
|
||||
|
||||
onDamageDealt.Register((attacker, damage) =>
|
||||
{
|
||||
GD.Print($"{attacker} dealt {damage} damage!");
|
||||
});
|
||||
|
||||
onDamageDealt.Trigger("Player", 50);
|
||||
```
|
||||
|
||||
### 4. [`EasyEvent<T, TK, TS>`](EasyEventGeneric.cs)
|
||||
|
||||
三参数泛型事件类。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 位置变化事件:对象、旧位置、新位置
|
||||
var onPositionChanged = new EasyEvent<string, Vector3, Vector3>();
|
||||
|
||||
onPositionChanged.Register((obj, oldPos, newPos) =>
|
||||
{
|
||||
GD.Print($"{obj} moved from {oldPos} to {newPos}");
|
||||
});
|
||||
|
||||
onPositionChanged.Trigger("Player", Vector3.Zero, new Vector3(10, 0, 0));
|
||||
```
|
||||
|
||||
### 5. [`EasyEvents`](EasyEvents.cs)
|
||||
|
||||
全局事件管理器,提供类型安全的事件注册和获取。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 注册全局事件类型
|
||||
EasyEvents.Register<GameStartEvent>();
|
||||
|
||||
// 获取事件实例
|
||||
var gameStartEvent = EasyEvents.Get<GameStartEvent>();
|
||||
|
||||
// 注册监听
|
||||
gameStartEvent.Register(() =>
|
||||
{
|
||||
GD.Print("Game started!");
|
||||
});
|
||||
|
||||
// 触发事件
|
||||
gameStartEvent.Trigger();
|
||||
```
|
||||
|
||||
### 6. [`TypeEventSystem`](TypeEventSystem.cs)
|
||||
|
||||
类型化事件系统,支持基于类型的事件发送和注册。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 使用全局事件系统
|
||||
var eventSystem = TypeEventSystem.Global;
|
||||
|
||||
// 注册类型化事件
|
||||
eventSystem.Register<PlayerDiedEvent>(e =>
|
||||
{
|
||||
GD.Print($"Player died at position: {e.Position}");
|
||||
});
|
||||
|
||||
// 发送事件(自动创建实例)
|
||||
eventSystem.Send<PlayerDiedEvent>();
|
||||
|
||||
// 发送事件(传递实例)
|
||||
eventSystem.Send(new PlayerDiedEvent
|
||||
{
|
||||
Position = new Vector3(10, 0, 5)
|
||||
});
|
||||
```
|
||||
|
||||
### 7. [`DefaultUnRegister`](DefaultUnRegister.cs)
|
||||
|
||||
默认注销器实现,封装注销回调。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
Action onUnregister = () => GD.Print("Unregistered");
|
||||
var unregister = new DefaultUnRegister(onUnregister);
|
||||
|
||||
// 执行注销
|
||||
unregister.UnRegister();
|
||||
```
|
||||
|
||||
### 8. [`OrEvent`](OrEvent.cs)
|
||||
|
||||
事件或运算组合器,当任意一个事件触发时触发。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var onAnyInput = new OrEvent()
|
||||
.Or(onKeyPressed)
|
||||
.Or(onMouseClicked)
|
||||
.Or(onTouchDetected);
|
||||
|
||||
// 当上述任意事件触发时,执行回调
|
||||
onAnyInput.Register(() =>
|
||||
{
|
||||
GD.Print("Input detected!");
|
||||
});
|
||||
```
|
||||
|
||||
## 在架构中使用事件
|
||||
|
||||
### 定义事件类
|
||||
|
||||
```csharp
|
||||
// 简单事件
|
||||
public struct GameStartedEvent { }
|
||||
|
||||
// 带数据的事件
|
||||
public struct PlayerDiedEvent
|
||||
{
|
||||
public Vector3 Position;
|
||||
public string Cause;
|
||||
}
|
||||
|
||||
// 复杂事件
|
||||
public struct LevelCompletedEvent
|
||||
{
|
||||
public int LevelId;
|
||||
public float CompletionTime;
|
||||
public int Score;
|
||||
public List<string> Achievements;
|
||||
}
|
||||
```
|
||||
|
||||
### Model 中发送事件
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 监听生命值变化
|
||||
Health.Register(newHealth =>
|
||||
{
|
||||
if (newHealth <= 0)
|
||||
{
|
||||
// 发送玩家死亡事件
|
||||
this.SendEvent(new PlayerDiedEvent
|
||||
{
|
||||
Position = Position,
|
||||
Cause = "Health depleted"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### System 中发送事件
|
||||
|
||||
```csharp
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit() { }
|
||||
|
||||
public void DealDamage(Character attacker, Character target, int damage)
|
||||
{
|
||||
target.Health -= damage;
|
||||
|
||||
// 发送伤害事件
|
||||
this.SendEvent(new DamageDealtEvent
|
||||
{
|
||||
Attacker = attacker.Name,
|
||||
Target = target.Name,
|
||||
Damage = damage
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controller 中注册事件
|
||||
|
||||
```csharp
|
||||
public partial class GameController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 注册多个事件
|
||||
this.RegisterEvent<GameStartedEvent>(OnGameStarted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<LevelCompletedEvent>(OnLevelCompleted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnGameStarted(GameStartedEvent e)
|
||||
{
|
||||
GD.Print("Game started!");
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
GD.Print($"Player died at {e.Position}: {e.Cause}");
|
||||
ShowGameOverScreen();
|
||||
}
|
||||
|
||||
private void OnLevelCompleted(LevelCompletedEvent e)
|
||||
{
|
||||
GD.Print($"Level {e.LevelId} completed! Score: {e.Score}");
|
||||
ShowVictoryScreen(e);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 1. 事件链式组合
|
||||
|
||||
```csharp
|
||||
// 使用 Or 组合多个事件
|
||||
var onAnyDamage = new OrEvent()
|
||||
.Or(onPhysicalDamage)
|
||||
.Or(onMagicDamage)
|
||||
.Or(onPoisonDamage);
|
||||
|
||||
onAnyDamage.Register(() =>
|
||||
{
|
||||
PlayDamageSound();
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 事件过滤
|
||||
|
||||
```csharp
|
||||
// 只处理高伤害事件
|
||||
this.RegisterEvent<DamageDealtEvent>(e =>
|
||||
{
|
||||
if (e.Damage >= 50)
|
||||
{
|
||||
ShowCriticalHitEffect();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 事件转发
|
||||
|
||||
```csharp
|
||||
public class EventBridge : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 将内部事件转发为公共事件
|
||||
this.RegisterEvent<InternalPlayerDiedEvent>(e =>
|
||||
{
|
||||
this.SendEvent(new PublicPlayerDiedEvent
|
||||
{
|
||||
PlayerId = e.Id,
|
||||
Timestamp = DateTime.Now
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 临时事件监听
|
||||
|
||||
```csharp
|
||||
public class TutorialController : Node, IController
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
// 只监听一次
|
||||
IUnRegister unregister = null;
|
||||
unregister = this.RegisterEvent<FirstEnemyKilledEvent>(e =>
|
||||
{
|
||||
ShowTutorialComplete();
|
||||
unregister?.UnRegister(); // 立即注销
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 条件事件
|
||||
|
||||
```csharp
|
||||
public class AchievementSystem : AbstractSystem
|
||||
{
|
||||
private int _killCount = 0;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<EnemyKilledEvent>(e =>
|
||||
{
|
||||
_killCount++;
|
||||
|
||||
// 条件满足时发送成就事件
|
||||
if (_killCount >= 100)
|
||||
{
|
||||
this.SendEvent(new AchievementUnlockedEvent
|
||||
{
|
||||
AchievementId = "kill_100_enemies"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 生命周期管理
|
||||
|
||||
### 使用 UnRegisterList
|
||||
|
||||
```csharp
|
||||
public class MyController : Node, IController
|
||||
{
|
||||
// 统一管理所有注销对象
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 所有注册都添加到列表
|
||||
this.RegisterEvent<Event1>(OnEvent1)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<Event2>(OnEvent2)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// 一次性注销所有
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 Godot 节点生命周期
|
||||
|
||||
```csharp
|
||||
public override void _Ready()
|
||||
{
|
||||
// 当节点退出场景树时自动注销
|
||||
this.RegisterEvent<GameEvent>(OnGameEvent)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **事件命名规范**
|
||||
- 使用过去式:`PlayerDiedEvent`、`LevelCompletedEvent`
|
||||
- 使用 `Event` 后缀:便于识别
|
||||
- 使用结构体:减少内存分配
|
||||
|
||||
2. **事件数据设计**
|
||||
- 只包含必要信息
|
||||
- 使用值类型(struct)提高性能
|
||||
- 避免传递可变引用
|
||||
|
||||
3. **避免事件循环**
|
||||
- 事件处理器中谨慎发送新事件
|
||||
- 使用命令打破循环依赖
|
||||
|
||||
4. **合理使用事件**
|
||||
- 用于通知状态变化
|
||||
- 用于跨模块通信
|
||||
- 不用于返回数据(使用 Query)
|
||||
|
||||
5. **注销管理**
|
||||
- 始终注销事件监听
|
||||
- 使用 `IUnRegisterList` 批量管理
|
||||
- 利用 Godot 节点生命周期
|
||||
|
||||
6. **性能考虑**
|
||||
- 避免频繁触发的事件(如每帧)
|
||||
- 事件处理器保持轻量
|
||||
- 使用结构体事件减少 GC
|
||||
|
||||
## 事件 vs 其他通信方式
|
||||
|
||||
| 方式 | 适用场景 | 优点 | 缺点 |
|
||||
|------|---------|------|------|
|
||||
| **Event** | 状态变化通知、跨模块通信 | 松耦合、一对多 | 难以追踪调用链 |
|
||||
| **Command** | 执行操作、修改状态 | 封装逻辑、可撤销 | 单向通信 |
|
||||
| **Query** | 查询数据 | 职责清晰、有返回值 | 同步调用 |
|
||||
| **BindableProperty** | UI 数据绑定 | 自动更新、响应式 | 仅限单一属性 |
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 提供全局事件系统
|
||||
- [`extensions`](../extensions/README.md) - 提供事件扩展方法
|
||||
- [`property`](../property/README.md) - 可绑定属性基于事件实现
|
||||
- [`controller`](../controller/README.md) - 控制器监听事件
|
||||
- [`model`](../model/README.md) - 模型发送事件
|
||||
- [`system`](../system/README.md) - 系统发送和监听事件
|
||||
23
framework/events/TypeEventSystem.cs
Normal file
23
framework/events/TypeEventSystem.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
/// <summary>
|
||||
/// TypeEventSystem
|
||||
/// </summary>
|
||||
public class TypeEventSystem
|
||||
{
|
||||
private readonly EasyEvents _mEvents = new();
|
||||
|
||||
public static readonly TypeEventSystem Global = new();
|
||||
|
||||
public void Send<T>() where T : new() => _mEvents.GetEvent<EasyEvent<T>>()?.Trigger(new T());
|
||||
|
||||
public void Send<T>(T e) => _mEvents.GetEvent<EasyEvent<T>>()?.Trigger(e);
|
||||
|
||||
public IUnRegister Register<T>(Action<T> onEvent) => _mEvents.GetOrAddEvent<EasyEvent<T>>().Register(onEvent);
|
||||
|
||||
public void UnRegister<T>(Action<T> onEvent)
|
||||
{
|
||||
var e = _mEvents.GetEvent<EasyEvent<T>>();
|
||||
e?.UnRegister(onEvent);
|
||||
}
|
||||
}
|
||||
39
framework/events/UnRegisterList.cs
Normal file
39
framework/events/UnRegisterList.cs
Normal file
@ -0,0 +1,39 @@
|
||||
namespace GFramework.framework.events;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册列表类,用于管理多个需要取消注册的对象
|
||||
/// </summary>
|
||||
public class UnRegisterList : IUnRegisterList
|
||||
{
|
||||
private readonly List<IUnRegister> _unRegisterList = [];
|
||||
|
||||
/// <summary>
|
||||
/// 向取消注册列表中添加一个新的可取消注册对象
|
||||
/// </summary>
|
||||
/// <param name="unRegister">需要添加到列表中的可取消注册对象</param>
|
||||
public void Add(IUnRegister unRegister)
|
||||
{
|
||||
_unRegisterList.Add(unRegister);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对列表中的所有对象执行取消注册操作,并清空列表
|
||||
/// </summary>
|
||||
public void UnRegisterAll()
|
||||
{
|
||||
// 遍历所有注册项并执行取消注册
|
||||
foreach (var t in _unRegisterList)
|
||||
{
|
||||
t.UnRegister();
|
||||
}
|
||||
|
||||
// 清空列表
|
||||
_unRegisterList.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取取消注册列表的只读属性
|
||||
/// </summary>
|
||||
public List<IUnRegister> UnregisterList { get; }
|
||||
}
|
||||
50
framework/extensions/CanGetExtensions.cs
Normal file
50
framework/extensions/CanGetExtensions.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.system;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
namespace GFramework.framework.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供获取模型对象扩展方法的静态类
|
||||
/// </summary>
|
||||
public static class CanGetModelExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 从架构中获取指定类型的模型实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的模型类型,必须实现IModel接口</typeparam>
|
||||
/// <param name="self">实现ICanGetModel接口的对象实例</param>
|
||||
/// <returns>指定类型的模型实例</returns>
|
||||
public static T GetModel<T>(this ICanGetModel self) where T : class, IModel =>
|
||||
self.GetArchitecture().GetModel<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提供获取系统对象扩展方法的静态类
|
||||
/// </summary>
|
||||
public static class CanGetSystemExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 从架构中获取指定类型的系统实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的系统类型,必须实现ISystem接口</typeparam>
|
||||
/// <param name="self">实现ICanGetSystem接口的对象实例</param>
|
||||
/// <returns>指定类型的系统实例</returns>
|
||||
public static T GetSystem<T>(this ICanGetSystem self) where T : class, ISystem =>
|
||||
self.GetArchitecture().GetSystem<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提供获取工具对象扩展方法的静态类
|
||||
/// </summary>
|
||||
public static class CanGetUtilityExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 从架构中获取指定类型的工具实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的工具类型,必须实现IUtility接口</typeparam>
|
||||
/// <param name="self">实现ICanGetUtility接口的对象实例</param>
|
||||
/// <returns>指定类型的工具实例</returns>
|
||||
public static T GetUtility<T>(this ICanGetUtility self) where T : class, IUtility =>
|
||||
self.GetArchitecture().GetUtility<T>();
|
||||
}
|
||||
28
framework/extensions/CanRegisterEventExtensions.cs
Normal file
28
framework/extensions/CanRegisterEventExtensions.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 事件注册扩展类,提供ICanRegisterEvent接口的扩展方法用于注册和注销事件
|
||||
/// </summary>
|
||||
public static class CanRegisterEventExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册指定类型的事件处理函数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件数据类型</typeparam>
|
||||
/// <param name="self">实现ICanRegisterEvent接口的对象实例</param>
|
||||
/// <param name="onEvent">事件处理回调函数</param>
|
||||
/// <returns>返回事件注销器接口,可用于后续注销事件</returns>
|
||||
public static IUnRegister RegisterEvent<T>(this ICanRegisterEvent self, Action<T> onEvent) =>
|
||||
self.GetArchitecture().RegisterEvent(onEvent);
|
||||
|
||||
/// <summary>
|
||||
/// 注销指定类型的事件处理函数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件数据类型</typeparam>
|
||||
/// <param name="self">实现ICanRegisterEvent接口的对象实例</param>
|
||||
/// <param name="onEvent">要注销的事件处理回调函数</param>
|
||||
public static void UnRegisterEvent<T>(this ICanRegisterEvent self, Action<T> onEvent) =>
|
||||
self.GetArchitecture().UnRegisterEvent(onEvent);
|
||||
}
|
||||
76
framework/extensions/CanSendExtensions.cs
Normal file
76
framework/extensions/CanSendExtensions.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using GFramework.framework.command;
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.query;
|
||||
|
||||
namespace GFramework.framework.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供发送命令功能的扩展类
|
||||
/// </summary>
|
||||
public static class CanSendCommandExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送指定类型的命令,该命令类型必须实现ICommand接口且具有无参构造函数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">命令类型,必须实现ICommand接口且具有无参构造函数</typeparam>
|
||||
/// <param name="self">实现ICanSendCommand接口的对象实例</param>
|
||||
public static void SendCommand<T>(this ICanSendCommand self) where T : ICommand, new() =>
|
||||
self.GetArchitecture().SendCommand(new T());
|
||||
|
||||
/// <summary>
|
||||
/// 发送指定的命令实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">命令类型,必须实现ICommand接口</typeparam>
|
||||
/// <param name="self">实现ICanSendCommand接口的对象实例</param>
|
||||
/// <param name="command">要发送的命令实例</param>
|
||||
public static void SendCommand<T>(this ICanSendCommand self, T command) where T : ICommand =>
|
||||
self.GetArchitecture().SendCommand(command);
|
||||
|
||||
/// <summary>
|
||||
/// 发送带有返回值的命令并获取执行结果
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令执行结果的类型</typeparam>
|
||||
/// <param name="self">实现ICanSendCommand接口的对象实例</param>
|
||||
/// <param name="command">要发送的命令实例,必须实现ICommand<TResult>接口</param>
|
||||
/// <returns>命令执行后的返回结果</returns>
|
||||
public static TResult SendCommand<TResult>(this ICanSendCommand self, ICommand<TResult> command) =>
|
||||
self.GetArchitecture().SendCommand(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提供发送事件功能的扩展类
|
||||
/// </summary>
|
||||
public static class CanSendEventExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送指定类型的事件,该事件类型必须具有无参构造函数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型,必须具有无参构造函数</typeparam>
|
||||
/// <param name="self">实现ICanSendEvent接口的对象实例</param>
|
||||
public static void SendEvent<T>(this ICanSendEvent self) where T : new() =>
|
||||
self.GetArchitecture().SendEvent<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 发送指定的事件实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="self">实现ICanSendEvent接口的对象实例</param>
|
||||
/// <param name="e">要发送的事件实例</param>
|
||||
public static void SendEvent<T>(this ICanSendEvent self, T e) => self.GetArchitecture().SendEvent(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提供发送查询功能的扩展类
|
||||
/// </summary>
|
||||
public static class CanSendQueryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送查询请求并获取查询结果
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果的类型</typeparam>
|
||||
/// <param name="self">实现ICanSendQuery接口的对象实例</param>
|
||||
/// <param name="query">要发送的查询实例,必须实现IQuery<TResult>接口</param>
|
||||
/// <returns>查询操作的返回结果</returns>
|
||||
public static TResult SendQuery<TResult>(this ICanSendQuery self, IQuery<TResult> query) =>
|
||||
self.GetArchitecture().SendQuery(query);
|
||||
}
|
||||
17
framework/extensions/OrEventExtensions.cs
Normal file
17
framework/extensions/OrEventExtensions.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供Or事件扩展方法的静态类
|
||||
/// </summary>
|
||||
public static class OrEventExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建一个OrEvent实例,将当前事件与指定事件进行逻辑或运算组合
|
||||
/// </summary>
|
||||
/// <param name="self">当前的IEasyEvent事件实例</param>
|
||||
/// <param name="e">要与当前事件进行或运算的另一个IEasyEvent事件实例</param>
|
||||
/// <returns>返回一个新的OrEvent实例,表示两个事件的或运算结果</returns>
|
||||
public static OrEvent Or(this IEasyEvent self, IEasyEvent e) => new OrEvent().Or(self).Or(e);
|
||||
}
|
||||
503
framework/extensions/README.md
Normal file
503
framework/extensions/README.md
Normal file
@ -0,0 +1,503 @@
|
||||
# Extensions 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Extensions 包提供了一系列扩展方法,简化了框架各个接口的使用。通过扩展方法,可以用更简洁的语法访问框架功能,提高代码可读性和开发效率。
|
||||
|
||||
## 扩展方法类别
|
||||
|
||||
### 1. 获取组件扩展 ([`CanGetExtensions.cs`](CanGetExtensions.cs))
|
||||
|
||||
为 [`ICanGetModel`](../model/ICanGetModel.cs)、[`ICanGetSystem`](../system/ICanGetSystem.cs)、[`ICanGetUtility`](../utility/ICanGetUtility.cs) 提供扩展方法。
|
||||
|
||||
#### CanGetModelExtension
|
||||
|
||||
```csharp
|
||||
public static T GetModel<T>(this ICanGetModel self) where T : class, IModel
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 在 Controller、Command、Query 中使用
|
||||
public class PlayerController : IController
|
||||
{
|
||||
public void UpdateUI()
|
||||
{
|
||||
// 直接通过 this 调用
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CanGetSystemExtension
|
||||
|
||||
```csharp
|
||||
public static T GetSystem<T>(this ICanGetSystem self) where T : class, ISystem
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class SaveCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 获取系统
|
||||
var saveSystem = this.GetSystem<SaveSystem>();
|
||||
var networkSystem = this.GetSystem<NetworkSystem>();
|
||||
|
||||
saveSystem.SaveGame();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CanGetUtilityExtension
|
||||
|
||||
```csharp
|
||||
public static T GetUtility<T>(this ICanGetUtility self) where T : class, IUtility
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class GameModel : AbstractModel
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 获取工具
|
||||
var timeUtility = this.GetUtility<TimeUtility>();
|
||||
var storageUtility = this.GetUtility<StorageUtility>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 发送命令扩展 ([`CanSendExtensions.cs`](CanSendExtensions.cs))
|
||||
|
||||
为 [`ICanSendCommand`](../command/ICanSendCommand.cs) 提供扩展方法。
|
||||
|
||||
#### CanSendCommandExtension
|
||||
|
||||
```csharp
|
||||
// 发送无参命令(通过类型)
|
||||
public static void SendCommand<T>(this ICanSendCommand self)
|
||||
where T : ICommand, new()
|
||||
|
||||
// 发送命令实例
|
||||
public static void SendCommand<T>(this ICanSendCommand self, T command)
|
||||
where T : ICommand
|
||||
|
||||
// 发送带返回值的命令
|
||||
public static TResult SendCommand<TResult>(this ICanSendCommand self,
|
||||
ICommand<TResult> command)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class GameController : IController
|
||||
{
|
||||
public void OnStartButtonClicked()
|
||||
{
|
||||
// 方式1:通过类型发送(需要无参构造函数)
|
||||
this.SendCommand<StartGameCommand>();
|
||||
|
||||
// 方式2:发送命令实例
|
||||
this.SendCommand(new LoadLevelCommand(levelId: 1));
|
||||
|
||||
// 方式3:发送带返回值的命令
|
||||
var score = this.SendCommand(new CalculateScoreCommand());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 发送事件扩展 ([`CanSendExtensions.cs`](CanSendExtensions.cs))
|
||||
|
||||
为 [`ICanSendEvent`](../events/ICanSendEvent.cs) 提供扩展方法。
|
||||
|
||||
#### CanSendEventExtension
|
||||
|
||||
```csharp
|
||||
// 发送无参事件
|
||||
public static void SendEvent<T>(this ICanSendEvent self) where T : new()
|
||||
|
||||
// 发送事件实例
|
||||
public static void SendEvent<T>(this ICanSendEvent self, T e)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
Health -= damage;
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
// 方式1:发送无参事件
|
||||
this.SendEvent<PlayerDiedEvent>();
|
||||
|
||||
// 方式2:发送带数据的事件
|
||||
this.SendEvent(new PlayerDiedEvent
|
||||
{
|
||||
Position = Position,
|
||||
Cause = "Damage"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 发送查询扩展 ([`CanSendExtensions.cs`](CanSendExtensions.cs))
|
||||
|
||||
为 [`ICanSendQuery`](../query/ICanSendQuery.cs) 提供扩展方法。
|
||||
|
||||
#### CanSendQueryExtension
|
||||
|
||||
```csharp
|
||||
public static TResult SendQuery<TResult>(this ICanSendQuery self,
|
||||
IQuery<TResult> query)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class InventoryController : IController
|
||||
{
|
||||
public void ShowInventory()
|
||||
{
|
||||
// 发送查询获取数据
|
||||
var items = this.SendQuery(new GetInventoryItemsQuery());
|
||||
var gold = this.SendQuery(new GetPlayerGoldQuery());
|
||||
|
||||
UpdateInventoryUI(items, gold);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 注册事件扩展 ([`CanRegisterEventExtensions.cs`](CanRegisterEventExtensions.cs))
|
||||
|
||||
为 [`ICanRegisterEvent`](../events/ICanRegisterEvent.cs) 提供扩展方法。
|
||||
|
||||
#### CanRegisterEventExtensions
|
||||
|
||||
```csharp
|
||||
// 注册事件
|
||||
public static IUnRegister RegisterEvent<T>(this ICanRegisterEvent self,
|
||||
Action<T> onEvent)
|
||||
|
||||
// 注销事件
|
||||
public static void UnRegisterEvent<T>(this ICanRegisterEvent self,
|
||||
Action<T> onEvent)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class GameController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<GameStartedEvent>(OnGameStarted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<PlayerLevelUpEvent>(OnPlayerLevelUp)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnGameStarted(GameStartedEvent e) { }
|
||||
private void OnPlayerLevelUp(PlayerLevelUpEvent e) { }
|
||||
}
|
||||
```
|
||||
|
||||
### 6. OrEvent 扩展 ([`OrEventExtensions.cs`](OrEventExtensions.cs))
|
||||
|
||||
为 [`IEasyEvent`](../events/IEasyEvent.cs) 提供事件组合功能。
|
||||
|
||||
#### OrEventExtensions
|
||||
|
||||
```csharp
|
||||
public static OrEvent Or(this IEasyEvent self, IEasyEvent e)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 组合多个事件:当任意一个触发时执行
|
||||
var onAnyInput = onKeyPressed.Or(onMouseClicked).Or(onTouchDetected);
|
||||
|
||||
onAnyInput.Register(() =>
|
||||
{
|
||||
GD.Print("Any input detected!");
|
||||
});
|
||||
|
||||
// 链式组合
|
||||
var onAnyDamage = onPhysicalDamage
|
||||
.Or(onMagicDamage)
|
||||
.Or(onPoisonDamage);
|
||||
```
|
||||
|
||||
### 7. UnRegister 扩展 ([`UnRegisterExtension.cs`](UnRegisterExtension.cs))
|
||||
|
||||
为 [`IUnRegister`](../events/IUnRegister.cs) 提供 Godot 生命周期绑定。
|
||||
|
||||
#### UnRegisterExtension
|
||||
|
||||
```csharp
|
||||
public static IUnRegister UnRegisterWhenNodeExitTree(this IUnRegister unRegister,
|
||||
Node node)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
#if GODOT
|
||||
public class PlayerController : Node, IController
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
// 当节点退出场景树时自动注销
|
||||
this.RegisterEvent<GameEvent>(OnGameEvent)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
|
||||
this.GetModel<PlayerModel>()
|
||||
.Health
|
||||
.Register(OnHealthChanged)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
|
||||
// 不需要手动在 _ExitTree 中注销
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
### 8. UnRegisterList 扩展 ([`UnRegisterListExtension.cs`](UnRegisterListExtension.cs))
|
||||
|
||||
为 [`IUnRegister`](../events/IUnRegister.cs) 和 [`IUnRegisterList`](../events/IUnRegisterList.cs) 提供批量管理功能。
|
||||
|
||||
#### UnRegisterListExtension
|
||||
|
||||
```csharp
|
||||
// 添加到注销列表
|
||||
public static void AddToUnregisterList(this IUnRegister self,
|
||||
IUnRegisterList unRegisterList)
|
||||
|
||||
// 批量注销
|
||||
public static void UnRegisterAll(this IUnRegisterList self)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class ComplexController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 所有注册都添加到列表中
|
||||
this.RegisterEvent<Event1>(OnEvent1)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<Event2>(OnEvent2)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.GetModel<Model1>().Property1.Register(OnProperty1Changed)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.GetModel<Model2>().Property2.Register(OnProperty2Changed)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// 一次性注销所有
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 完整使用示例
|
||||
|
||||
### Controller 示例
|
||||
|
||||
```csharp
|
||||
public partial class GameplayController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 使用扩展方法获取 Model
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameModel = this.GetModel<GameModel>();
|
||||
|
||||
// 使用扩展方法注册事件
|
||||
this.RegisterEvent<GameStartedEvent>(OnGameStarted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 监听可绑定属性
|
||||
playerModel.Health.Register(OnHealthChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 或者使用 Godot 特定的自动注销
|
||||
gameModel.Score.Register(OnScoreChanged)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (Input.IsActionJustPressed("attack"))
|
||||
{
|
||||
// 使用扩展方法发送命令
|
||||
this.SendCommand(new AttackCommand(targetId: 1));
|
||||
}
|
||||
|
||||
if (Input.IsActionJustPressed("use_item"))
|
||||
{
|
||||
// 使用扩展方法发送查询
|
||||
var hasPotion = this.SendQuery(new HasItemQuery("health_potion"));
|
||||
if (hasPotion)
|
||||
{
|
||||
this.SendCommand<UseHealthPotionCommand>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGameStarted(GameStartedEvent e)
|
||||
{
|
||||
GD.Print("Game started!");
|
||||
}
|
||||
|
||||
private void OnHealthChanged(int health)
|
||||
{
|
||||
UpdateHealthBar(health);
|
||||
}
|
||||
|
||||
private void OnScoreChanged(int score)
|
||||
{
|
||||
UpdateScoreDisplay(score);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Command 示例
|
||||
|
||||
```csharp
|
||||
public class ComplexGameCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 获取多个组件
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameSystem = this.GetSystem<GameSystem>();
|
||||
var timeUtility = this.GetUtility<TimeUtility>();
|
||||
|
||||
// 执行业务逻辑
|
||||
var currentTime = timeUtility.GetCurrentTime();
|
||||
gameSystem.ProcessGameLogic(playerModel, currentTime);
|
||||
|
||||
// 发送事件通知
|
||||
this.SendEvent(new GameStateChangedEvent());
|
||||
|
||||
// 可以发送其他命令(谨慎使用)
|
||||
this.SendCommand<SaveGameCommand>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### System 示例
|
||||
|
||||
```csharp
|
||||
public class AchievementSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled);
|
||||
this.RegisterEvent<LevelCompletedEvent>(OnLevelCompleted);
|
||||
}
|
||||
|
||||
private void OnEnemyKilled(EnemyKilledEvent e)
|
||||
{
|
||||
// 获取模型
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.EnemyKillCount++;
|
||||
|
||||
// 检查成就
|
||||
if (playerModel.EnemyKillCount >= 100)
|
||||
{
|
||||
// 发送成就解锁事件
|
||||
this.SendEvent(new AchievementUnlockedEvent
|
||||
{
|
||||
AchievementId = "kill_100_enemies"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLevelCompleted(LevelCompletedEvent e)
|
||||
{
|
||||
// 发送查询
|
||||
var completionTime = this.SendQuery(new GetLevelTimeQuery(e.LevelId));
|
||||
|
||||
if (completionTime < 60)
|
||||
{
|
||||
this.SendEvent(new AchievementUnlockedEvent
|
||||
{
|
||||
AchievementId = "speed_runner"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展方法的优势
|
||||
|
||||
1. **简洁的语法**:不需要显式调用 `GetArchitecture()`
|
||||
2. **类型安全**:编译时检查类型
|
||||
3. **可读性高**:代码意图更清晰
|
||||
4. **智能提示**:IDE 可以提供完整的自动补全
|
||||
5. **链式调用**:支持流式编程风格
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **确保引用命名空间**:
|
||||
```csharp
|
||||
using GFramework.framework.extensions;
|
||||
```
|
||||
|
||||
2. **理解扩展方法本质**:
|
||||
- 扩展方法是静态方法的语法糖
|
||||
- 不会改变原始类型的结构
|
||||
- 仅在编译时解析
|
||||
|
||||
3. **Godot 特定功能**:
|
||||
- `UnRegisterWhenNodeExitTree` 仅在 Godot 环境下可用
|
||||
- 使用 `#if GODOT` 编译指令控制
|
||||
|
||||
4. **性能考虑**:
|
||||
- 扩展方法本身无性能开销
|
||||
- 实际调用的是底层方法
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 扩展方法最终调用架构方法
|
||||
- [`command`](../command/README.md) - 命令发送扩展
|
||||
- [`query`](../query/README.md) - 查询发送扩展
|
||||
- [`events`](../events/README.md) - 事件注册和 Or 组合扩展
|
||||
- [`model`](../model/README.md) - 模型获取扩展
|
||||
- [`system`](../system/README.md) - 系统获取扩展
|
||||
- [`utility`](../utility/README.md) - 工具获取扩展
|
||||
26
framework/extensions/UnRegisterExtension.cs
Normal file
26
framework/extensions/UnRegisterExtension.cs
Normal file
@ -0,0 +1,26 @@
|
||||
#if GODOT
|
||||
using Godot;
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供取消注册扩展方法的静态类
|
||||
/// </summary>
|
||||
public static class UnRegisterExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 当节点退出场景树时自动取消注册监听器
|
||||
/// </summary>
|
||||
/// <param name="unRegister">需要在节点退出时被取消注册的监听器接口实例</param>
|
||||
/// <param name="node">Godot节点对象,当该节点退出场景树时触发取消注册操作</param>
|
||||
/// <returns>返回传入的原始IUnRegister实例,支持链式调用</returns>
|
||||
public static IUnRegister UnRegisterWhenNodeExitTree(this IUnRegister unRegister, Node node)
|
||||
{
|
||||
// 监听节点的TreeExiting事件,在节点即将退出场景树时执行取消注册操作
|
||||
node.TreeExiting += unRegister.UnRegister;
|
||||
return unRegister;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
34
framework/extensions/UnRegisterListExtension.cs
Normal file
34
framework/extensions/UnRegisterListExtension.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.extensions;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 扩展方法类,为IUnRegister和IUnRegisterList接口提供便捷的注册和注销功能
|
||||
/// </summary>
|
||||
public static class UnRegisterListExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 将指定的可注销对象添加到注销列表中
|
||||
/// </summary>
|
||||
/// <param name="self">要添加的可注销对象</param>
|
||||
/// <param name="unRegisterList">目标注销列表</param>
|
||||
public static void AddToUnregisterList(this IUnRegister self, IUnRegisterList unRegisterList) =>
|
||||
unRegisterList.UnregisterList.Add(self);
|
||||
|
||||
/// <summary>
|
||||
/// 注销列表中的所有对象并清空列表
|
||||
/// </summary>
|
||||
/// <param name="self">包含注销列表的对象</param>
|
||||
public static void UnRegisterAll(this IUnRegisterList self)
|
||||
{
|
||||
// 遍历注销列表中的所有对象并执行注销操作
|
||||
foreach (var unRegister in self.UnregisterList)
|
||||
{
|
||||
unRegister.UnRegister();
|
||||
}
|
||||
|
||||
// 清空注销列表
|
||||
self.UnregisterList.Clear();
|
||||
}
|
||||
}
|
||||
42
framework/ioc/IocContainer.cs
Normal file
42
framework/ioc/IocContainer.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GFramework.framework.ioc;
|
||||
|
||||
/// <summary>
|
||||
/// IOC容器类,用于管理对象的注册和获取
|
||||
/// </summary>
|
||||
public class IocContainer
|
||||
{
|
||||
private readonly Dictionary<Type, object> _mInstances = new();
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个实例到IOC容器中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实例的类型</typeparam>
|
||||
/// <param name="instance">要注册的实例对象</param>
|
||||
public void Register<T>(T instance)
|
||||
{
|
||||
var key = typeof(T);
|
||||
|
||||
_mInstances[key] = instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从IOC容器中获取指定类型的实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的实例类型</typeparam>
|
||||
/// <returns>返回指定类型的实例,如果未找到则返回null</returns>
|
||||
public T Get<T>() where T : class
|
||||
{
|
||||
var key = typeof(T);
|
||||
|
||||
// 尝试从字典中获取实例
|
||||
if (_mInstances.TryGetValue(key, out var retInstance))
|
||||
{
|
||||
return retInstance as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
446
framework/ioc/README.md
Normal file
446
framework/ioc/README.md
Normal file
@ -0,0 +1,446 @@
|
||||
# IoC 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
IoC(Inversion of Control,控制反转)包提供了一个轻量级的依赖注入容器,用于管理框架中各种组件的注册和获取。通过 IoC 容器,可以实现组件间的解耦,便于测试和维护。
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`IocContainer`](IocContainer.cs)
|
||||
|
||||
IoC 容器类,负责管理对象的注册和获取。
|
||||
|
||||
**主要功能:**
|
||||
- 注册实例到容器
|
||||
- 从容器中获取实例
|
||||
- 类型安全的依赖管理
|
||||
|
||||
## 核心方法
|
||||
|
||||
### 1. Register<T>
|
||||
|
||||
注册一个实例到容器中。
|
||||
|
||||
```csharp
|
||||
public void Register<T>(T instance)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `instance`: 要注册的实例对象
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 注册各种类型的实例
|
||||
container.Register<IPlayerModel>(new PlayerModel());
|
||||
container.Register<IGameSystem>(new GameSystem());
|
||||
container.Register<IStorageUtility>(new StorageUtility());
|
||||
```
|
||||
|
||||
### 2. Get<T>
|
||||
|
||||
从容器中获取指定类型的实例。
|
||||
|
||||
```csharp
|
||||
public T Get<T>() where T : class
|
||||
```
|
||||
|
||||
**返回值:**
|
||||
- 返回指定类型的实例,如果未找到则返回 `null`
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 获取已注册的实例
|
||||
var playerModel = container.Get<IPlayerModel>();
|
||||
var gameSystem = container.Get<IGameSystem>();
|
||||
|
||||
// 如果类型未注册,返回 null
|
||||
var unknownService = container.Get<IUnknownService>(); // null
|
||||
```
|
||||
|
||||
## 在框架中的使用
|
||||
|
||||
### Architecture 中的应用
|
||||
|
||||
IoC 容器是 [`Architecture`](../architecture/Architecture.cs) 类的核心组件,用于管理所有的 System、Model 和 Utility。
|
||||
|
||||
```csharp
|
||||
public abstract class Architecture<T> : IArchitecture where T : Architecture<T>, new()
|
||||
{
|
||||
// 内置 IoC 容器
|
||||
private readonly IocContainer _mContainer = new();
|
||||
|
||||
// 注册系统
|
||||
public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
|
||||
{
|
||||
system.SetArchitecture(this);
|
||||
_mContainer.Register(system); // 注册到容器
|
||||
// ...
|
||||
}
|
||||
|
||||
// 获取系统
|
||||
public TSystem GetSystem<TSystem>() where TSystem : class, ISystem
|
||||
=> _mContainer.Get<TSystem>(); // 从容器获取
|
||||
|
||||
// Model 和 Utility 同理
|
||||
}
|
||||
```
|
||||
|
||||
### 注册组件到容器
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture<GameArchitecture>
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 这些方法内部都使用 IoC 容器
|
||||
|
||||
// 注册 Model(存储游戏数据)
|
||||
RegisterModel<IPlayerModel>(new PlayerModel());
|
||||
RegisterModel<IInventoryModel>(new InventoryModel());
|
||||
|
||||
// 注册 System(业务逻辑)
|
||||
RegisterSystem<IGameplaySystem>(new GameplaySystem());
|
||||
RegisterSystem<ISaveSystem>(new SaveSystem());
|
||||
|
||||
// 注册 Utility(工具类)
|
||||
RegisterUtility<ITimeUtility>(new TimeUtility());
|
||||
RegisterUtility<IStorageUtility>(new StorageUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 从容器获取组件
|
||||
|
||||
```csharp
|
||||
// 通过扩展方法间接使用 IoC 容器
|
||||
public class PlayerController : IController
|
||||
{
|
||||
public void Start()
|
||||
{
|
||||
// GetModel 内部调用 Architecture.GetModel
|
||||
// Architecture.GetModel 内部调用 IocContainer.Get
|
||||
var playerModel = this.GetModel<IPlayerModel>();
|
||||
|
||||
var gameplaySystem = this.GetSystem<IGameplaySystem>();
|
||||
var timeUtility = this.GetUtility<ITimeUtility>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 内部实现
|
||||
|
||||
```csharp
|
||||
public class IocContainer
|
||||
{
|
||||
// 使用字典存储类型到实例的映射
|
||||
private readonly Dictionary<Type, object> _mInstances = new();
|
||||
|
||||
public void Register<T>(T instance)
|
||||
{
|
||||
var key = typeof(T);
|
||||
_mInstances[key] = instance; // 注册或覆盖
|
||||
}
|
||||
|
||||
public T Get<T>() where T : class
|
||||
{
|
||||
var key = typeof(T);
|
||||
if (_mInstances.TryGetValue(key, out var retInstance))
|
||||
{
|
||||
return retInstance as T; // 类型转换
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 注册流程
|
||||
|
||||
```
|
||||
用户代码
|
||||
↓
|
||||
RegisterSystem<T>(system)
|
||||
↓
|
||||
IocContainer.Register<T>(system)
|
||||
↓
|
||||
Dictionary[typeof(T)] = system
|
||||
```
|
||||
|
||||
### 获取流程
|
||||
|
||||
```
|
||||
用户代码
|
||||
↓
|
||||
this.GetSystem<T>()
|
||||
↓
|
||||
Architecture.GetSystem<T>()
|
||||
↓
|
||||
IocContainer.Get<T>()
|
||||
↓
|
||||
Dictionary.TryGetValue(typeof(T))
|
||||
↓
|
||||
返回实例或 null
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础使用
|
||||
|
||||
```csharp
|
||||
// 1. 创建容器
|
||||
var container = new IocContainer();
|
||||
|
||||
// 2. 注册服务
|
||||
var playerService = new PlayerService();
|
||||
container.Register<IPlayerService>(playerService);
|
||||
|
||||
// 3. 获取服务
|
||||
var service = container.Get<IPlayerService>();
|
||||
service.DoSomething();
|
||||
```
|
||||
|
||||
### 接口和实现分离
|
||||
|
||||
```csharp
|
||||
// 定义接口
|
||||
public interface IDataService
|
||||
{
|
||||
void SaveData(string data);
|
||||
string LoadData();
|
||||
}
|
||||
|
||||
// 实现类
|
||||
public class LocalDataService : IDataService
|
||||
{
|
||||
public void SaveData(string data) { /* 本地存储 */ }
|
||||
public string LoadData() { /* 本地加载 */ return ""; }
|
||||
}
|
||||
|
||||
public class CloudDataService : IDataService
|
||||
{
|
||||
public void SaveData(string data) { /* 云端存储 */ }
|
||||
public string LoadData() { /* 云端加载 */ return ""; }
|
||||
}
|
||||
|
||||
// 注册(可以根据配置选择不同实现)
|
||||
var container = new IocContainer();
|
||||
|
||||
#if CLOUD_SAVE
|
||||
container.Register<IDataService>(new CloudDataService());
|
||||
#else
|
||||
container.Register<IDataService>(new LocalDataService());
|
||||
#endif
|
||||
|
||||
// 使用(不需要关心具体实现)
|
||||
var dataService = container.Get<IDataService>();
|
||||
dataService.SaveData("game data");
|
||||
```
|
||||
|
||||
### 覆盖注册
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 首次注册
|
||||
container.Register<IConfig>(new DefaultConfig());
|
||||
|
||||
// 后续注册会覆盖
|
||||
container.Register<IConfig>(new CustomConfig());
|
||||
|
||||
// 获取到的是最后注册的实例
|
||||
var config = container.Get<IConfig>(); // CustomConfig
|
||||
```
|
||||
|
||||
## 设计特点
|
||||
|
||||
### 1. 简单轻量
|
||||
|
||||
- 只有两个核心方法:`Register` 和 `Get`
|
||||
- 基于字典实现,性能高效
|
||||
- 无复杂的依赖解析逻辑
|
||||
|
||||
### 2. 手动注册
|
||||
|
||||
- 需要显式注册每个组件
|
||||
- 不支持自动依赖注入
|
||||
- 完全可控的组件生命周期
|
||||
|
||||
### 3. 单例模式
|
||||
|
||||
- 每个类型只能注册一个实例
|
||||
- 适合管理全局单例服务
|
||||
- 后续注册会覆盖前面的实例
|
||||
|
||||
### 4. 类型安全
|
||||
|
||||
- 基于泛型,编译时类型检查
|
||||
- 避免字符串键导致的错误
|
||||
- IDE 友好,支持自动补全
|
||||
|
||||
## 与其他 IoC 容器的区别
|
||||
|
||||
### 本框架的 IocContainer
|
||||
|
||||
```csharp
|
||||
// 简单直接
|
||||
var container = new IocContainer();
|
||||
container.Register(new MyService());
|
||||
var service = container.Get<MyService>();
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- ✅ 简单易用
|
||||
- ✅ 性能高
|
||||
- ❌ 不支持构造函数注入
|
||||
- ❌ 不支持自动解析依赖
|
||||
- ❌ 不支持生命周期管理(Transient/Scoped/Singleton)
|
||||
|
||||
### 完整的 IoC 框架(如 Autofac、Zenject)
|
||||
|
||||
```csharp
|
||||
// 复杂但功能强大
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<MyService>().As<IMyService>().SingleInstance();
|
||||
builder.RegisterType<MyController>().WithParameter("config", config);
|
||||
var container = builder.Build();
|
||||
|
||||
// 自动解析依赖
|
||||
var controller = container.Resolve<MyController>();
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- ✅ 自动依赖注入
|
||||
- ✅ 生命周期管理
|
||||
- ✅ 复杂场景支持
|
||||
- ❌ 学习成本高
|
||||
- ❌ 性能开销大
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 在架构初始化时注册
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture<GameArchitecture>
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 按顺序注册组件
|
||||
// 1. 工具类(无依赖)
|
||||
RegisterUtility(new TimeUtility());
|
||||
RegisterUtility(new StorageUtility());
|
||||
|
||||
// 2. 模型(可能依赖工具)
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterModel(new GameModel());
|
||||
|
||||
// 3. 系统(可能依赖模型和工具)
|
||||
RegisterSystem(new GameplaySystem());
|
||||
RegisterSystem(new SaveSystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用接口类型注册
|
||||
|
||||
```csharp
|
||||
// ❌ 不推荐:直接使用实现类
|
||||
RegisterSystem(new ConcreteSystem());
|
||||
var system = GetSystem<ConcreteSystem>();
|
||||
|
||||
// ✅ 推荐:使用接口
|
||||
RegisterSystem<IGameSystem>(new ConcreteSystem());
|
||||
var system = GetSystem<IGameSystem>();
|
||||
```
|
||||
|
||||
### 3. 避免运行时频繁注册
|
||||
|
||||
```csharp
|
||||
// ❌ 不好:游戏运行时频繁注册
|
||||
void Update()
|
||||
{
|
||||
RegisterService(new TempService()); // 每帧创建
|
||||
}
|
||||
|
||||
// ✅ 好:在初始化时一次性注册
|
||||
protected override void Init()
|
||||
{
|
||||
RegisterService(new PersistentService());
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 检查 null 返回值
|
||||
|
||||
```csharp
|
||||
// 获取可能不存在的服务
|
||||
var service = container.Get<IOptionalService>();
|
||||
if (service != null)
|
||||
{
|
||||
service.DoSomething();
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Service not registered!");
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **类型唯一性**
|
||||
- 每个类型只能注册一个实例
|
||||
- 重复注册会覆盖之前的实例
|
||||
|
||||
2. **手动管理依赖顺序**
|
||||
- 组件的依赖关系需要手动保证
|
||||
- 先注册被依赖的组件
|
||||
|
||||
3. **无生命周期管理**
|
||||
- 实例一旦注册就一直存在
|
||||
- 需要手动管理实例的生命周期
|
||||
|
||||
4. **线程安全**
|
||||
- 当前实现非线程安全
|
||||
- 避免多线程同时访问
|
||||
|
||||
## 扩展可能性
|
||||
|
||||
如果需要更复杂的功能,可以扩展 `IocContainer`:
|
||||
|
||||
```csharp
|
||||
// 支持工厂模式
|
||||
public class AdvancedIocContainer : IocContainer
|
||||
{
|
||||
private Dictionary<Type, Func<object>> _factories = new();
|
||||
|
||||
public void RegisterFactory<T>(Func<T> factory) where T : class
|
||||
{
|
||||
_factories[typeof(T)] = () => factory();
|
||||
}
|
||||
|
||||
public new T Get<T>() where T : class
|
||||
{
|
||||
// 先尝试获取实例
|
||||
var instance = base.Get<T>();
|
||||
if (instance != null) return instance;
|
||||
|
||||
// 如果没有实例,尝试使用工厂创建
|
||||
if (_factories.TryGetValue(typeof(T), out var factory))
|
||||
{
|
||||
return factory() as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 使用 IoC 容器管理所有组件
|
||||
- [`model`](../model/README.md) - Model 通过 IoC 容器注册和获取
|
||||
- [`system`](../system/README.md) - System 通过 IoC 容器注册和获取
|
||||
- [`utility`](../utility/README.md) - Utility 通过 IoC 容器注册和获取
|
||||
37
framework/model/AbstractModel.cs
Normal file
37
framework/model/AbstractModel.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using GFramework.framework.architecture;
|
||||
using GFramework.framework.model;
|
||||
|
||||
namespace GFramework.framework.model;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象模型基类,实现IModel接口,提供模型的基本架构支持
|
||||
/// </summary>
|
||||
public abstract class AbstractModel : IModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 模型所属的架构实例
|
||||
/// </summary>
|
||||
protected IArchitecture Architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 获取模型所属的架构实例
|
||||
/// </summary>
|
||||
/// <returns>返回当前模型关联的架构对象</returns>
|
||||
public IArchitecture GetArchitecture() => Architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 设置模型所属的架构实例
|
||||
/// </summary>
|
||||
/// <param name="architecture">要关联到此模型的架构实例</param>
|
||||
public void SetArchitecture(IArchitecture architecture) => Architecture = architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化模型,调用抽象方法OnInit执行具体初始化逻辑
|
||||
/// </summary>
|
||||
void IModel.Init() => OnInit();
|
||||
|
||||
/// <summary>
|
||||
/// 抽象初始化方法,由子类实现具体的初始化逻辑
|
||||
/// </summary>
|
||||
protected abstract void OnInit();
|
||||
}
|
||||
9
framework/model/ICanGetModel.cs
Normal file
9
framework/model/ICanGetModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.model;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个接口,表示可以获取模型的架构组件。
|
||||
/// 该接口继承自IBelongToArchitecture,表明实现此接口的类型属于特定架构。
|
||||
/// </summary>
|
||||
public interface ICanGetModel : IBelongToArchitecture;
|
||||
16
framework/model/IModel.cs
Normal file
16
framework/model/IModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.rule;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
namespace GFramework.framework.model;
|
||||
|
||||
/// <summary>
|
||||
/// 模型接口,定义了模型的基本行为和功能
|
||||
/// </summary>
|
||||
public interface IModel : ICanSetArchitecture, ICanGetUtility, ICanSendEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化模型
|
||||
/// </summary>
|
||||
void Init();
|
||||
}
|
||||
140
framework/model/README.md
Normal file
140
framework/model/README.md
Normal file
@ -0,0 +1,140 @@
|
||||
# Model 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Model 包定义了数据模型层的接口和基类。Model 是 MVC 架构中的 M 层,负责管理应用程序的数据和状态。Model 层应该只包含数据和简单的数据逻辑,不包含复杂的业务逻辑。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IModel`](IModel.cs)
|
||||
|
||||
模型接口,定义了模型的基本行为和功能。
|
||||
|
||||
**继承的能力接口:**
|
||||
- [`ICanSetArchitecture`](../rule/ICanSetArchitecture.cs) - 可设置架构引用
|
||||
- [`ICanGetUtility`](../utility/ICanGetUtility.cs) - 可获取工具类
|
||||
- [`ICanSendEvent`](../events/ICanSendEvent.cs) - 可发送事件
|
||||
|
||||
**核心方法:**
|
||||
```csharp
|
||||
void Init(); // 初始化模型
|
||||
```
|
||||
|
||||
### [`ICanGetModel`](ICanGetModel.cs)
|
||||
|
||||
标记接口,表示实现者可以获取模型。继承自 [`IBelongToArchitecture`](../rule/IBelongToArchitecture.cs)。
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractModel`](AbstractModel.cs)
|
||||
|
||||
抽象模型基类,提供模型的基础实现。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
// 使用 BindableProperty 定义可监听的属性
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 监听生命值变化
|
||||
Health.Register(newHealth =>
|
||||
{
|
||||
if (newHealth <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Model 的职责
|
||||
|
||||
### ✅ 应该做的事
|
||||
|
||||
1. **存储数据和状态**
|
||||
2. **提供数据访问接口**
|
||||
3. **监听自身属性变化并做出响应**
|
||||
4. **发送数据变化事件**
|
||||
|
||||
### ❌ 不应该做的事
|
||||
|
||||
1. **不包含复杂业务逻辑** - 业务逻辑应该在 System 中
|
||||
2. **不直接依赖其他 Model** - 通过 Command 协调
|
||||
3. **不包含 UI 逻辑** - UI 逻辑应该在 Controller 中
|
||||
|
||||
## 常见 Model 示例
|
||||
|
||||
### 玩家数据模型
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<string> PlayerId { get; } = new("");
|
||||
public BindableProperty<string> PlayerName { get; } = new("Player");
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
public BindableProperty<int> Gold { get; } = new(0);
|
||||
|
||||
public Vector3 Position { get; set; }
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
Health.Register(hp =>
|
||||
{
|
||||
if (hp <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 游戏状态模型
|
||||
|
||||
```csharp
|
||||
public class GameModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<GameState> State { get; } = new(GameState.Menu);
|
||||
public BindableProperty<int> Score { get; } = new(0);
|
||||
public BindableProperty<int> HighScore { get; } = new(0);
|
||||
public BindableProperty<int> CurrentLevel { get; } = new(1);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
Score.Register(newScore =>
|
||||
{
|
||||
if (newScore > HighScore.Value)
|
||||
{
|
||||
HighScore.Value = newScore;
|
||||
this.SendEvent(new NewHighScoreEvent { Score = newScore });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用 BindableProperty 存储需要监听的数据**
|
||||
2. **Model 之间不要直接调用,通过 Command/System 协调**
|
||||
3. **复杂计算和业务逻辑放在 System 中**
|
||||
4. **使用事件通知数据的重要变化**
|
||||
5. **保持 Model 简单纯粹,只做数据管理**
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 提供 Model 的注册和获取
|
||||
- [`property`](../property/README.md) - BindableProperty 用于定义可监听属性
|
||||
- [`events`](../events/README.md) - Model 发送事件通知变化
|
||||
- [`utility`](../utility/README.md) - Model 可以使用工具类
|
||||
- [`extensions`](../extensions/README.md) - 提供 GetModel 等扩展方法
|
||||
120
framework/property/BindableProperty.cs
Normal file
120
framework/property/BindableProperty.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.property;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 可绑定属性类,用于实现数据绑定功能
|
||||
/// </summary>
|
||||
/// <typeparam name="T">属性值的类型</typeparam>
|
||||
/// <param name="defaultValue">属性的默认值</param>
|
||||
public class BindableProperty<T>(T defaultValue = default) : IBindableProperty<T>
|
||||
{
|
||||
protected T MValue = defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置属性值比较器,默认使用Equals方法进行比较
|
||||
/// </summary>
|
||||
public static Func<T, T, bool> Comparer { get; set; } = (a, b) => a.Equals(b);
|
||||
|
||||
/// <summary>
|
||||
/// 设置自定义比较器
|
||||
/// </summary>
|
||||
/// <param name="comparer">用于比较两个值是否相等的函数</param>
|
||||
/// <returns>当前可绑定属性实例</returns>
|
||||
public BindableProperty<T> WithComparer(Func<T, T, bool> comparer)
|
||||
{
|
||||
Comparer = comparer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置属性值,当值发生变化时会触发注册的回调事件
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => GetValue();
|
||||
set
|
||||
{
|
||||
// 使用 default(T) 替代 null 比较,避免 SonarQube 警告
|
||||
if (EqualityComparer<T>.Default.Equals(value, default) &&
|
||||
EqualityComparer<T>.Default.Equals(MValue, default))
|
||||
return;
|
||||
|
||||
// 若新值与旧值相等则不执行后续操作
|
||||
if (!EqualityComparer<T>.Default.Equals(value, default) && Comparer(value, MValue))
|
||||
return;
|
||||
|
||||
SetValue(value);
|
||||
_mOnValueChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置属性值的虚方法,可在子类中重写
|
||||
/// </summary>
|
||||
/// <param name="newValue">新的属性值</param>
|
||||
protected virtual void SetValue(T newValue) => MValue = newValue;
|
||||
|
||||
/// <summary>
|
||||
/// 获取属性值的虚方法,可在子类中重写
|
||||
/// </summary>
|
||||
/// <returns>当前属性值</returns>
|
||||
protected virtual T GetValue() => MValue;
|
||||
|
||||
/// <summary>
|
||||
/// 直接设置属性值而不触发事件
|
||||
/// </summary>
|
||||
/// <param name="newValue">新的属性值</param>
|
||||
public void SetValueWithoutEvent(T newValue) => MValue = newValue;
|
||||
|
||||
private Action<T> _mOnValueChanged = (_) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 注册属性值变化事件回调
|
||||
/// </summary>
|
||||
/// <param name="onValueChanged">属性值变化时的回调函数</param>
|
||||
/// <returns>可用于取消注册的接口</returns>
|
||||
public IUnRegister Register(Action<T> onValueChanged)
|
||||
{
|
||||
_mOnValueChanged += onValueChanged;
|
||||
return new BindablePropertyUnRegister<T>(this, onValueChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册属性值变化事件回调,并立即调用回调函数传递当前值
|
||||
/// </summary>
|
||||
/// <param name="action">属性值变化时的回调函数</param>
|
||||
/// <returns>可用于取消注册的接口</returns>
|
||||
public IUnRegister RegisterWithInitValue(Action<T> action)
|
||||
{
|
||||
action(MValue);
|
||||
return Register(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册属性值变化事件回调
|
||||
/// </summary>
|
||||
/// <param name="onValueChanged">要取消注册的回调函数</param>
|
||||
public void UnRegister(Action<T> onValueChanged) => _mOnValueChanged -= onValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 实现IEasyEvent接口的注册方法,将无参事件转换为有参事件处理
|
||||
/// </summary>
|
||||
/// <param name="onEvent">无参事件回调</param>
|
||||
/// <returns>可用于取消注册的接口</returns>
|
||||
IUnRegister IEasyEvent.Register(Action onEvent)
|
||||
{
|
||||
return Register(Action);
|
||||
void Action(T _) => onEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回属性值的字符串表示形式
|
||||
/// </summary>
|
||||
/// <returns>属性值的字符串表示</returns>
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
|
||||
38
framework/property/BindablePropertyUnRegister.cs
Normal file
38
framework/property/BindablePropertyUnRegister.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.property;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 可绑定属性注销器类,用于取消注册可绑定属性的值变化监听
|
||||
/// </summary>
|
||||
/// <typeparam name="T">可绑定属性的值类型</typeparam>
|
||||
/// <param name="bindableProperty">需要注销的可绑定属性实例</param>
|
||||
/// <param name="onValueChanged">需要注销的值变化回调函数</param>
|
||||
public class BindablePropertyUnRegister<T>(BindableProperty<T> bindableProperty, Action<T> onValueChanged)
|
||||
: IUnRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置可绑定属性实例
|
||||
/// </summary>
|
||||
public BindableProperty<T> BindableProperty { get; set; } = bindableProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置值变化时的回调函数
|
||||
/// </summary>
|
||||
public Action<T> OnValueChanged { get; set; } = onValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 执行注销操作,取消注册值变化监听并清理引用
|
||||
/// </summary>
|
||||
public void UnRegister()
|
||||
{
|
||||
// 调用可绑定属性的注销方法,传入值变化回调函数
|
||||
BindableProperty.UnRegister(OnValueChanged);
|
||||
// 清理属性引用
|
||||
BindableProperty = null;
|
||||
// 清理回调函数引用
|
||||
OnValueChanged = null;
|
||||
}
|
||||
}
|
||||
19
framework/property/IBindableProperty.cs
Normal file
19
framework/property/IBindableProperty.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace GFramework.framework.property;
|
||||
|
||||
/// <summary>
|
||||
/// 可绑定属性接口,继承自只读可绑定属性接口,提供可读写的属性绑定功能
|
||||
/// </summary>
|
||||
/// <typeparam name="T">属性值的类型</typeparam>
|
||||
public interface IBindableProperty<T> : IReadonlyBindableProperty<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置属性的值
|
||||
/// </summary>
|
||||
new T Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置属性值但不触发事件通知
|
||||
/// </summary>
|
||||
/// <param name="newValue">要设置的新值</param>
|
||||
void SetValueWithoutEvent(T newValue);
|
||||
}
|
||||
37
framework/property/IReadonlyBindableProperty.cs
Normal file
37
framework/property/IReadonlyBindableProperty.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using GFramework.framework.events;
|
||||
|
||||
namespace GFramework.framework.property;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 只读可绑定属性接口,提供属性值的读取和变更监听功能
|
||||
/// </summary>
|
||||
/// <typeparam name="T">属性值的类型</typeparam>
|
||||
public interface IReadonlyBindableProperty<out T> : IEasyEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取属性的当前值
|
||||
/// </summary>
|
||||
T Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 注册属性值变更回调,并立即执行一次初始值的回调
|
||||
/// </summary>
|
||||
/// <param name="action">属性值变更时执行的回调函数,参数为新的属性值</param>
|
||||
/// <returns>用于取消注册的句柄对象</returns>
|
||||
IUnRegister RegisterWithInitValue(Action<T> action);
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册属性值变更回调
|
||||
/// </summary>
|
||||
/// <param name="onValueChanged">要取消注册的回调函数</param>
|
||||
void UnRegister(Action<T> onValueChanged);
|
||||
|
||||
/// <summary>
|
||||
/// 注册属性值变更回调
|
||||
/// </summary>
|
||||
/// <param name="onValueChanged">属性值变更时执行的回调函数,参数为新的属性值</param>
|
||||
/// <returns>用于取消注册的句柄对象</returns>
|
||||
IUnRegister Register(Action<T> onValueChanged);
|
||||
}
|
||||
297
framework/property/README.md
Normal file
297
framework/property/README.md
Normal file
@ -0,0 +1,297 @@
|
||||
# Property 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现 MVVM 模式和数据绑定的核心组件。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IReadonlyBindableProperty<T>`](IReadonlyBindableProperty.cs)
|
||||
|
||||
只读可绑定属性接口,提供属性值的读取和变更监听功能。
|
||||
|
||||
**核心成员:**
|
||||
```csharp
|
||||
T Value { get; } // 获取属性值
|
||||
IUnRegister Register(Action<T> onValueChanged); // 注册监听
|
||||
IUnRegister RegisterWithInitValue(Action<T> action); // 注册并立即回调当前值
|
||||
void UnRegister(Action<T> onValueChanged); // 取消监听
|
||||
```
|
||||
|
||||
### [`IBindableProperty<T>`](IBindableProperty.cs)
|
||||
|
||||
可绑定属性接口,继承自只读接口,增加了修改能力。
|
||||
|
||||
**核心成员:**
|
||||
```csharp
|
||||
new T Value { get; set; } // 可读写的属性值
|
||||
void SetValueWithoutEvent(T newValue); // 设置值但不触发事件
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`BindableProperty<T>`](BindableProperty.cs)
|
||||
|
||||
可绑定属性的完整实现。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建可绑定属性
|
||||
var health = new BindableProperty<int>(100);
|
||||
|
||||
// 监听值变化
|
||||
var unregister = health.Register(newValue =>
|
||||
{
|
||||
GD.Print($"Health changed to: {newValue}");
|
||||
});
|
||||
|
||||
// 修改值(会触发监听器)
|
||||
health.Value = 50; // 输出: Health changed to: 50
|
||||
|
||||
// 取消监听
|
||||
unregister.UnRegister();
|
||||
|
||||
// 设置值但不触发事件
|
||||
health.SetValueWithoutEvent(75);
|
||||
```
|
||||
|
||||
**高级功能:**
|
||||
|
||||
```csharp
|
||||
// 1. 注册并立即获得当前值
|
||||
health.RegisterWithInitValue(value =>
|
||||
{
|
||||
GD.Print($"Current health: {value}"); // 立即输出当前值
|
||||
// 后续值变化时也会调用
|
||||
});
|
||||
|
||||
// 2. 自定义比较器
|
||||
var position = new BindableProperty<Vector3>(Vector3.Zero)
|
||||
.WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等
|
||||
|
||||
// 3. 链式调用
|
||||
health.Value = 100;
|
||||
```
|
||||
|
||||
### [`BindablePropertyUnRegister<T>`](BindablePropertyUnRegister.cs)
|
||||
|
||||
可绑定属性的注销器,负责清理监听。
|
||||
|
||||
## 在 Model 中使用
|
||||
|
||||
### 定义可绑定属性
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
// 可读写属性
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
|
||||
// 只读属性(外部只能读取和监听)
|
||||
public IReadonlyBindableProperty<int> ReadonlyHealth => Health;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 内部监听属性变化
|
||||
Health.Register(hp =>
|
||||
{
|
||||
if (hp <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 在 Controller 中监听
|
||||
|
||||
### UI 数据绑定
|
||||
|
||||
```csharp
|
||||
public partial class PlayerUI : Control, IController
|
||||
{
|
||||
[Export] private Label _healthLabel;
|
||||
[Export] private Label _nameLabel;
|
||||
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 绑定生命值到UI(立即显示当前值)
|
||||
playerModel.Health
|
||||
.RegisterWithInitValue(health =>
|
||||
{
|
||||
_healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}";
|
||||
})
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 绑定名称
|
||||
playerModel.Name
|
||||
.RegisterWithInitValue(name =>
|
||||
{
|
||||
_nameLabel.Text = name;
|
||||
})
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见使用模式
|
||||
|
||||
### 1. 双向绑定
|
||||
|
||||
```csharp
|
||||
// Model
|
||||
public class SettingsModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<float> MasterVolume { get; } = new(1.0f);
|
||||
protected override void OnInit() { }
|
||||
}
|
||||
|
||||
// UI Controller
|
||||
public partial class VolumeSlider : HSlider, IController
|
||||
{
|
||||
private BindableProperty<float> _volumeProperty;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_volumeProperty = this.GetModel<SettingsModel>().MasterVolume;
|
||||
|
||||
// Model -> UI
|
||||
_volumeProperty.RegisterWithInitValue(vol => Value = vol)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
|
||||
// UI -> Model
|
||||
ValueChanged += newValue => _volumeProperty.Value = (float)newValue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 计算属性
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
public BindableProperty<float> HealthPercent { get; } = new(1.0f);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 自动计算百分比
|
||||
Action updatePercent = () =>
|
||||
{
|
||||
HealthPercent.Value = (float)Health.Value / MaxHealth.Value;
|
||||
};
|
||||
|
||||
Health.Register(_ => updatePercent());
|
||||
MaxHealth.Register(_ => updatePercent());
|
||||
|
||||
updatePercent(); // 初始计算
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 属性验证
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
private BindableProperty<int> _health = new(100);
|
||||
|
||||
public BindableProperty<int> Health
|
||||
{
|
||||
get => _health;
|
||||
set
|
||||
{
|
||||
// 限制范围
|
||||
var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value);
|
||||
_health.Value = clampedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
|
||||
protected override void OnInit() { }
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 条件监听
|
||||
|
||||
```csharp
|
||||
public class CombatController : Node, IController
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 只在生命值低于30%时显示警告
|
||||
playerModel.Health.Register(hp =>
|
||||
{
|
||||
if (hp < playerModel.MaxHealth.Value * 0.3f)
|
||||
{
|
||||
ShowLowHealthWarning();
|
||||
}
|
||||
else
|
||||
{
|
||||
HideLowHealthWarning();
|
||||
}
|
||||
}).UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 避免频繁触发
|
||||
|
||||
```csharp
|
||||
// 使用 SetValueWithoutEvent 批量修改
|
||||
public void LoadPlayerData(SaveData data)
|
||||
{
|
||||
// 临时关闭事件
|
||||
Health.SetValueWithoutEvent(data.Health);
|
||||
Mana.SetValueWithoutEvent(data.Mana);
|
||||
Gold.SetValueWithoutEvent(data.Gold);
|
||||
|
||||
// 最后统一触发一次更新事件
|
||||
this.SendEvent(new PlayerDataLoadedEvent());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 自定义比较器
|
||||
|
||||
```csharp
|
||||
// 避免浮点数精度问题导致的频繁触发
|
||||
var position = new BindableProperty<Vector3>()
|
||||
.WithComparer((a, b) => a.DistanceTo(b) < 0.001f);
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层
|
||||
2. **使用只读接口暴露** - 防止外部随意修改
|
||||
3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree
|
||||
4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值
|
||||
5. **避免循环依赖** - 属性监听器中修改其他属性要小心
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`model`](../model/README.md) - Model 中大量使用 BindableProperty
|
||||
- [`events`](../events/README.md) - BindableProperty 基于事件系统实现
|
||||
- [`controller`](../controller/README.md) - Controller 监听属性变化更新 UI
|
||||
- [`extensions`](../extensions/README.md) - 提供便捷的注销扩展方法
|
||||
36
framework/query/AbstractQuery.cs
Normal file
36
framework/query/AbstractQuery.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using GFramework.framework.architecture;
|
||||
|
||||
namespace GFramework.framework.query;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象查询类,提供查询操作的基础实现
|
||||
/// </summary>
|
||||
/// <typeparam name="T">查询结果的类型</typeparam>
|
||||
public abstract class AbstractQuery<T> : IQuery<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行查询操作
|
||||
/// </summary>
|
||||
/// <returns>查询结果</returns>
|
||||
public T Do() => OnDo();
|
||||
|
||||
/// <summary>
|
||||
/// 抽象方法,由子类实现具体的查询逻辑
|
||||
/// </summary>
|
||||
/// <returns>查询结果</returns>
|
||||
protected abstract T OnDo();
|
||||
|
||||
private IArchitecture _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 获取架构实例
|
||||
/// </summary>
|
||||
/// <returns>架构实例</returns>
|
||||
public IArchitecture GetArchitecture() => _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 设置架构实例
|
||||
/// </summary>
|
||||
/// <param name="architecture">要设置的架构实例</param>
|
||||
public void SetArchitecture(IArchitecture architecture) => _mArchitecture = architecture;
|
||||
}
|
||||
9
framework/query/ICanSendQuery.cs
Normal file
9
framework/query/ICanSendQuery.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.query;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个可以发送查询的接口契约
|
||||
/// 该接口继承自IBelongToArchitecture,表示实现此接口的类型属于某个架构组件
|
||||
/// </summary>
|
||||
public interface ICanSendQuery : IBelongToArchitecture;
|
||||
20
framework/query/IQuery.cs
Normal file
20
framework/query/IQuery.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.rule;
|
||||
using GFramework.framework.system;
|
||||
|
||||
namespace GFramework.framework.query;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 查询接口,定义了执行查询操作的契约
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果的类型</typeparam>
|
||||
public interface IQuery<out TResult> : ICanSetArchitecture, ICanGetModel, ICanGetSystem,
|
||||
ICanSendQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行查询操作并返回结果
|
||||
/// </summary>
|
||||
/// <returns>查询的结果,类型为 TResult</returns>
|
||||
TResult Do();
|
||||
}
|
||||
395
framework/query/README.md
Normal file
395
framework/query/README.md
Normal file
@ -0,0 +1,395 @@
|
||||
# Query 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Query 包实现了 CQRS(命令查询职责分离)模式中的查询部分。Query 用于封装数据查询逻辑,与 Command 不同的是,Query 有返回值且不应该修改系统状态。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`ICanSendQuery`](ICanSendQuery.cs)
|
||||
|
||||
标记接口,表示该类型可以发送查询。
|
||||
|
||||
**继承关系:**
|
||||
```csharp
|
||||
public interface ICanSendQuery : IBelongToArchitecture
|
||||
```
|
||||
|
||||
### [`IQuery<TResult>`](IQuery.cs)
|
||||
|
||||
查询接口,定义了查询的基本契约。
|
||||
|
||||
**核心成员:**
|
||||
```csharp
|
||||
TResult Do(); // 执行查询并返回结果
|
||||
```
|
||||
|
||||
**继承的能力:**
|
||||
- `ICanSetArchitecture` - 可设置架构
|
||||
- `ICanGetModel` - 可获取 Model
|
||||
- `ICanGetSystem` - 可获取 System
|
||||
- `ICanSendQuery` - 可发送其他 Query
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractQuery<T>`](AbstractQuery.cs)
|
||||
|
||||
抽象查询基类,提供了查询的基础实现。
|
||||
|
||||
**使用方式:**
|
||||
```csharp
|
||||
public abstract class AbstractQuery<T> : IQuery<T>
|
||||
{
|
||||
public T Do() => OnDo();
|
||||
protected abstract T OnDo(); // 子类实现查询逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义查询
|
||||
|
||||
```csharp
|
||||
// 查询玩家金币数量
|
||||
public class GetPlayerGoldQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
return this.GetModel<PlayerModel>().Gold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询玩家是否死亡
|
||||
public class IsPlayerDeadQuery : AbstractQuery<bool>
|
||||
{
|
||||
protected override bool OnDo()
|
||||
{
|
||||
return this.GetModel<PlayerModel>().Health.Value <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询背包中指定物品的数量
|
||||
public class GetItemCountQuery : AbstractQuery<int>
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
protected override int OnDo()
|
||||
{
|
||||
var inventory = this.GetModel<InventoryModel>();
|
||||
return inventory.GetItemCount(ItemId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 发送查询(在 Controller 中)
|
||||
|
||||
```csharp
|
||||
public partial class ShopUI : Control, IController
|
||||
{
|
||||
[Export] private Button _buyButton;
|
||||
[Export] private int _itemPrice = 100;
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_buyButton.Pressed += OnBuyButtonPressed;
|
||||
}
|
||||
|
||||
private void OnBuyButtonPressed()
|
||||
{
|
||||
// 查询玩家金币
|
||||
int playerGold = this.SendQuery(new GetPlayerGoldQuery());
|
||||
|
||||
if (playerGold >= _itemPrice)
|
||||
{
|
||||
// 发送购买命令
|
||||
this.SendCommand(new BuyItemCommand { ItemId = "sword_01" });
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("金币不足!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在 System 中使用
|
||||
|
||||
```csharp
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
|
||||
}
|
||||
|
||||
private void OnEnemyAttack(EnemyAttackEvent e)
|
||||
{
|
||||
// 查询玩家是否已经死亡
|
||||
bool isDead = this.SendQuery(new IsPlayerDeadQuery());
|
||||
|
||||
if (!isDead)
|
||||
{
|
||||
// 执行伤害逻辑
|
||||
this.SendCommand(new TakeDamageCommand { Damage = e.Damage });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 1. 带参数的复杂查询
|
||||
|
||||
```csharp
|
||||
// 查询指定范围内的敌人列表
|
||||
public class GetEnemiesInRangeQuery : AbstractQuery<List<Enemy>>
|
||||
{
|
||||
public Vector3 Center { get; set; }
|
||||
public float Radius { get; set; }
|
||||
|
||||
protected override List<Enemy> OnDo()
|
||||
{
|
||||
var enemySystem = this.GetSystem<EnemySpawnSystem>();
|
||||
return enemySystem.GetEnemiesInRange(Center, Radius);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
var enemies = this.SendQuery(new GetEnemiesInRangeQuery
|
||||
{
|
||||
Center = playerPosition,
|
||||
Radius = 10.0f
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 组合查询
|
||||
|
||||
```csharp
|
||||
// 查询玩家是否可以使用技能
|
||||
public class CanUseSkillQuery : AbstractQuery<bool>
|
||||
{
|
||||
public string SkillId { get; set; }
|
||||
|
||||
protected override bool OnDo()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var skillModel = this.GetModel<SkillModel>();
|
||||
|
||||
// 查询技能消耗
|
||||
var skillCost = this.SendQuery(new GetSkillCostQuery { SkillId = SkillId });
|
||||
|
||||
// 检查是否满足条件
|
||||
return playerModel.Mana.Value >= skillCost.ManaCost
|
||||
&& !this.SendQuery(new IsSkillOnCooldownQuery { SkillId = SkillId });
|
||||
}
|
||||
}
|
||||
|
||||
public class GetSkillCostQuery : AbstractQuery<SkillCost>
|
||||
{
|
||||
public string SkillId { get; set; }
|
||||
|
||||
protected override SkillCost OnDo()
|
||||
{
|
||||
return this.GetModel<SkillModel>().GetSkillCost(SkillId);
|
||||
}
|
||||
}
|
||||
|
||||
public class IsSkillOnCooldownQuery : AbstractQuery<bool>
|
||||
{
|
||||
public string SkillId { get; set; }
|
||||
|
||||
protected override bool OnDo()
|
||||
{
|
||||
return this.GetModel<SkillModel>().IsOnCooldown(SkillId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 聚合数据查询
|
||||
|
||||
```csharp
|
||||
// 查询玩家战斗力
|
||||
public class GetPlayerPowerQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var equipmentModel = this.GetModel<EquipmentModel>();
|
||||
|
||||
int basePower = playerModel.Level.Value * 10;
|
||||
int equipmentPower = equipmentModel.GetTotalPower();
|
||||
int buffPower = this.SendQuery(new GetBuffPowerQuery());
|
||||
|
||||
return basePower + equipmentPower + buffPower;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询玩家详细信息(用于UI显示)
|
||||
public class GetPlayerInfoQuery : AbstractQuery<PlayerInfo>
|
||||
{
|
||||
protected override PlayerInfo OnDo()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
return new PlayerInfo
|
||||
{
|
||||
Name = playerModel.Name.Value,
|
||||
Level = playerModel.Level.Value,
|
||||
Health = playerModel.Health.Value,
|
||||
MaxHealth = playerModel.MaxHealth.Value,
|
||||
Gold = this.SendQuery(new GetPlayerGoldQuery()),
|
||||
Power = this.SendQuery(new GetPlayerPowerQuery())
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 跨 System 查询
|
||||
|
||||
```csharp
|
||||
// 在 AI System 中查询玩家状态
|
||||
public class EnemyAISystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit() { }
|
||||
|
||||
public void UpdateEnemyBehavior(Enemy enemy)
|
||||
{
|
||||
// 查询玩家位置
|
||||
var playerPos = this.SendQuery(new GetPlayerPositionQuery());
|
||||
|
||||
// 查询玩家是否在攻击范围内
|
||||
bool inRange = this.SendQuery(new IsPlayerInRangeQuery
|
||||
{
|
||||
Position = enemy.Position,
|
||||
Range = enemy.AttackRange
|
||||
});
|
||||
|
||||
if (inRange)
|
||||
{
|
||||
// 查询是否可以攻击
|
||||
bool canAttack = this.SendQuery(new CanEnemyAttackQuery
|
||||
{
|
||||
EnemyId = enemy.Id
|
||||
});
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
this.SendCommand(new EnemyAttackCommand { EnemyId = enemy.Id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Command vs Query
|
||||
|
||||
### Command(命令)
|
||||
- **用途**:修改系统状态
|
||||
- **返回值**:无返回值(void)
|
||||
- **示例**:购买物品、造成伤害、升级角色
|
||||
|
||||
### Query(查询)
|
||||
- **用途**:读取数据,不修改状态
|
||||
- **返回值**:有返回值
|
||||
- **示例**:获取金币数量、检查技能冷却、查询玩家位置
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:在 Query 中修改状态
|
||||
public class BadQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>();
|
||||
model.Gold.Value += 100; // 不应该在 Query 中修改数据!
|
||||
return model.Gold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确:Query 只读取数据
|
||||
public class GoodQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
return this.GetModel<PlayerModel>().Gold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 修改数据应该使用 Command
|
||||
public class AddGoldCommand : AbstractCommand
|
||||
{
|
||||
public int Amount { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>();
|
||||
model.Gold.Value += Amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **查询只读取,不修改** - 保持 Query 的纯粹性
|
||||
2. **小而专注** - 每个 Query 只负责一个具体的查询任务
|
||||
3. **可组合** - 复杂查询可以通过组合简单查询实现
|
||||
4. **避免过度查询** - 如果需要频繁查询,考虑使用 BindableProperty
|
||||
5. **命名清晰** - Query 名称应该清楚表达查询意图(Get、Is、Can、Has等前缀)
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 缓存查询结果
|
||||
|
||||
```csharp
|
||||
// 在 Model 中缓存复杂计算
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
private int? _cachedPower;
|
||||
|
||||
public int GetPower()
|
||||
{
|
||||
if (_cachedPower == null)
|
||||
{
|
||||
_cachedPower = CalculatePower();
|
||||
}
|
||||
return _cachedPower.Value;
|
||||
}
|
||||
|
||||
private int CalculatePower()
|
||||
{
|
||||
// 复杂计算...
|
||||
return 100;
|
||||
}
|
||||
|
||||
public void InvalidatePowerCache()
|
||||
{
|
||||
_cachedPower = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 批量查询
|
||||
|
||||
```csharp
|
||||
// 一次查询多个数据,而不是多次单独查询
|
||||
public class GetMultipleItemCountsQuery : AbstractQuery<Dictionary<string, int>>
|
||||
{
|
||||
public List<string> ItemIds { get; set; }
|
||||
|
||||
protected override Dictionary<string, int> OnDo()
|
||||
{
|
||||
var inventory = this.GetModel<InventoryModel>();
|
||||
return ItemIds.ToDictionary(id => id, id => inventory.GetItemCount(id));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`command`](../command/README.md) - CQRS 的命令部分
|
||||
- [`model`](../model/README.md) - Query 主要从 Model 获取数据
|
||||
- [`system`](../system/README.md) - System 中可以发送 Query
|
||||
- [`controller`](../controller/README.md) - Controller 中可以发送 Query
|
||||
- [`extensions`](../extensions/README.md) - 提供 SendQuery 扩展方法
|
||||
17
framework/rule/IBelongToArchitecture.cs
Normal file
17
framework/rule/IBelongToArchitecture.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using GFramework.framework.architecture;
|
||||
|
||||
namespace GFramework.framework.rule;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个接口,用于标识某个对象属于特定的架构体系。
|
||||
/// 实现此接口的对象可以通过GetArchitecture方法获取其所属的架构实例。
|
||||
/// </summary>
|
||||
public interface IBelongToArchitecture
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前对象所属的架构实例。
|
||||
/// </summary>
|
||||
/// <returns>返回实现IArchitecture接口的架构实例</returns>
|
||||
IArchitecture GetArchitecture();
|
||||
}
|
||||
15
framework/rule/ICanSetArchitecture.cs
Normal file
15
framework/rule/ICanSetArchitecture.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using GFramework.framework.architecture;
|
||||
|
||||
namespace GFramework.framework.rule;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个接口,用于设置架构实例
|
||||
/// </summary>
|
||||
public interface ICanSetArchitecture
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置架构实例
|
||||
/// </summary>
|
||||
/// <param name="architecture">要设置的架构实例</param>
|
||||
void SetArchitecture(IArchitecture architecture);
|
||||
}
|
||||
329
framework/rule/README.md
Normal file
329
framework/rule/README.md
Normal file
@ -0,0 +1,329 @@
|
||||
# Rule 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Rule 包定义了框架的核心规则接口,这些接口规定了框架各个组件之间的关系和约束。所有框架组件都需要遵循这些规则接口。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IBelongToArchitecture`](IBelongToArchitecture.cs)
|
||||
|
||||
标记接口,表示某个对象属于特定的架构体系。
|
||||
|
||||
**接口定义:**
|
||||
```csharp
|
||||
public interface IBelongToArchitecture
|
||||
{
|
||||
IArchitecture GetArchitecture();
|
||||
}
|
||||
```
|
||||
|
||||
**实现此接口的类型:**
|
||||
- Controller
|
||||
- System
|
||||
- Model
|
||||
- Command
|
||||
- Query
|
||||
- Event 处理器
|
||||
|
||||
**作用:**
|
||||
所有实现此接口的类型都能够获取其所属的架构实例,从而访问架构提供的各种能力。
|
||||
|
||||
### [`ICanSetArchitecture`](ICanSetArchitecture.cs)
|
||||
|
||||
定义可以设置架构实例的能力。
|
||||
|
||||
**接口定义:**
|
||||
```csharp
|
||||
public interface ICanSetArchitecture
|
||||
{
|
||||
void SetArchitecture(IArchitecture architecture);
|
||||
}
|
||||
```
|
||||
|
||||
**实现此接口的类型:**
|
||||
- Command
|
||||
- Query
|
||||
|
||||
**作用:**
|
||||
在 Command 和 Query 执行前,框架会自动调用 `SetArchitecture` 方法注入架构实例,使其能够访问 Model、System 等组件。
|
||||
|
||||
## 接口关系图
|
||||
|
||||
```
|
||||
IBelongToArchitecture (属于架构)
|
||||
↓ 被继承
|
||||
├── ICanGetModel (可获取 Model)
|
||||
├── ICanGetSystem (可获取 System)
|
||||
├── ICanGetUtility (可获取 Utility)
|
||||
├── ICanSendCommand (可发送 Command)
|
||||
├── ICanSendEvent (可发送 Event)
|
||||
├── ICanSendQuery (可发送 Query)
|
||||
└── ICanRegisterEvent (可注册 Event)
|
||||
|
||||
ICanSetArchitecture (可设置架构)
|
||||
↓ 被继承
|
||||
├── ICommand (命令接口)
|
||||
└── IQuery<T> (查询接口)
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. Controller 实现 IBelongToArchitecture
|
||||
|
||||
```csharp
|
||||
// Controller 通过实现 IBelongToArchitecture 获得架构访问能力
|
||||
public partial class PlayerController : Node, IController
|
||||
{
|
||||
// 实现 IBelongToArchitecture 接口
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 因为实现了 IBelongToArchitecture,所以可以使用扩展方法
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
this.SendCommand(new InitPlayerCommand());
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
GD.Print("Player died!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Command 实现 ICanSetArchitecture
|
||||
|
||||
```csharp
|
||||
// Command 实现 ICanSetArchitecture,框架会自动注入架构
|
||||
public class BuyItemCommand : AbstractCommand
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 框架已经通过 SetArchitecture 注入了架构实例
|
||||
// 所以这里可以直接使用 this.GetModel
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var shopModel = this.GetModel<ShopModel>();
|
||||
|
||||
int price = shopModel.GetItemPrice(ItemId);
|
||||
if (playerModel.Gold.Value >= price)
|
||||
{
|
||||
playerModel.Gold.Value -= price;
|
||||
this.SendCommand(new AddItemCommand { ItemId = ItemId });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 自定义组件遵循规则
|
||||
|
||||
```csharp
|
||||
// 自定义管理器遵循框架规则
|
||||
public class SaveManager : IBelongToArchitecture
|
||||
{
|
||||
private IArchitecture _architecture;
|
||||
|
||||
public SaveManager(IArchitecture architecture)
|
||||
{
|
||||
_architecture = architecture;
|
||||
}
|
||||
|
||||
public IArchitecture GetArchitecture() => _architecture;
|
||||
|
||||
public void SaveGame()
|
||||
{
|
||||
// 因为实现了 IBelongToArchitecture,可以使用扩展方法
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var saveData = new SaveData
|
||||
{
|
||||
PlayerName = playerModel.Name.Value,
|
||||
Level = playerModel.Level.Value,
|
||||
Gold = playerModel.Gold.Value
|
||||
};
|
||||
|
||||
// 保存逻辑...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 规则约束
|
||||
|
||||
### 1. 组件注册规则
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture<GameArchitecture>
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// ✅ 正确:Model/System/Utility 自动获得架构引用
|
||||
this.RegisterModel<PlayerModel>(new PlayerModel());
|
||||
this.RegisterSystem<CombatSystem>(new CombatSystem());
|
||||
this.RegisterUtility<StorageUtility>(new StorageUtility());
|
||||
}
|
||||
}
|
||||
|
||||
// Model 继承 AbstractModel,AbstractModel 实现了 IBelongToArchitecture
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
// 无需手动实现 GetArchitecture,基类已实现
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 可以直接使用架构能力
|
||||
this.SendEvent(new PlayerModelInitializedEvent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Command/Query 自动注入规则
|
||||
|
||||
```csharp
|
||||
// Controller 中发送 Command
|
||||
public class ShopUI : Control, IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
private void OnBuyButtonPressed()
|
||||
{
|
||||
var command = new BuyItemCommand { ItemId = "sword_01" };
|
||||
|
||||
// SendCommand 内部会自动调用 command.SetArchitecture(this.GetArchitecture())
|
||||
this.SendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
// Command 执行时已经有了架构引用
|
||||
public class BuyItemCommand : AbstractCommand
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 此时 GetArchitecture() 已经可用
|
||||
var model = this.GetModel<ShopModel>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 设计模式
|
||||
|
||||
### 1. 依赖注入模式
|
||||
|
||||
Rule 接口体现了依赖注入(DI)的思想:
|
||||
|
||||
```csharp
|
||||
// 接口定义了"需要什么"
|
||||
public interface IBelongToArchitecture
|
||||
{
|
||||
IArchitecture GetArchitecture();
|
||||
}
|
||||
|
||||
// 框架负责"提供什么"
|
||||
public static class CanSendExtensions
|
||||
{
|
||||
public static void SendCommand<T>(this ICanSendCommand self, T command)
|
||||
where T : ICommand
|
||||
{
|
||||
// 自动注入架构依赖
|
||||
command.SetArchitecture(self.GetArchitecture());
|
||||
command.Execute();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 接口隔离原则(ISP)
|
||||
|
||||
Rule 接口遵循接口隔离原则,每个接口职责单一:
|
||||
|
||||
```csharp
|
||||
// ❌ 不好的设计:一个大接口包含所有能力
|
||||
public interface IBigInterface
|
||||
{
|
||||
IArchitecture GetArchitecture();
|
||||
void SetArchitecture(IArchitecture architecture);
|
||||
T GetModel<T>() where T : class, IModel;
|
||||
T GetSystem<T>() where T : class, ISystem;
|
||||
void SendCommand<T>(T command) where T : ICommand;
|
||||
// ... 更多方法
|
||||
}
|
||||
|
||||
// ✅ 好的设计:小接口组合
|
||||
public interface IBelongToArchitecture { ... } // 只负责获取架构
|
||||
public interface ICanSetArchitecture { ... } // 只负责设置架构
|
||||
public interface ICanGetModel { ... } // 只负责获取 Model
|
||||
public interface ICanSendCommand { ... } // 只负责发送 Command
|
||||
```
|
||||
|
||||
### 3. 组合优于继承
|
||||
|
||||
通过接口组合实现不同能力:
|
||||
|
||||
```csharp
|
||||
// Controller 需要获取 Model 和发送 Command
|
||||
public interface IController : ICanGetModel, ICanGetSystem, ICanSendCommand,
|
||||
ICanSendQuery, ICanRegisterEvent
|
||||
{
|
||||
}
|
||||
|
||||
// Command 需要设置架构和获取 Model/System
|
||||
public interface ICommand : ICanSetArchitecture, ICanGetModel, ICanGetSystem,
|
||||
ICanSendEvent, ICanSendQuery
|
||||
{
|
||||
}
|
||||
|
||||
// System 只需要获取其他组件
|
||||
public interface ISystem : ICanGetModel, ICanGetUtility, ICanGetSystem,
|
||||
ICanRegisterEvent, ICanSendEvent, ICanSendQuery
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展规则
|
||||
|
||||
### 自定义规则接口
|
||||
|
||||
```csharp
|
||||
// 定义新的规则接口
|
||||
public interface ICanAccessDatabase : IBelongToArchitecture
|
||||
{
|
||||
}
|
||||
|
||||
// 提供扩展方法
|
||||
public static class CanAccessDatabaseExtensions
|
||||
{
|
||||
public static DatabaseUtility GetDatabase(this ICanAccessDatabase self)
|
||||
{
|
||||
return self.GetArchitecture().GetUtility<DatabaseUtility>();
|
||||
}
|
||||
}
|
||||
|
||||
// 让特定组件实现这个接口
|
||||
public class DatabaseCommand : AbstractCommand, ICanAccessDatabase
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var db = this.GetDatabase();
|
||||
// 使用数据库...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **遵循既有规则** - 优先使用框架提供的规则接口
|
||||
2. **不要绕过规则** - 不要直接存储架构引用,使用接口获取
|
||||
3. **接口组合** - 通过实现多个小接口获得所需能力
|
||||
4. **扩展规则** - 需要新能力时,定义新的规则接口
|
||||
5. **保持一致** - 自定义组件也应遵循相同的规则体系
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](../architecture/README.md) - 定义 IArchitecture 接口
|
||||
- [`command`](../command/README.md) - Command 实现 ICanSetArchitecture
|
||||
- [`query`](../query/README.md) - Query 实现 ICanSetArchitecture
|
||||
- [`controller`](../controller/README.md) - Controller 实现 IBelongToArchitecture
|
||||
- [`system`](../system/README.md) - System 实现 IBelongToArchitecture
|
||||
- [`model`](../model/README.md) - Model 实现 IBelongToArchitecture
|
||||
- [`extensions`](../extensions/README.md) - 基于规则接口提供扩展方法
|
||||
35
framework/system/AbstractSystem.cs
Normal file
35
framework/system/AbstractSystem.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using GFramework.framework.architecture;
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.system;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象系统基类,实现系统接口的基本功能
|
||||
/// 提供架构关联和初始化机制
|
||||
/// </summary>
|
||||
public abstract class AbstractSystem : ISystem
|
||||
{
|
||||
private IArchitecture _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前系统所属的架构实例
|
||||
/// </summary>
|
||||
/// <returns>返回系统关联的架构对象</returns>
|
||||
IArchitecture IBelongToArchitecture.GetArchitecture() => _mArchitecture;
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统所属的架构实例
|
||||
/// </summary>
|
||||
/// <param name="architecture">要关联的架构对象</param>
|
||||
void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => _mArchitecture = architecture;
|
||||
|
||||
/// <summary>
|
||||
/// 系统初始化方法,调用抽象初始化方法
|
||||
/// </summary>
|
||||
void ISystem.Init() => OnInit();
|
||||
|
||||
/// <summary>
|
||||
/// 抽象初始化方法,由子类实现具体的初始化逻辑
|
||||
/// </summary>
|
||||
protected abstract void OnInit();
|
||||
}
|
||||
9
framework/system/ICanGetSystem.cs
Normal file
9
framework/system/ICanGetSystem.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.system;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个接口,表示可以获取系统的对象。
|
||||
/// 该接口继承自IBelongToArchitecture接口,用于标识那些属于系统架构并能够获取系统实例的类型。
|
||||
/// </summary>
|
||||
public interface ICanGetSystem : IBelongToArchitecture;
|
||||
20
framework/system/ISystem.cs
Normal file
20
framework/system/ISystem.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using GFramework.framework.events;
|
||||
using GFramework.framework.model;
|
||||
using GFramework.framework.rule;
|
||||
using GFramework.framework.utility;
|
||||
|
||||
namespace GFramework.framework.system;
|
||||
|
||||
/// <summary>
|
||||
/// 系统接口,定义了系统的基本行为和功能
|
||||
/// 该接口继承了多个框架相关的接口,提供了系统初始化能力
|
||||
/// </summary>
|
||||
public interface ISystem : ICanSetArchitecture, ICanGetModel, ICanGetUtility,
|
||||
ICanRegisterEvent, ICanSendEvent, ICanGetSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化系统
|
||||
/// 在系统被创建后调用,用于执行系统的初始化逻辑
|
||||
/// </summary>
|
||||
void Init();
|
||||
}
|
||||
527
framework/system/README.md
Normal file
527
framework/system/README.md
Normal file
@ -0,0 +1,527 @@
|
||||
# System 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
System 包定义了业务逻辑层(Business Logic Layer)。System 负责处理游戏的核心业务逻辑,协调 Model 之间的交互,响应事件并执行复杂的业务流程。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`ICanGetSystem`](ICanGetSystem.cs)
|
||||
|
||||
标记接口,表示该类型可以获取其他 System。
|
||||
|
||||
**继承关系:**
|
||||
```csharp
|
||||
public interface ICanGetSystem : IBelongToArchitecture
|
||||
```
|
||||
|
||||
### [`ISystem`](ISystem.cs)
|
||||
|
||||
System 接口,定义了系统的基本行为。
|
||||
|
||||
**核心成员:**
|
||||
```csharp
|
||||
void Init(); // 系统初始化方法
|
||||
```
|
||||
|
||||
**继承的能力:**
|
||||
- `ICanSetArchitecture` - 可设置架构
|
||||
- `ICanGetModel` - 可获取 Model
|
||||
- `ICanGetUtility` - 可获取 Utility
|
||||
- `ICanGetSystem` - 可获取其他 System
|
||||
- `ICanRegisterEvent` - 可注册事件
|
||||
- `ICanSendEvent` - 可发送事件
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractSystem`](AbstractSystem.cs)
|
||||
|
||||
抽象 System 基类,提供了 System 的基础实现。
|
||||
|
||||
**使用方式:**
|
||||
```csharp
|
||||
public abstract class AbstractSystem : ISystem
|
||||
{
|
||||
void ISystem.Init() => OnInit();
|
||||
protected abstract void OnInit(); // 子类实现初始化逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义 System
|
||||
|
||||
```csharp
|
||||
// 战斗系统
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
|
||||
this.RegisterEvent<PlayerAttackEvent>(OnPlayerAttack);
|
||||
}
|
||||
|
||||
private void OnEnemyAttack(EnemyAttackEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 计算伤害
|
||||
int damage = CalculateDamage(e.AttackPower, playerModel.Defense.Value);
|
||||
|
||||
// 应用伤害
|
||||
playerModel.Health.Value -= damage;
|
||||
|
||||
// 发送伤害事件
|
||||
this.SendEvent(new PlayerTookDamageEvent { Damage = damage });
|
||||
}
|
||||
|
||||
private void OnPlayerAttack(PlayerAttackEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var enemyModel = this.GetModel<EnemyModel>();
|
||||
|
||||
int damage = CalculateDamage(playerModel.AttackPower.Value, e.Enemy.Defense);
|
||||
e.Enemy.Health -= damage;
|
||||
|
||||
this.SendEvent(new EnemyTookDamageEvent
|
||||
{
|
||||
EnemyId = e.Enemy.Id,
|
||||
Damage = damage
|
||||
});
|
||||
}
|
||||
|
||||
private int CalculateDamage(int attackPower, int defense)
|
||||
{
|
||||
return Math.Max(1, attackPower - defense / 2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 注册 System
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture<GameArchitecture>
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Model
|
||||
this.RegisterModel<PlayerModel>(new PlayerModel());
|
||||
this.RegisterModel<EnemyModel>(new EnemyModel());
|
||||
|
||||
// 注册 System(系统注册后会自动调用 Init)
|
||||
this.RegisterSystem<CombatSystem>(new CombatSystem());
|
||||
this.RegisterSystem<InventorySystem>(new InventorySystem());
|
||||
this.RegisterSystem<QuestSystem>(new QuestSystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在其他组件中获取 System
|
||||
|
||||
```csharp
|
||||
// 在 Controller 中
|
||||
public partial class GameController : Node, IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 获取 System
|
||||
var combatSystem = this.GetSystem<CombatSystem>();
|
||||
var questSystem = this.GetSystem<QuestSystem>();
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Command 中
|
||||
public class StartBattleCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var combatSystem = this.GetSystem<CombatSystem>();
|
||||
// 使用 System...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见使用模式
|
||||
|
||||
### 1. 事件驱动的 System
|
||||
|
||||
```csharp
|
||||
public class InventorySystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 监听物品相关事件
|
||||
this.RegisterEvent<ItemAddedEvent>(OnItemAdded);
|
||||
this.RegisterEvent<ItemRemovedEvent>(OnItemRemoved);
|
||||
this.RegisterEvent<ItemUsedEvent>(OnItemUsed);
|
||||
}
|
||||
|
||||
private void OnItemAdded(ItemAddedEvent e)
|
||||
{
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
|
||||
// 添加物品
|
||||
inventoryModel.AddItem(e.ItemId, e.Count);
|
||||
|
||||
// 检查成就
|
||||
CheckAchievements(e.ItemId);
|
||||
|
||||
// 发送通知
|
||||
this.SendEvent(new ShowNotificationEvent
|
||||
{
|
||||
Message = $"获得物品: {e.ItemId} x{e.Count}"
|
||||
});
|
||||
}
|
||||
|
||||
private void OnItemUsed(ItemUsedEvent e)
|
||||
{
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
if (inventoryModel.HasItem(e.ItemId))
|
||||
{
|
||||
// 应用物品效果
|
||||
ApplyItemEffect(e.ItemId, playerModel);
|
||||
|
||||
// 移除物品
|
||||
inventoryModel.RemoveItem(e.ItemId, 1);
|
||||
|
||||
this.SendEvent(new ItemEffectAppliedEvent { ItemId = e.ItemId });
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyItemEffect(string itemId, PlayerModel player)
|
||||
{
|
||||
// 物品效果逻辑...
|
||||
if (itemId == "health_potion")
|
||||
{
|
||||
player.Health.Value = Math.Min(
|
||||
player.Health.Value + 50,
|
||||
player.MaxHealth.Value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAchievements(string itemId)
|
||||
{
|
||||
// 成就检查逻辑...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 定时更新的 System
|
||||
|
||||
```csharp
|
||||
public class BuffSystem : AbstractSystem
|
||||
{
|
||||
private List<BuffData> _activeBuffs = new();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<BuffAppliedEvent>(OnBuffApplied);
|
||||
this.RegisterEvent<GameUpdateEvent>(OnUpdate);
|
||||
}
|
||||
|
||||
private void OnBuffApplied(BuffAppliedEvent e)
|
||||
{
|
||||
_activeBuffs.Add(new BuffData
|
||||
{
|
||||
BuffId = e.BuffId,
|
||||
Duration = e.Duration,
|
||||
RemainingTime = e.Duration
|
||||
});
|
||||
|
||||
ApplyBuffEffect(e.BuffId, true);
|
||||
}
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 更新所有 Buff
|
||||
for (int i = _activeBuffs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var buff = _activeBuffs[i];
|
||||
buff.RemainingTime -= e.DeltaTime;
|
||||
|
||||
if (buff.RemainingTime <= 0)
|
||||
{
|
||||
// Buff 过期
|
||||
ApplyBuffEffect(buff.BuffId, false);
|
||||
_activeBuffs.RemoveAt(i);
|
||||
|
||||
this.SendEvent(new BuffExpiredEvent { BuffId = buff.BuffId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBuffEffect(string buffId, bool apply)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
// 应用或移除 Buff 效果...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 跨 System 协作
|
||||
|
||||
```csharp
|
||||
public class QuestSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled);
|
||||
this.RegisterEvent<ItemCollectedEvent>(OnItemCollected);
|
||||
}
|
||||
|
||||
private void OnEnemyKilled(EnemyKilledEvent e)
|
||||
{
|
||||
var questModel = this.GetModel<QuestModel>();
|
||||
var activeQuests = questModel.GetActiveQuests();
|
||||
|
||||
foreach (var quest in activeQuests)
|
||||
{
|
||||
if (quest.Type == QuestType.KillEnemy && quest.TargetId == e.EnemyType)
|
||||
{
|
||||
quest.Progress++;
|
||||
|
||||
if (quest.Progress >= quest.RequiredAmount)
|
||||
{
|
||||
// 任务完成
|
||||
CompleteQuest(quest.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompleteQuest(string questId)
|
||||
{
|
||||
var questModel = this.GetModel<QuestModel>();
|
||||
var quest = questModel.GetQuest(questId);
|
||||
|
||||
// 标记任务完成
|
||||
questModel.CompleteQuest(questId);
|
||||
|
||||
// 发放奖励(通过其他 System)
|
||||
this.SendEvent(new QuestCompletedEvent
|
||||
{
|
||||
QuestId = questId,
|
||||
Rewards = quest.Rewards
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class RewardSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<QuestCompletedEvent>(OnQuestCompleted);
|
||||
}
|
||||
|
||||
private void OnQuestCompleted(QuestCompletedEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 发放奖励
|
||||
foreach (var reward in e.Rewards)
|
||||
{
|
||||
switch (reward.Type)
|
||||
{
|
||||
case RewardType.Gold:
|
||||
playerModel.Gold.Value += reward.Amount;
|
||||
break;
|
||||
case RewardType.Experience:
|
||||
playerModel.Experience.Value += reward.Amount;
|
||||
break;
|
||||
case RewardType.Item:
|
||||
this.SendEvent(new ItemAddedEvent
|
||||
{
|
||||
ItemId = reward.ItemId,
|
||||
Count = reward.Amount
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.SendEvent(new RewardsGrantedEvent { Rewards = e.Rewards });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 管理复杂状态机
|
||||
|
||||
```csharp
|
||||
public class GameStateSystem : AbstractSystem
|
||||
{
|
||||
private GameState _currentState = GameState.MainMenu;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<GameStateChangeRequestEvent>(OnStateChangeRequest);
|
||||
}
|
||||
|
||||
private void OnStateChangeRequest(GameStateChangeRequestEvent e)
|
||||
{
|
||||
if (CanTransition(_currentState, e.TargetState))
|
||||
{
|
||||
ExitState(_currentState);
|
||||
_currentState = e.TargetState;
|
||||
EnterState(_currentState);
|
||||
|
||||
this.SendEvent(new GameStateChangedEvent
|
||||
{
|
||||
PreviousState = _currentState,
|
||||
NewState = e.TargetState
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanTransition(GameState from, GameState to)
|
||||
{
|
||||
// 状态转换规则
|
||||
return (from, to) switch
|
||||
{
|
||||
(GameState.MainMenu, GameState.Playing) => true,
|
||||
(GameState.Playing, GameState.Paused) => true,
|
||||
(GameState.Paused, GameState.Playing) => true,
|
||||
(GameState.Playing, GameState.GameOver) => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private void EnterState(GameState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case GameState.Playing:
|
||||
// 开始游戏
|
||||
this.SendCommand(new StartGameCommand());
|
||||
break;
|
||||
case GameState.Paused:
|
||||
// 暂停游戏
|
||||
this.SendEvent(new GamePausedEvent());
|
||||
break;
|
||||
case GameState.GameOver:
|
||||
// 游戏结束
|
||||
this.SendCommand(new GameOverCommand());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExitState(GameState state)
|
||||
{
|
||||
// 清理当前状态
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## System vs Model
|
||||
|
||||
### Model(数据层)
|
||||
- **职责**:存储数据和状态
|
||||
- **特点**:被动,等待修改
|
||||
- **示例**:PlayerModel、InventoryModel
|
||||
|
||||
### System(逻辑层)
|
||||
- **职责**:处理业务逻辑,协调 Model
|
||||
- **特点**:主动,响应事件
|
||||
- **示例**:CombatSystem、QuestSystem
|
||||
|
||||
```csharp
|
||||
// ✅ 正确的职责划分
|
||||
|
||||
// Model: 存储数据
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> Mana { get; } = new(50);
|
||||
|
||||
protected override void OnInit() { }
|
||||
}
|
||||
|
||||
// System: 处理逻辑
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<AttackEvent>(OnAttack);
|
||||
}
|
||||
|
||||
private void OnAttack(AttackEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// System 负责计算和决策
|
||||
int damage = CalculateDamage(e);
|
||||
playerModel.Health.Value -= damage;
|
||||
|
||||
if (playerModel.Health.Value <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **单一职责** - 每个 System 专注于一个业务领域
|
||||
2. **事件驱动** - 通过事件与其他组件通信
|
||||
3. **无状态或少状态** - 优先将状态存储在 Model 中
|
||||
4. **可组合** - System 之间通过事件松耦合协作
|
||||
5. **初始化注册** - 在 `OnInit` 中注册所有事件监听
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 避免频繁的 GetModel/GetSystem
|
||||
|
||||
```csharp
|
||||
// ❌ 不好:每次都获取
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>(); // 频繁调用
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ 好:缓存引用
|
||||
private PlayerModel _playerModel;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_playerModel = this.GetModel<PlayerModel>(); // 只获取一次
|
||||
}
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 直接使用缓存的引用
|
||||
_playerModel.Health.Value += 1;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 批量处理
|
||||
|
||||
```csharp
|
||||
public class ParticleSystem : AbstractSystem
|
||||
{
|
||||
private List<Particle> _particles = new();
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 批量更新,而不是每个粒子发一个事件
|
||||
for (int i = _particles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
UpdateParticle(_particles[i], e.DeltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`model`](../model/README.md) - System 操作 Model 的数据
|
||||
- [`events`](../events/README.md) - System 通过事件通信
|
||||
- [`command`](../command/README.md) - System 中可以发送 Command
|
||||
- [`query`](../query/README.md) - System 中可以发送 Query
|
||||
- [`utility`](../utility/README.md) - System 可以使用 Utility
|
||||
- [`architecture`](../architecture/README.md) - 在架构中注册 System
|
||||
9
framework/utility/ICanGetUtility.cs
Normal file
9
framework/utility/ICanGetUtility.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using GFramework.framework.rule;
|
||||
|
||||
namespace GFramework.framework.utility;
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个接口,表示可以获取工具类的对象
|
||||
/// 该接口继承自IBelongToArchitecture,表明实现此接口的类型属于某个架构组件
|
||||
/// </summary>
|
||||
public interface ICanGetUtility : IBelongToArchitecture;
|
||||
7
framework/utility/IUtility.cs
Normal file
7
framework/utility/IUtility.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace GFramework.framework.utility;
|
||||
|
||||
/// <summary>
|
||||
/// IUtility接口定义了通用工具类的基本契约
|
||||
/// 该接口作为所有工具类实现的基础接口,提供统一的工具方法规范
|
||||
/// </summary>
|
||||
public interface IUtility;
|
||||
584
framework/utility/README.md
Normal file
584
framework/utility/README.md
Normal file
@ -0,0 +1,584 @@
|
||||
# Utility 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Utility 包定义了工具类层。Utility 提供无状态的辅助功能,如数学计算、文件操作、序列化等通用工具方法。与 System 不同,Utility 不依赖架构状态,是纯粹的工具函数集合。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`ICanGetUtility`](ICanGetUtility.cs)
|
||||
|
||||
标记接口,表示该类型可以获取 Utility。
|
||||
|
||||
**继承关系:**
|
||||
```csharp
|
||||
public interface ICanGetUtility : IBelongToArchitecture
|
||||
```
|
||||
|
||||
### [`IUtility`](IUtility.cs)
|
||||
|
||||
Utility 标记接口,所有工具类都应实现此接口。
|
||||
|
||||
**接口定义:**
|
||||
```csharp
|
||||
public interface IUtility
|
||||
{
|
||||
// 标记接口,无方法定义
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义 Utility
|
||||
|
||||
```csharp
|
||||
// 存储工具类
|
||||
public class StorageUtility : IUtility
|
||||
{
|
||||
private const string SavePath = "user://save_data.json";
|
||||
|
||||
public void Save<T>(T data)
|
||||
{
|
||||
string json = Json.Stringify(data);
|
||||
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write);
|
||||
file.StoreString(json);
|
||||
}
|
||||
|
||||
public T Load<T>() where T : new()
|
||||
{
|
||||
if (!FileAccess.FileExists(SavePath))
|
||||
return new T();
|
||||
|
||||
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read);
|
||||
string json = file.GetAsText();
|
||||
return Json.Parse<T>(json);
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (FileAccess.FileExists(SavePath))
|
||||
{
|
||||
DirAccess.RemoveAbsolute(SavePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数学工具类
|
||||
public class MathUtility : IUtility
|
||||
{
|
||||
public float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * Mathf.Clamp(t, 0f, 1f);
|
||||
}
|
||||
|
||||
public Vector3 BezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, float t)
|
||||
{
|
||||
float u = 1 - t;
|
||||
return u * u * p0 + 2 * u * t * p1 + t * t * p2;
|
||||
}
|
||||
|
||||
public bool IsInRange(Vector3 point, Vector3 center, float radius)
|
||||
{
|
||||
return point.DistanceTo(center) <= radius;
|
||||
}
|
||||
|
||||
public int RollDice(int sides)
|
||||
{
|
||||
return GD.RandRange(1, sides);
|
||||
}
|
||||
}
|
||||
|
||||
// 时间工具类
|
||||
public class TimeUtility : IUtility
|
||||
{
|
||||
public string FormatTime(float seconds)
|
||||
{
|
||||
int minutes = (int)(seconds / 60);
|
||||
int secs = (int)(seconds % 60);
|
||||
return $"{minutes:D2}:{secs:D2}";
|
||||
}
|
||||
|
||||
public long GetCurrentTimestamp()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
public bool IsExpired(long timestamp, int durationSeconds)
|
||||
{
|
||||
return GetCurrentTimestamp() > timestamp + durationSeconds;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 注册 Utility
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture<GameArchitecture>
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Utility(不需要初始化)
|
||||
this.RegisterUtility<StorageUtility>(new StorageUtility());
|
||||
this.RegisterUtility<MathUtility>(new MathUtility());
|
||||
this.RegisterUtility<TimeUtility>(new TimeUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用 Utility
|
||||
|
||||
```csharp
|
||||
// 在 System 中使用
|
||||
public class SaveSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<SaveGameEvent>(OnSaveGame);
|
||||
this.RegisterEvent<LoadGameEvent>(OnLoadGame);
|
||||
}
|
||||
|
||||
private void OnSaveGame(SaveGameEvent e)
|
||||
{
|
||||
var storage = this.GetUtility<StorageUtility>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
var saveData = new SaveData
|
||||
{
|
||||
PlayerName = playerModel.Name.Value,
|
||||
Level = playerModel.Level.Value,
|
||||
Gold = playerModel.Gold.Value,
|
||||
Timestamp = this.GetUtility<TimeUtility>().GetCurrentTimestamp()
|
||||
};
|
||||
|
||||
storage.Save(saveData);
|
||||
this.SendEvent(new GameSavedEvent());
|
||||
}
|
||||
|
||||
private void OnLoadGame(LoadGameEvent e)
|
||||
{
|
||||
var storage = this.GetUtility<StorageUtility>();
|
||||
var saveData = storage.Load<SaveData>();
|
||||
|
||||
if (saveData != null)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Name.Value = saveData.PlayerName;
|
||||
playerModel.Level.Value = saveData.Level;
|
||||
playerModel.Gold.Value = saveData.Gold;
|
||||
|
||||
this.SendEvent(new GameLoadedEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Command 中使用
|
||||
public class MovePlayerCommand : AbstractCommand
|
||||
{
|
||||
public Vector3 TargetPosition { get; set; }
|
||||
public float Speed { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var mathUtil = this.GetUtility<MathUtility>();
|
||||
|
||||
// 使用工具类计算
|
||||
Vector3 currentPos = playerModel.Position.Value;
|
||||
Vector3 direction = (TargetPosition - currentPos).Normalized();
|
||||
Vector3 newPos = currentPos + direction * Speed;
|
||||
|
||||
playerModel.Position.Value = newPos;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见 Utility 类型
|
||||
|
||||
### 1. 序列化/反序列化工具
|
||||
|
||||
```csharp
|
||||
public class JsonUtility : IUtility
|
||||
{
|
||||
public string Serialize<T>(T obj)
|
||||
{
|
||||
return Json.Stringify(obj);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string json) where T : new()
|
||||
{
|
||||
return Json.Parse<T>(json);
|
||||
}
|
||||
|
||||
public bool TryDeserialize<T>(string json, out T result) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Json.Parse<T>(json);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 随机数工具
|
||||
|
||||
```csharp
|
||||
public class RandomUtility : IUtility
|
||||
{
|
||||
private Random _random = new Random();
|
||||
|
||||
public int Range(int min, int max)
|
||||
{
|
||||
return _random.Next(min, max + 1);
|
||||
}
|
||||
|
||||
public float Range(float min, float max)
|
||||
{
|
||||
return min + (float)_random.NextDouble() * (max - min);
|
||||
}
|
||||
|
||||
public T Choose<T>(params T[] items)
|
||||
{
|
||||
return items[Range(0, items.Length - 1)];
|
||||
}
|
||||
|
||||
public List<T> Shuffle<T>(List<T> list)
|
||||
{
|
||||
var shuffled = new List<T>(list);
|
||||
for (int i = shuffled.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = Range(0, i);
|
||||
(shuffled[i], shuffled[j]) = (shuffled[j], shuffled[i]);
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
public bool Probability(float chance)
|
||||
{
|
||||
return _random.NextDouble() < chance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 字符串工具
|
||||
|
||||
```csharp
|
||||
public class StringUtility : IUtility
|
||||
{
|
||||
public string Truncate(string text, int maxLength, string suffix = "...")
|
||||
{
|
||||
if (text.Length <= maxLength)
|
||||
return text;
|
||||
return text.Substring(0, maxLength - suffix.Length) + suffix;
|
||||
}
|
||||
|
||||
public string FormatNumber(int number)
|
||||
{
|
||||
if (number >= 1000000)
|
||||
return $"{number / 1000000.0:F1}M";
|
||||
if (number >= 1000)
|
||||
return $"{number / 1000.0:F1}K";
|
||||
return number.ToString();
|
||||
}
|
||||
|
||||
public string ToTitleCase(string text)
|
||||
{
|
||||
return System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text.ToLower());
|
||||
}
|
||||
|
||||
public bool IsValidEmail(string email)
|
||||
{
|
||||
return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 加密工具
|
||||
|
||||
```csharp
|
||||
public class EncryptionUtility : IUtility
|
||||
{
|
||||
private const string EncryptionKey = "YourSecretKey123";
|
||||
|
||||
public string Encrypt(string plainText)
|
||||
{
|
||||
byte[] data = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] encrypted = EncryptBytes(data);
|
||||
return Convert.ToBase64String(encrypted);
|
||||
}
|
||||
|
||||
public string Decrypt(string encryptedText)
|
||||
{
|
||||
byte[] data = Convert.FromBase64String(encryptedText);
|
||||
byte[] decrypted = DecryptBytes(data);
|
||||
return System.Text.Encoding.UTF8.GetString(decrypted);
|
||||
}
|
||||
|
||||
private byte[] EncryptBytes(byte[] data)
|
||||
{
|
||||
// 简单的 XOR 加密示例(实际项目应使用更安全的算法)
|
||||
byte[] key = System.Text.Encoding.UTF8.GetBytes(EncryptionKey);
|
||||
byte[] result = new byte[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
result[i] = (byte)(data[i] ^ key[i % key.Length]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] DecryptBytes(byte[] data)
|
||||
{
|
||||
return EncryptBytes(data); // XOR 解密与加密相同
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 对象池工具
|
||||
|
||||
```csharp
|
||||
public class ObjectPoolUtility : IUtility
|
||||
{
|
||||
private Dictionary<Type, Queue<object>> _pools = new();
|
||||
|
||||
public T Get<T>() where T : new()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (_pools.ContainsKey(type) && _pools[type].Count > 0)
|
||||
{
|
||||
return (T)_pools[type].Dequeue();
|
||||
}
|
||||
return new T();
|
||||
}
|
||||
|
||||
public void Return<T>(T obj)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (!_pools.ContainsKey(type))
|
||||
{
|
||||
_pools[type] = new Queue<object>();
|
||||
}
|
||||
_pools[type].Enqueue(obj);
|
||||
}
|
||||
|
||||
public void Clear<T>()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (_pools.ContainsKey(type))
|
||||
{
|
||||
_pools[type].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
_pools.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 日志工具
|
||||
|
||||
```csharp
|
||||
public class LogUtility : IUtility
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
public void Log(string message, LogLevel level = LogLevel.Info)
|
||||
{
|
||||
string prefix = level switch
|
||||
{
|
||||
LogLevel.Debug => "[DEBUG]",
|
||||
LogLevel.Info => "[INFO]",
|
||||
LogLevel.Warning => "[WARN]",
|
||||
LogLevel.Error => "[ERROR]",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
string timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||
GD.Print($"{timestamp} {prefix} {message}");
|
||||
}
|
||||
|
||||
public void Debug(string message) => Log(message, LogLevel.Debug);
|
||||
public void Info(string message) => Log(message, LogLevel.Info);
|
||||
public void Warning(string message) => Log(message, LogLevel.Warning);
|
||||
public void Error(string message) => Log(message, LogLevel.Error);
|
||||
}
|
||||
```
|
||||
|
||||
## Utility vs System
|
||||
|
||||
### Utility(工具层)
|
||||
- **无状态** - 不存储业务数据
|
||||
- **纯函数** - 相同输入产生相同输出
|
||||
- **独立性** - 不依赖架构状态
|
||||
- **可复用** - 可在多个项目中使用
|
||||
|
||||
### System(逻辑层)
|
||||
- **有状态** - 可能存储临时状态
|
||||
- **业务逻辑** - 处理特定业务流程
|
||||
- **架构依赖** - 需要访问 Model
|
||||
- **项目特定** - 针对特定项目设计
|
||||
|
||||
```csharp
|
||||
// ✅ Utility: 无状态的工具方法
|
||||
public class MathUtility : IUtility
|
||||
{
|
||||
public float CalculateDamage(float attackPower, float defense)
|
||||
{
|
||||
return Math.Max(1, attackPower - defense * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ System: 有状态的业务逻辑
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
private List<CombatInstance> _activeCombats = new();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<AttackEvent>(OnAttack);
|
||||
}
|
||||
|
||||
private void OnAttack(AttackEvent e)
|
||||
{
|
||||
var mathUtil = this.GetUtility<MathUtility>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 使用 Utility 计算,但在 System 中处理业务逻辑
|
||||
float damage = mathUtil.CalculateDamage(e.AttackPower, playerModel.Defense.Value);
|
||||
playerModel.Health.Value -= (int)damage;
|
||||
|
||||
_activeCombats.Add(new CombatInstance { Damage = damage });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持无状态** - Utility 不应存储业务状态
|
||||
2. **纯函数优先** - 相同输入应产生相同输出
|
||||
3. **单一职责** - 每个 Utility 专注于一类功能
|
||||
4. **避免依赖** - 不依赖 Model、System 等架构组件
|
||||
5. **可测试** - 易于单元测试的纯函数
|
||||
|
||||
## 错误示例
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:Utility 中存储状态
|
||||
public class BadUtility : IUtility
|
||||
{
|
||||
private int _counter = 0; // 不应该有状态
|
||||
|
||||
public int GetNextId()
|
||||
{
|
||||
return ++_counter; // 依赖内部状态
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 错误:Utility 中访问 Model
|
||||
public class BadUtility2 : IUtility
|
||||
{
|
||||
public void DoSomething()
|
||||
{
|
||||
// Utility 不应该访问架构组件
|
||||
var model = this.GetModel<PlayerModel>(); // 编译错误!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确:如果需要状态,应该使用 System
|
||||
public class IdGeneratorSystem : AbstractSystem
|
||||
{
|
||||
private int _counter = 0;
|
||||
|
||||
protected override void OnInit() { }
|
||||
|
||||
public int GetNextId()
|
||||
{
|
||||
return ++_counter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 缓存计算结果
|
||||
|
||||
```csharp
|
||||
public class PathfindingUtility : IUtility
|
||||
{
|
||||
private Dictionary<(Vector3, Vector3), List<Vector3>> _pathCache = new();
|
||||
|
||||
public List<Vector3> FindPath(Vector3 start, Vector3 end, bool useCache = true)
|
||||
{
|
||||
var key = (start, end);
|
||||
|
||||
if (useCache && _pathCache.ContainsKey(key))
|
||||
{
|
||||
return _pathCache[key];
|
||||
}
|
||||
|
||||
var path = CalculatePath(start, end);
|
||||
|
||||
if (useCache)
|
||||
{
|
||||
_pathCache[key] = path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private List<Vector3> CalculatePath(Vector3 start, Vector3 end)
|
||||
{
|
||||
// A* 算法等复杂计算...
|
||||
return new List<Vector3>();
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
_pathCache.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 对象复用
|
||||
|
||||
```csharp
|
||||
public class CollectionUtility : IUtility
|
||||
{
|
||||
private List<Vector3> _tempList = new();
|
||||
|
||||
public List<Vector3> GetPointsInRadius(Vector3 center, float radius, List<Vector3> points)
|
||||
{
|
||||
_tempList.Clear();
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
if (point.DistanceTo(center) <= radius)
|
||||
{
|
||||
_tempList.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
return new List<Vector3>(_tempList); // 返回副本
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`system`](../system/README.md) - System 中使用 Utility
|
||||
- [`command`](../command/README.md) - Command 中可以使用 Utility
|
||||
- [`architecture`](../architecture/README.md) - 在架构中注册 Utility
|
||||
- [`ioc`](../ioc/README.md) - Utility 通过 IoC 容器管理
|
||||
- [`extensions`](../extensions/README.md) - 提供 GetUtility 扩展方法
|
||||
Loading…
x
Reference in New Issue
Block a user