docs(tutorials): 更新基础教程导航结构并添加完整教程内容

- 重构教程导航,将基础教程拆分为多个子章节
- 添加第1章:环境准备,包含.NET SDK和Godot引擎安装指南
- 添加第2章:项目创建与初始化,介绍GFramework项目结构搭建
- 添加第3章:基础计数器实现,演示传统MVC模式及问题分析
- 添加第4章:引入Model重构,展示GFramework的Model层设计
- 配置教程间的前后导航链接
- 更新导航菜单结构,支持折叠展开功能
This commit is contained in:
GeWuYou 2026-02-12 01:08:55 +08:00
parent da1c0d0295
commit e4e79e16dc
20 changed files with 4010 additions and 4107 deletions

View File

@ -139,8 +139,21 @@ export default defineConfig({
{
text: '教程',
items: [
{ text: '入门教程', link: '/zh-CN/tutorials/getting-started' },
{ text: '基础教程', link: '/zh-CN/tutorials/basic-tutorial' },
{
text: '基础教程',
link: '/zh-CN/tutorials/basic/',
collapsed: false,
items: [
{ text: '教程概览', link: '/zh-CN/tutorials/basic/' },
{ text: '1. 环境准备', link: '/zh-CN/tutorials/basic/01-environment' },
{ text: '2. 项目创建与初始化', link: '/zh-CN/tutorials/basic/02-project-setup' },
{ text: '3. 基础计数器实现', link: '/zh-CN/tutorials/basic/03-counter-basic' },
{ text: '4. 引入 Model 重构', link: '/zh-CN/tutorials/basic/04-model-refactor' },
{ text: '5. 命令系统优化', link: '/zh-CN/tutorials/basic/05-command-system' },
{ text: '6. Utility 与 System', link: '/zh-CN/tutorials/basic/06-utility-system' },
{ text: '7. 总结与最佳实践', link: '/zh-CN/tutorials/basic/07-summary' }
]
},
{ text: 'Godot集成', link: '/zh-CN/tutorials/godot-integration' },
{ text: '高级模式', link: '/zh-CN/tutorials/advanced-patterns' }
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,733 +0,0 @@
# GFramework 基础教程
这是一个循序渐进的教程,将带你从零开始学习 GFramework通过构建一个计数器应用来理解框架的核心概念和最佳实践。
## 目录
### 第一部分:入门准备
- [环境准备](#环境准备)
- [GFramework 核心概念](#gframework-核心概念)
### 第二部分:基础实践
- [创建第一个项目](#创建第一个项目)
- [实现基础计数器](#实现基础计数器)
### 第三部分:架构深化
- [Model层设计](#model层设计)
### 第四部分:架构完善
- [System层实现](#system层实现)
- [Utility层设计](#utility层设计)
### 第五部分:架构整合
- [完整架构集成](#完整架构集成)
### 第六部分:进阶理解
- [架构原理深入](#架构原理深入)
- [最佳实践总结](#最佳实践总结)
---
## 环境准备
### 系统要求
- **操作系统**: Windows 10+, macOS 10.15+, 或 Linux
- **.NET SDK**: 6.0 或更高版本
- **Godot 引擎**: 4.5.1 或更高版本
- **IDE**: Visual Studio 2022+, JetBrains Rider, 或 VS Code
### 安装 .NET SDK
1. 访问 [.NET 官网](https://dotnet.microsoft.com/download)
2. 下载并安装 .NET 6.0 SDK
3. 验证安装:
```bash
dotnet --version
# 应该显示 6.0.x 或更高版本
```
### 安装 Godot
1. 访问 [Godot 官网](https://godotengine.org/download)
2. 下载 Godot Mono版
3. 解压到合适的位置并启动
4. 在编辑器设置中确认 .NET 支持
### 验证环境
创建一个测试项目验证环境:
```bash
# 创建测试项目
dotnet new console -n TestProject
cd TestProject
# 添加 GFramework 引用
dotnet add package GeWuYou.GFramework.Core
dotnet add package GeWuYou.GFramework.Godot
# 编译测试
dotnet build
```
---
## GFramework 核心概念
### MVC 架构模式简介
在传统的 MVCModel-View-Controller架构中
- **Model模型**: 管理应用程序的数据和业务逻辑
- **View视图**: 负责数据的展示和用户界面
- **Controller控制器**: 处理用户输入,协调 Model 和 View
### GFramework 架构组件概述
GFramework 在传统 MVC 基础上进行了扩展,包含以下核心组件:
```mermaid
graph TD
A[GFramework Architecture] --> B[Model]
A --> C[View]
A --> D[Controller]
A --> E[System]
A --> F[Utility]
A --> G[EventBus]
B --> B1[数据模型层]
C --> C1[视图层]
D --> D1[控制器层]
E --> E1[系统业务逻辑层]
F --> F1[工具服务层]
G --> G1[事件总线]
```
### 为什么选择事件驱动架构
传统的直接调用方式存在耦合问题:
```csharp
// 传统方式 - 强耦合
Model.Increment();
View.UpdateDisplay(Model.Count);
```
事件驱动架构的优势:
```csharp
// 事件驱动 - 松耦合
Model.Increment(); // 只关心业务逻辑
// View 通过事件订阅自动更新
```
---
## 创建第一个项目
### 项目结构规划
创建以下文件夹结构:
```text
MyGFrameworkGame/
├── scripts/ # 脚本代码
│ ├── architecture/ # 架构定义
│ ├── model/ # 数据模型
│ ├── system/ # 业务系统
│ ├── utility/ # 工具服务
│ ├── module/ # 模块定义
│ └── app/ # 应用入口
├── scenes/ # 场景文件
├── assets/ # 游戏资源
└── global/ # 全局类
```
### 包管理配置
推荐使用以下包配置:
```bash
# 核心框架
dotnet add package GeWuYou.GFramework
# Godot 集成
dotnet add package GeWuYou.GFramework.Godot
# 源码生成器(可选但推荐)
dotnet add package GeWuYou.GFramework.SourceGenerators
```
### 基础场景搭建
创建一个简单的 UI 场景:
```text
Control (App)
├── VBoxContainer
│ ├── Label ("%Label")
│ ├── HBoxContainer
│ │ ├── Button ("%AddButton")
│ │ └── Button ("%SubButton")
```
---
## 实现基础计数器
### 传统 MVC 实现方式
让我们先用传统方式实现一个简单的计数器:
```csharp
using Godot;
public partial class App : Control
{
private Button _addButton => GetNode<Button>("%AddButton");
private Button _subButton => GetNode<Button>("%SubButton");
private Label _label => GetNode<Label>("%Label");
private int _count = 0;
public override void _Ready()
{
_addButton.Pressed += () => {
_count++;
UpdateView();
};
_subButton.Pressed += () => {
_count--;
UpdateView();
};
UpdateView();
}
private void UpdateView()
{
_label.Text = $"Count: {_count}";
}
}
```
### 识别设计问题
上述实现存在以下问题:
1. **View 与 Controller 耦合过紧**
- UI 控件和业务逻辑直接耦合
- 难以维护和扩展
2. **数据状态没有抽象**
- 状态直接存在控制器中
- 无法复用和独立测试
3. **缺乏统一上下文管理**
- 状态管理分散
- 跨组件通信困难
4. **可测试性低**
- 业务逻辑与 Godot 节点耦合
- 难以进行单元测试
### 引入 GFramework 改进
让我们用 GFramework 来解决这些问题:
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.Core.extensions;
using GFramework.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Abstractions.rule;
using Godot;
[ContextAware]
[Log]
public partial class App : Control, IController
{
private Button AddButton => GetNode<Button>("%AddButton");
private Button SubButton => GetNode<Button>("%SubButton");
private Label Label => GetNode<Label>("%Label");
public override void _Ready()
{
// 事件驱动的视图更新
this.RegisterEvent<CounterModel.ChangedCountEvent>(e => {
UpdateView(e.Count);
});
// 命令驱动的用户交互
AddButton.Pressed += () => {
this.SendCommand(new IncreaseCountCommand());
};
SubButton.Pressed += () => {
this.SendCommand(new DecreaseCountCommand());
};
UpdateView();
}
private void UpdateView(int count = 0)
{
Label.Text = $"Count: {count}";
}
}
```
---
## Model层设计
### 抽象模型接口
首先定义计数器模型接口:
```csharp
using GFramework.Core.Abstractions.model;
public interface ICounterModel : IModel
{
int Count { get; }
void Increment();
void Decrement();
}
```
### 具体模型实现
实现具体的计数器模型:
```csharp
using GFramework.Core.extensions;
using GFramework.Core.model;
public class CounterModel : AbstractModel, ICounterModel
{
public int Count { get; private set; }
protected override void OnInit() { }
public sealed record ChangedCountEvent
{
public int Count { get; init; }
}
public void Increment()
{
Count++;
this.SendEvent(new ChangedCountEvent { Count = Count });
}
public void Decrement()
{
Count--;
this.SendEvent(new ChangedCountEvent { Count = Count });
}
}
```
### 模型注册与使用
在模块中注册模型:
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
public class ModelModule : AbstractModule
{
public override void Install(IArchitecture architecture)
{
architecture.RegisterModel(new CounterModel());
}
}
```
---
## System层实现
### 系统模块概念
System 层负责处理业务逻辑和规则:
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
public class SystemModule : AbstractModule
{
public override void Install(IArchitecture architecture)
{
// 注册业务系统
architecture.RegisterSystem(new CounterBusinessSystem());
}
}
```
### 业务逻辑封装
创建业务逻辑系统:
```csharp
using GFramework.Core.system;
using GFramework.Core.extensions;
public class CounterBusinessSystem : AbstractSystem
{
protected override void OnInit()
{
// 系统初始化逻辑
}
public void ValidateCount(int count)
{
// 业务验证逻辑
if (count < 0)
throw new InvalidOperationException("计数不能为负数");
}
}
```
### 命令模式应用
创建计数命令:
```csharp
using GFramework.Core.command;
using GFramework.Core.extensions;
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<ICounterModel>();
model.Increment();
}
}
public class DecreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<ICounterModel>();
model.Decrement();
}
}
```
---
## Utility层设计
### 工具类职责
Utility 层提供无状态的辅助功能:
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
public class UtilityModule : AbstractModule
{
public override void Install(IArchitecture architecture)
{
// 注册工具服务
architecture.RegisterUtility(new MathUtility());
architecture.RegisterUtility(new StringUtility());
}
}
```
### 依赖注入管理
工具类的依赖注入示例:
```csharp
public class MathUtility
{
public int Clamp(int value, int min, int max)
{
return Math.Max(min, Math.Min(max, value));
}
public float Lerp(float a, float b, float t)
{
return a + (b - a) * t;
}
}
```
### 通用功能封装
常用的工具方法:
```csharp
public class StringUtility
{
public string FormatCount(int count)
{
return count >= 1000
? $"{count / 1000.0:F1}K"
: count.ToString();
}
public bool IsValidName(string name)
{
return !string.IsNullOrWhiteSpace(name) && name.Length <= 20;
}
}
```
---
## 完整架构集成
### 模块化架构设计
整合所有模块:
```csharp
using GFramework.Godot.architecture;
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
InstallModule(new ModelModule());
InstallModule(new SystemModule());
InstallModule(new UtilityModule());
}
}
```
### 依赖关系管理
清晰的依赖层次:
```text
GameArchitecture
├── ModelModule
│ └── CounterModel
├── SystemModule
│ └── CounterBusinessSystem
└── UtilityModule
├── MathUtility
└── StringUtility
```
### 初始化流程
游戏入口点:
```csharp
using GFramework.Core.Abstractions.architecture;
using Godot;
public partial class GameEntryPoint : Node
{
public static IArchitecture Architecture { get; private set; }
public override void _Ready()
{
Architecture = new GameArchitecture();
Architecture.Initialize();
}
}
```
### 上下文管理
使用上下文感知特性:
```csharp
[ContextAware]
public partial class App : Control, IController
{
// 框架自动注入上下文
// 可以直接使用 this.GetModel<T>() 等方法
}
```
---
## 架构原理深入
### 事件驱动架构优势
事件驱动的核心价值:
```csharp
// 发布事件
this.SendEvent(new GameEvent.LevelCompleted { Level = 1 });
// 订阅事件(多个订阅者)
this.RegisterEvent<GameEvent.LevelCompleted>(e => {
// 更新UI
});
this.RegisterEvent<GameEvent.LevelCompleted>(e => {
// 保存进度
});
this.RegisterEvent<GameEvent.LevelCompleted>(e => {
// 播放音效
});
```
### 解耦设计思想
组件间的松耦合关系:
```text
Model ←→ EventBus ←→ View
↑ ↑
└───────System────────┘
```
### 可测试性提升
各层组件的可测试性:
```csharp
// Model 层测试
[Test]
public void CounterModel_Increment_ShouldIncreaseCount()
{
var model = new CounterModel();
model.Initialize();
model.Increment();
Assert.Equal(1, model.Count);
}
// System 层测试
[Test]
public void CounterBusinessSystem_ValidateCount_ShouldThrowForNegative()
{
var system = new CounterBusinessSystem();
system.Initialize();
Assert.Throws<InvalidOperationException>(() =>
system.ValidateCount(-1));
}
```
### 扩展性考虑
易于扩展的架构设计:
```csharp
// 添加新功能只需扩展相应层
public class SaveSystem : AbstractSystem
{
public void SaveGame()
{
var counterModel = this.GetModel<ICounterModel>();
// 保存逻辑
}
}
// 在模块中注册
public override void Install(IArchitecture architecture)
{
architecture.RegisterSystem(new SaveSystem());
}
```
---
## 最佳实践总结
### 代码组织规范
推荐的项目结构:
```text
Project/
├── scripts/
│ ├── architecture/
│ │ ├── GameArchitecture.cs
│ │ └── GameModule.cs
│ ├── model/
│ │ ├── ICounterModel.cs
│ │ └── CounterModel.cs
│ ├── system/
│ │ ├── CounterBusinessSystem.cs
│ │ └── SaveSystem.cs
│ ├── utility/
│ │ ├── MathUtility.cs
│ │ └── StringUtility.cs
│ ├── command/
│ │ ├── IncreaseCountCommand.cs
│ │ └── DecreaseCountCommand.cs
│ └── app/
│ └── App.cs
└── scenes/
```
### 设计模式应用
核心设计模式:
1. **观察者模式**: 事件系统
2. **命令模式**: 业务操作封装
3. **工厂模式**: 对象创建
4. **单例模式**: 架构上下文
### 性能优化建议
```csharp
// 避免频繁事件发送
public void BatchUpdate()
{
// 批量处理逻辑
this.SendEvent(new BatchUpdateEvent { Data = batchData });
}
// 合理使用事件过滤
this.RegisterEvent<CounterModel.ChangedCountEvent>(
e => UpdateView(e.Count),
filter: e => e.Count % 10 == 0 // 每10次更新一次
);
```
### 常见问题解答
**Q: 什么时候使用 Model vs System?**
A: Model 管理状态数据System 处理业务逻辑
**Q: 如何处理异步操作?**
A: 使用 AbstractAsyncCommand 和协程调度器
**Q: 如何进行模块间通信?**
A: 通过事件总线,避免直接依赖
**Q: 如何管理复杂的依赖关系?**
A: 使用模块化设计,明确依赖方向
---
## 总结
通过本教程,你已经学习了:
1. **基础概念**: MVC 架构和事件驱动设计
2. **实践应用**: 从传统实现到框架改进
3. **架构分层**: Model、System、Utility 各层职责
4. **核心原理**: 解耦设计和可测试性
5. **最佳实践**: 代码组织和设计模式应用
现在你已经具备了使用 GFramework 构建游戏应用的基础知识,可以开始创建更复杂的游戏功能了!

View File

@ -0,0 +1,352 @@
---
prev:
text: '教程首页'
link: './index'
next:
text: '项目创建与初始化'
link: './02-project-setup'
---
# 第 1 章:环境准备
在开始使用 GFramework 之前,我们需要配置好开发环境。本章将指导你完成所有必要工具的安装和验证。
## 系统要求
### 操作系统
- **Windows**Windows 10 或更高版本(推荐 Windows 11
- **macOS**macOS 10.15 Catalina 或更高版本
- **Linux**主流发行版Ubuntu 20.04+, Fedora 35+, Arch Linux 等)
### 硬件要求
- **CPU**:双核或更高(推荐四核)
- **内存**8 GB RAM推荐 16 GB
- **存储**:至少 5 GB 可用空间
- **显卡**:支持 OpenGL 3.3 或更高
## 安装 .NET SDK
GFramework 基于 .NET 6.0+,需要先安装 .NET SDK。
### Windows
1. 访问 [.NET 官方下载页](https://dotnet.microsoft.com/download)
2. 下载 **.NET 6.0 SDK** 或更高版本
3. 运行安装程序,按照提示完成安装
4. 安装完成后,打开命令提示符或 PowerShell
### macOS
**方式一:使用官方安装包**
```bash
# 访问官网下载 .pkg 安装包
# https://dotnet.microsoft.com/download
```
**方式二:使用 Homebrew**
```bash
brew install --cask dotnet-sdk
```
### Linux
**Ubuntu/Debian**
```bash
# 添加 Microsoft 包仓库
wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
# 安装 .NET SDK
sudo apt-get update
sudo apt-get install -y dotnet-sdk-6.0
```
**Fedora**
```bash
sudo dnf install dotnet-sdk-6.0
```
**Arch Linux**
```bash
sudo pacman -S dotnet-sdk
```
### 验证 .NET 安装
打开终端或命令提示符,运行:
```bash
dotnet --version
```
你应该看到类似以下输出:
```
6.0.428
```
或更高版本(如 7.0.x, 8.0.x
::: tip 提示
建议安装最新的 LTS长期支持版本。本教程兼容 .NET 6.0 及以上所有版本。
:::
## 安装 Godot 引擎
GFramework 的 Godot 集成需要 **Godot 4.5.1** 或更高版本,并且必须是 **.NET 版本**(不是标准版)。
### 下载 Godot
1. 访问 [Godot 官方下载页](https://godotengine.org/download)
2. 选择 **Godot 4.5.x - .NET** 版本
- **Windows**: 下载 `Godot_v4.5.x-stable_mono_win64.zip`
- **macOS**: 下载 `Godot_v4.5.x-stable_mono_macos.universal.zip`
- **Linux**: 下载 `Godot_v4.5.x-stable_mono_linux_x86_64.zip`
### 安装 Godot
**Windows**
1. 解压下载的 ZIP 文件
2. 将文件夹移动到合适的位置(如 `C:\Godot\`
3. 双击 `Godot_v4.5.x-stable_mono_win64.exe` 启动
**macOS**
1. 解压下载的 ZIP 文件
2. 将 `Godot.app` 拖动到 `应用程序` 文件夹
3. 首次运行时,右键点击 → 打开(绕过安全检查)
**Linux**
1. 解压下载的 ZIP 文件
2. 添加可执行权限:
```bash
chmod +x Godot_v4.5.x-stable_mono_linux.x86_64
```
3. 运行:
```bash
./Godot_v4.5.x-stable_mono_linux.x86_64
```
### 配置 Godot .NET 支持
首次启动 Godot 时,编辑器会自动检测 .NET SDK。
1. 打开 Godot 编辑器
2. 点击菜单 **编辑器 → 编辑器设置**
3. 在左侧导航中找到 **Mono → Builds**
4. 确认 **Build Tool** 设置为 `dotnet CLI`
如果 Godot 提示找不到 .NET SDK确保
- .NET SDK 已正确安装
- 终端中能运行 `dotnet --version`
- 重启 Godot 编辑器
::: warning 注意
必须下载 **.NET (Mono) 版本** 的 Godot标准版不支持 C# 开发。文件名中应包含 `mono` 字样。
:::
## 安装 IDE可选但推荐
虽然可以使用任何文本编辑器,但专业的 IDE 能大幅提升开发效率。
### Visual Studio 2022推荐 Windows 用户)
1. 下载 [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)(免费)
2. 安装时选择以下工作负载:
- **.NET 桌面开发**
- **游戏开发 → 使用 Unity 的游戏开发**(可选,提供更好的游戏开发工具)
3. 确保勾选 **.NET 6.0 SDK**
### JetBrains Rider推荐跨平台用户
1. 下载 [JetBrains Rider](https://www.jetbrains.com/rider/)
2. 安装并激活(提供 30 天试用,或使用社区许可证)
3. 首次启动时Rider 会自动检测 .NET SDK
**优势**
- 跨平台支持Windows, macOS, Linux
- 出色的代码补全和重构工具
- 原生支持 Godot 项目
### Visual Studio Code
1. 下载 [Visual Studio Code](https://code.visualstudio.com/)
2. 安装以下扩展:
- **C# (Microsoft)**
- **C# Dev Kit**
- **godot-tools**
::: tip IDE 选择建议
- **Windows 用户**Visual Studio 2022 或 Rider
- **macOS/Linux 用户**Rider 或 VS Code
- **轻量级需求**VS Code
:::
## 环境验证
完成所有安装后,让我们创建一个测试项目来验证环境。
### 1. 创建测试控制台项目
打开终端,运行:
```bash
# 创建新的控制台项目
dotnet new console -n GFrameworkTest
cd GFrameworkTest
# 添加 GFramework 核心包
dotnet add package GeWuYou.GFramework.Core
# 编译测试
dotnet build
```
如果编译成功,说明 .NET 环境配置正确。
### 2. 创建测试 Godot 项目
1. 打开 Godot 编辑器
2. 点击 **新建项目**
3. 选择项目路径,命名为 `GodotTest`
4. **渲染器** 选择 **Forward+** 或 **Mobile**
5. 点击 **创建并编辑**
项目创建后Godot 会提示初始化 C# 项目:
![初始化 C# 项目](../assets/basic-tutorial/image-20260211210657387.png)
点击 **创建 C# 解决方案**Godot 会自动生成 `.csproj` 文件。
### 3. 验证 Godot C# 支持
在 Godot 编辑器中:
1. 右键点击 **文件系统** 面板的根目录
2. 选择 **创建新脚本**
3. **语言** 选择 **C#**
4. 脚本名称输入 `TestScript.cs`
5. 点击 **创建**
如果脚本成功创建并打开外部编辑器,说明 Godot 和 .NET 集成正常。
### 4. 完整验证脚本
将以下代码粘贴到 `TestScript.cs`
```csharp
using Godot;
using System;
public partial class TestScript : Node
{
public override void _Ready()
{
GD.Print("✅ Godot + .NET 环境配置成功!");
GD.Print($"✅ .NET 版本: {Environment.Version}");
GD.Print("✅ 准备开始使用 GFramework");
}
}
```
在场景中添加一个 Node 节点,附加这个脚本,然后点击 **运行场景**F6
如果在输出面板看到:
```
✅ Godot + .NET 环境配置成功!
✅ .NET 版本: 6.0.x
✅ 准备开始使用 GFramework
```
恭喜!你的环境已经完全配置好了!
## 常见问题排查
### Godot 找不到 .NET SDK
**症状**Godot 提示"无法找到 .NET SDK"
**解决方案**
1. 确认终端中能运行 `dotnet --version`
2. 重启 Godot 编辑器
3. 检查 **编辑器设置 → Mono → Builds** 中的 .NET 路径
4. 尝试手动指定 .NET SDK 路径
### MSBuild 错误
**症状**:编译时提示 MSBuild 相关错误
**解决方案**
```bash
# 清理并重新生成项目
dotnet clean
dotnet build
```
### macOS 安全限制
**症状**macOS 阻止 Godot 运行
**解决方案**
1. 右键点击 `Godot.app`
2. 选择 **打开**(不是双击)
3. 在弹出的对话框中点击 **打开**
4. 后续可以正常双击启动
### Linux 缺少依赖
**症状**Linux 下 Godot 无法启动
**解决方案**
```bash
# Ubuntu/Debian
sudo apt-get install libxcursor1 libxinerama1 libxi6 libxrandr2 libgl1
# Fedora
sudo dnf install libXcursor libXinerama libXi libXrandr mesa-libGL
```
## 下一步
环境配置完成!现在你已经准备好开始使用 GFramework 了。
在下一章中,我们将:
- 创建一个新的 Godot 项目
- 引入 GFramework NuGet 包
- 搭建基础的项目架构
- 创建游戏入口点
👉 [第 2 章:项目创建与初始化](./02-project-setup.md)
---
::: details 本章检查清单
- [ ] .NET SDK 已安装并可以运行 `dotnet --version`
- [ ] Godot 4.5.1+ .NET 版本已安装
- [ ] IDE 已安装Visual Studio / Rider / VS Code
- [ ] 测试项目成功编译
- [ ] Godot 能够创建和运行 C# 脚本
:::

View File

@ -0,0 +1,484 @@
---
prev:
text: '环境准备'
link: './01-environment'
next:
text: '基础计数器实现'
link: './03-counter-basic'
---
# 第 2 章:项目创建与初始化
本章将指导你创建一个新的 Godot 项目,引入 GFramework并搭建基础的架构结构。
## 创建 Godot 项目
### 1. 新建项目
1. 打开 Godot 编辑器
2. 点击 **新建项目**
3. 配置项目信息:
- **项目名称**`MyGFrameworkGame`
- **项目路径**:选择合适的文件夹
- **渲染器**Forward+ 或 Mobile根据需求选择
4. 点击 **创建并编辑**
### 2. 初始化 C# 项目
项目创建后Godot 会提示初始化 C# 支持:
![初始化 C# 项目](../assets/basic-tutorial/image-20260211210657387.png)
点击 **创建 C# 解决方案**Godot 会自动生成:
- `MyGFrameworkGame.csproj` - C# 项目文件
- `MyGFrameworkGame.sln` - 解决方案文件
### 3. 配置项目结构
在项目根目录创建以下文件夹:
```
MyGFrameworkGame/
├── scripts/ # C# 脚本代码
│ ├── architecture/ # 架构相关
│ ├── module/ # 模块定义
│ ├── model/ # 数据模型
│ ├── system/ # 系统逻辑
│ ├── utility/ # 工具类
│ └── app/ # 应用逻辑
├── scenes/ # Godot 场景文件
├── assets/ # 游戏资源(图片、音频等)
├── global/ # 全局脚本
└── project.godot # Godot 项目配置
```
::: tip 文件夹组织建议
这只是推荐的结构,你可以根据项目需求调整。核心原则是:
- **按功能分类**(如 model、system
- **保持清晰**(避免过深的嵌套)
- **易于导航**(团队成员能快速找到文件)
:::
## 引入 GFramework
### 方式一:使用命令行(推荐)
打开项目根目录的终端,运行:
```bash
# 核心能力 + 游戏扩展(一键安装)
dotnet add package GeWuYou.GFramework
# Godot 集成
dotnet add package GeWuYou.GFramework.Godot
# 源码生成器(可选,但推荐)
dotnet add package GeWuYou.GFramework.SourceGenerators
```
::: details 分包安装(了解即可)
如果需要更细粒度的控制,也可以单独安装各个包:
```bash
# 核心能力
dotnet add package GeWuYou.GFramework.Core
dotnet add package GeWuYou.GFramework.Core.Abstractions
# 游戏扩展
dotnet add package GeWuYou.GFramework.Game
dotnet add package GeWuYou.GFramework.Game.Abstractions
# Godot 集成
dotnet add package GeWuYou.GFramework.Godot
# 源码生成器
dotnet add package GeWuYou.GFramework.SourceGenerators
```
:::
### 方式二:使用 IDE
**Visual Studio / Rider**
1. 右键点击项目 → **管理 NuGet 包**
2. 搜索 `GeWuYou.GFramework`
3. 安装以下包:
- `GeWuYou.GFramework`
- `GeWuYou.GFramework.Godot`
- `GeWuYou.GFramework.SourceGenerators`
![NuGet 包管理](../assets/basic-tutorial/image-20260211211756993.png)
### 验证安装
安装完成后,运行:
```bash
dotnet build
```
如果编译成功,说明 GFramework 已正确引入。
## 搭建基础架构
### 1. 创建游戏架构类
`scripts/architecture/` 创建 `GameArchitecture.cs`
```csharp
using GFramework.Godot.architecture;
namespace MyGFrameworkGame.scripts.architecture;
/// <summary>
/// 游戏架构类,负责管理整个应用的模块和依赖
/// </summary>
public class GameArchitecture : AbstractArchitecture
{
/// <summary>
/// 安装游戏所需的各个功能模块
/// </summary>
protected override void InstallModules()
{
// 稍后在这里注册模块
}
}
```
::: tip 架构类的作用
`GameArchitecture` 是整个应用的核心入口点,它负责:
- 注册和管理所有模块
- 提供依赖注入容器
- 管理应用生命周期
:::
### 2. 创建模型模块
`scripts/module/` 创建 `ModelModule.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
namespace MyGFrameworkGame.scripts.module;
/// <summary>
/// 模型模块,负责注册所有的数据模型
/// </summary>
public class ModelModule : AbstractModule
{
/// <summary>
/// 安装模型到架构中
/// </summary>
public override void Install(IArchitecture architecture)
{
// 稍后在这里注册 Model
}
}
```
::: tip Model 是什么?
**Model模型** 表示游戏的状态和数据。它是简单的数据容器,负责:
- 存储应用状态(如计数器的值)
- 提供数据访问接口
- 发送状态变化事件
Model **不应该**包含业务逻辑或 UI 逻辑。
:::
### 3. 创建系统模块
`scripts/module/` 创建 `SystemModule.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
namespace MyGFrameworkGame.scripts.module;
/// <summary>
/// 系统模块,负责注册所有的系统逻辑
/// </summary>
public class SystemModule : AbstractModule
{
/// <summary>
/// 安装系统到架构中
/// </summary>
public override void Install(IArchitecture architecture)
{
// 稍后在这里注册 System
}
}
```
::: tip System 是什么?
**System系统** 包含游戏的业务逻辑和规则。它负责:
- 响应状态变化
- 执行游戏规则
- 协调多个 Model 的交互
示例设置系统、UI 路由系统、场景管理系统等。
:::
### 4. 创建工具模块
`scripts/module/` 创建 `UtilityModule.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
namespace MyGFrameworkGame.scripts.module;
/// <summary>
/// 工具模块,负责注册所有的工具类
/// </summary>
public class UtilityModule : AbstractModule
{
/// <summary>
/// 安装工具类到架构中
/// </summary>
public override void Install(IArchitecture architecture)
{
// 稍后在这里注册 Utility
}
}
```
::: tip Utility 是什么?
**Utility工具类** 提供可复用的辅助函数,负责:
- 无状态的计算和验证
- 数据转换和格式化
- 纯函数式的业务规则
示例:数学计算、数据校验、文件操作等。
:::
### 5. 注册模块到架构
回到 `GameArchitecture.cs`,注册刚创建的模块:
```csharp
using GFramework.Godot.architecture;
using MyGFrameworkGame.scripts.module;
namespace MyGFrameworkGame.scripts.architecture;
/// <summary>
/// 游戏架构类,负责管理整个应用的模块和依赖
/// </summary>
public class GameArchitecture : AbstractArchitecture
{
/// <summary>
/// 安装游戏所需的各个功能模块
/// </summary>
protected override void InstallModules()
{
// 按顺序安装模块
InstallModule(new ModelModule()); // 数据模型
InstallModule(new SystemModule()); // 系统逻辑
InstallModule(new UtilityModule()); // 工具类
}
}
```
::: warning 模块安装顺序
模块的安装顺序很重要!如果 System 依赖 Model那么 Model 必须先安装。
推荐顺序:
1. Utility无依赖
2. Model可能依赖 Utility
3. System可能依赖 Model 和 Utility
:::
## 创建游戏入口点
### 1. 创建全局类
在 Godot 编辑器中:
1. 点击 **项目 → 项目设置**
2. 选择 **自动加载** 标签
3. 点击文件夹图标,选择 **新建脚本**
4. 配置脚本:
- **语言**C#
- **模板**Node
- **类名**`GameEntryPoint`
- **路径**`global/GameEntryPoint.cs`
5. 点击 **创建**
6. 将脚本添加为自动加载单例
![创建全局类](../assets/basic-tutorial/image-20260211222402064.png)
### 2. 实现入口点逻辑
编辑 `global/GameEntryPoint.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using Godot;
using MyGFrameworkGame.scripts.architecture;
namespace MyGFrameworkGame.global;
/// <summary>
/// 游戏入口点,在游戏启动时初始化架构
/// </summary>
public partial class GameEntryPoint : Node
{
/// <summary>
/// 获取游戏架构的静态属性
/// </summary>
public static IArchitecture Architecture { get; private set; } = null!;
/// <summary>
/// 当节点首次进入场景树时调用
/// </summary>
public override void _Ready()
{
// 创建游戏架构实例
Architecture = new GameArchitecture();
// 初始化游戏架构(会自动调用 InstallModules
Architecture.Initialize();
GD.Print("✅ GFramework 架构初始化完成!");
}
}
```
### 3. 验证架构初始化
运行游戏F5在输出面板应该看到
```
✅ GFramework 架构初始化完成!
```
如果看到这条消息,说明架构初始化成功!
## 项目结构总览
此时,你的项目结构应该如下:
```
MyGFrameworkGame/
├── scripts/
│ ├── architecture/
│ │ └── GameArchitecture.cs ✅ 游戏架构
│ └── module/
│ ├── ModelModule.cs ✅ 模型模块
│ ├── SystemModule.cs ✅ 系统模块
│ └── UtilityModule.cs ✅ 工具模块
├── global/
│ └── GameEntryPoint.cs ✅ 入口点
├── scenes/
├── assets/
├── project.godot
├── MyGFrameworkGame.csproj ✅ C# 项目文件
└── MyGFrameworkGame.sln ✅ 解决方案文件
```
## 理解架构设计
让我们回顾一下刚才搭建的架构:
```
GameEntryPoint (入口)
GameArchitecture (架构核心)
InstallModules (安装模块)
├── ModelModule → 注册 Model
├── SystemModule → 注册 System
└── UtilityModule → 注册 Utility
```
### 为什么要分模块?
1. **关注点分离**Model、System、Utility 各司其职
2. **依赖管理**:通过模块控制初始化顺序
3. **可维护性**:相关功能集中在一个模块中
4. **可扩展性**:新增功能只需添加新模块
### 依赖注入的优势
通过 `architecture.Register*()`GFramework 提供了依赖注入能力:
```csharp
// 注册时
architecture.RegisterModel(new CounterModel());
// 使用时(在 Controller 或 Command 中)
var model = this.GetModel<ICounterModel>();
```
优势:
- **无需手动管理单例**
- **自动处理依赖关系**
- **便于单元测试**(可以 mock 依赖)
## 常见问题
### 编译错误:"找不到类型或命名空间"
**原因**NuGet 包未正确安装
**解决方案**
```bash
dotnet restore
dotnet build
```
### GameEntryPoint 未自动执行
**原因**:未添加为自动加载
**解决方案**
1. 打开 **项目 → 项目设置 → 自动加载**
2. 确认 `GameEntryPoint` 在列表中
3. 确认 **启用** 复选框已勾选
### 模块注册顺序错误
**症状**System 初始化时找不到 Model
**解决方案**
`InstallModules()` 中调整顺序,确保被依赖的模块先安装。
## 下一步
基础架构搭建完成!虽然现在还没有具体功能,但我们已经建立了一个坚实的基础。
在下一章中,我们将:
- 创建一个简单的计数器 UI
- 实现基础的增减功能
- 分析传统实现的问题
- 为后续的架构优化做准备
👉 [第 3 章:基础计数器实现](./03-counter-basic.md)
---
::: details 本章检查清单
- [ ] Godot 项目已创建
- [ ] C# 解决方案已初始化
- [ ] GFramework NuGet 包已安装
- [ ] GameArchitecture 类已创建
- [ ] 三大模块Model/System/Utility已创建
- [ ] GameEntryPoint 入口点已配置
- [ ] 运行游戏能看到"架构初始化完成"消息
:::

View File

@ -0,0 +1,417 @@
---
prev:
text: '项目创建与初始化'
link: './02-project-setup'
next:
text: '引入 Model 重构'
link: './04-model-refactor'
---
# 第 3 章:基础计数器实现
在本章中,我们将创建一个简单的计数器应用。首先用最直接的方式实现功能,然后分析这种实现方式的问题,为后续的架构优化做准备。
## 创建 UI 场景
### 1. 创建主场景
1. 在 Godot 编辑器中,点击 **场景 → 新建场景**
2. 选择 **用户界面** 作为根节点类型Control
3. 将根节点重命名为 `App`
4. 保存场景为 `scenes/App.tscn`
### 2. 搭建 UI 布局
添加以下节点结构:
```
App (Control)
├── CenterContainer (CenterContainer)
│ └── VBoxContainer (VBoxContainer)
│ ├── Label (Label) - 唯一名称:%Label
│ ├── HBoxContainer (HBoxContainer)
│ │ ├── SubButton (Button) - 唯一名称:%SubButton
│ │ └── AddButton (Button) - 唯一名称:%AddButton
```
::: tip 唯一名称的作用
在 Godot 中,带 `%` 前缀的唯一名称可以让我们在代码中更方便地访问节点:
```csharp
GetNode("%Label") // 无论节点在哪个层级都能找到
```
:::
### 3. 配置节点属性
**App (Control)**
- **布局**Anchor Preset → Full Rect
**CenterContainer**
- **布局**Anchor Preset → Full Rect
**VBoxContainer**
- **主题覆盖 → 常量 → Separation**20
**Label (%Label)**
- **文本**Count: 0
- **水平对齐**Center
- **主题覆盖 → 字体大小**32
**SubButton (%SubButton)**
- **文本**-
- **最小尺寸**:宽度 60, 高度 40
**AddButton (%AddButton)**
- **文本**+
- **最小尺寸**:宽度 60, 高度 40
### 4. 最终效果
完成后的界面应该如下:
![计数器界面](../assets/basic-tutorial/image-20260211214905664.png)
## 实现基础功能
### 1. 创建 App 脚本
右键点击 `App` 节点,选择 **附加脚本**
- **语言**C#
- **模板**Node: Basic Script
- **路径**`scripts/app/App.cs`
点击 **创建**
### 2. 编写代码
编辑 `App.cs`,实现基础功能:
```csharp
using Godot;
namespace MyGFrameworkGame.scripts.app;
/// <summary>
/// 计数器应用的主控制器
/// </summary>
public partial class App : Control
{
/// <summary>
/// 获取 Add 按钮节点
/// </summary>
private Button AddButton => GetNode<Button>("%AddButton");
/// <summary>
/// 获取 Sub 按钮节点
/// </summary>
private Button SubButton => GetNode<Button>("%SubButton");
/// <summary>
/// 获取 Label 节点
/// </summary>
private Label Label => GetNode<Label>("%Label");
/// <summary>
/// 计数器的当前值
/// </summary>
private int _count;
/// <summary>
/// 节点准备就绪时调用
/// </summary>
public override void _Ready()
{
// 绑定按钮点击事件
AddButton.Pressed += () =>
{
_count++;
UpdateView();
};
SubButton.Pressed += () =>
{
_count--;
UpdateView();
};
// 初始化界面
UpdateView();
}
/// <summary>
/// 更新界面显示
/// </summary>
private void UpdateView()
{
Label.Text = $"Count: {_count}";
}
}
```
### 3. 设置为主场景
1. 点击 **项目 → 项目设置 → 应用 → 运行**
2. **主场景** 选择 `res://scenes/App.tscn`
3. 点击 **关闭**
### 4. 运行游戏
**F5** 运行游戏,点击 + 和 - 按钮,观察计数变化:
![运行效果](../assets/basic-tutorial/image-20260211215626940.png)
功能正常工作!🎉
## 代码分析
虽然功能实现了,但让我们分析一下这段代码的结构。
### 识别 MVC 组成部分
#### View视图
```csharp
// 节点引用
private Button AddButton => GetNode<Button>("%AddButton");
private Button SubButton => GetNode<Button>("%SubButton");
private Label Label => GetNode<Label>("%Label");
// 渲染逻辑
private void UpdateView()
{
Label.Text = $"Count: {_count}";
}
```
**职责**:呈现 UI接收用户输入
#### Model数据
```csharp
// 状态数据
private int _count;
```
**职责**:保存应用状态
#### Controller控制器
```csharp
// 事件绑定和业务逻辑
AddButton.Pressed += () =>
{
_count++; // 修改 Model
UpdateView(); // 通知 View
};
SubButton.Pressed += () =>
{
_count--;
UpdateView();
};
```
**职责**:响应事件,协调 Model 和 View
### 执行流程
```
用户点击按钮
Pressed 事件触发
修改 _count (Model)
调用 UpdateView() (View)
界面更新
```
## 设计问题分析
虽然代码简洁明了,但存在以下问题:
### 问题 1View 与 Controller 耦合过紧
```csharp
AddButton.Pressed += () =>
{
_count++; // ← 业务逻辑
UpdateView(); // ← UI 更新
};
```
**问题**
- UI 控件和业务逻辑直接耦合
- 修改逻辑可能影响界面,反之亦然
- 当功能复杂化时,代码难以维护
**示例**:如果将来需要:
- 多个按钮触发相同逻辑
- 多个 Label 显示相同数据
- 支持键盘快捷键
当前实现会导致代码重复和混乱。
### 问题 2数据状态没有抽象
```csharp
private int _count;
```
**问题**
- 计数器直接存在 Controller 里
- 没有独立的 Model 层
- 无法在不同界面或场景复用数据
- 无法单独测试逻辑
**示例**:如果需要:
- 在多个场景共享计数器状态
- 保存和加载计数器数据
- 实现撤销/重做功能
当前实现无法满足这些需求。
### 问题 3缺乏统一的上下文管理
```csharp
public override void _Ready()
{
// 事件绑定和视图更新直接写在这里
AddButton.Pressed += ...
}
```
**问题**
- 当应用复杂化,涉及异步操作、多场景时
- 容易出现状态不一致或上下文丢失
- 无法在多个 Controller 间共享状态
**示例**:如果计数器需要:
- 在多个 Service 之间共享
- 跨场景保持状态
- 支持网络同步
当前实现会非常麻烦。
### 问题 4可测试性低
```csharp
_count++; // ← 逻辑和按钮点击事件耦合
```
**问题**
- `_count` 逻辑和按钮点击事件紧密耦合
- 无法在单元测试中独立测试计数器逻辑
- 只能在运行场景里手动点击按钮验证
**示例**:无法写出这样的单元测试:
```csharp
[Test]
public void Increment_ShouldIncreaseCount()
{
var counter = new CounterModel();
counter.Increment();
Assert.AreEqual(1, counter.Count);
}
```
## 问题总结
| 问题 | 后果 | 示例场景 |
|--------------------|-----------|-----------------|
| View-Controller 耦合 | 代码重复,难以维护 | 多个界面显示相同数据 |
| Model 未抽象 | 无法复用,难以测试 | 跨场景共享状态 |
| 缺乏上下文管理 | 状态混乱,扩展困难 | 多 Controller 协作 |
| 可测试性低 | 只能手动测试 | 业务逻辑复杂化 |
## 重构的必要性
随着项目规模增长,这些问题会被放大:
**场景 1新增功能**
```csharp
AddButton.Pressed += () =>
{
_count++;
if (_count > 10) { /* 做某事 */ }
if (_count < -10) { /* 做别的事 */ }
SaveToFile();
PlaySound();
UpdateAchievement();
UpdateView();
};
```
→ **Controller 迅速膨胀**
**场景 2跨场景共享**
```csharp
// 场景 A
private int _count;
// 场景 B
private int _count; // ← 重复的状态,如何同步?
```
→ **状态同步困难**
**场景 3单元测试**
```csharp
// 如何测试这个逻辑?
AddButton.Pressed += () => { _count++; };
```
→ **无法测试**
## 下一步
我们已经实现了基础功能,并分析了当前实现的问题。在下一章中,我们将:
1. **引入 Model 层**:将状态抽离到独立的 Model
2. **使用事件系统**:实现数据驱动的 UI 更新
3. **解耦 View 和 Controller**:让代码更清晰、可测试
通过这些重构,你将看到 GFramework 如何帮助我们解决这些设计问题。
👉 [第 4 章:引入 Model 重构](./04-model-refactor.md)
---
::: details 本章检查清单
- [ ] UI 场景已创建App.tscn
- [ ] 节点结构正确Label, AddButton, SubButton
- [ ] App.cs 脚本已创建并附加到根节点
- [ ] 运行游戏,点击按钮能正常增减计数
- [ ] 理解了当前实现的 4 个主要问题
:::
::: tip 思考题
1. 如果要在另一个场景也显示这个计数器,你会怎么做?
2. 如何实现"计数超过 10 时播放音效"的功能?
3. 能否在不运行游戏的情况下测试计数逻辑?
这些问题在下一章中都会得到解答!
:::

View File

@ -0,0 +1,552 @@
---
prev:
text: '基础计数器实现'
link: './03-counter-basic'
next:
text: '命令系统优化'
link: './05-command-system'
---
# 第 4 章:引入 Model 重构
在上一章中,我们实现了基础功能但也发现了一些设计问题。本章将通过引入 **Model 层****事件系统**,重构我们的计数器应用。
## 理解 Model 的作用
在 GFramework 中,**Model模型** 负责:
**存储应用状态**:保存数据,如计数器的值
**提供数据访问接口**:通过方法暴露数据操作
**发送状态变化事件**:当数据改变时通知监听者
Model **不应该**
❌ 包含 UI 逻辑(如更新 Label
❌ 直接调用 Controller 方法
❌ 知道谁在使用它
## 创建 Model
### 1. 定义 Model 接口
`scripts/model/` 创建 `ICounterModel.cs`
```csharp
using GFramework.Core.Abstractions.model;
namespace MyGFrameworkGame.scripts.model;
/// <summary>
/// 计数器模型接口,定义计数器的基本操作
/// </summary>
public interface ICounterModel : IModel
{
/// <summary>
/// 获取当前计数器的值
/// </summary>
int Count { get; }
/// <summary>
/// 将计数器的值增加 1
/// </summary>
void Increment();
/// <summary>
/// 将计数器的值减少 1
/// </summary>
void Decrement();
}
```
::: tip 为什么要定义接口?
使用接口的好处:
- **依赖倒置**:依赖抽象而不是具体实现
- **易于测试**:可以创建 Mock 实现
- **灵活替换**:可以切换不同的实现
这是 SOLID 原则中的 **依赖倒置原则DIP**
:::
### 2. 实现 Model 类
`scripts/model/` 创建 `CounterModel.cs`
```csharp
using GFramework.Core.extensions;
using GFramework.Core.model;
namespace MyGFrameworkGame.scripts.model;
/// <summary>
/// 计数器模型实现
/// </summary>
public class CounterModel : AbstractModel, ICounterModel
{
/// <summary>
/// 获取当前计数器的值
/// </summary>
public int Count { get; private set; }
/// <summary>
/// 初始化 Model可选
/// </summary>
protected override void OnInit()
{
// 可以在这里初始化默认值或加载数据
}
/// <summary>
/// 计数变化事件
/// </summary>
public sealed record ChangedCountEvent
{
public int Count { get; init; }
}
/// <summary>
/// 增加计数器的值
/// </summary>
public void Increment()
{
Count++;
// 发送事件通知监听者
this.SendEvent(new ChangedCountEvent { Count = Count });
}
/// <summary>
/// 减少计数器的值
/// </summary>
public void Decrement()
{
Count--;
// 发送事件通知监听者
this.SendEvent(new ChangedCountEvent { Count = Count });
}
}
```
::: tip 事件驱动设计
注意 `Increment``Decrement` 方法的最后一行:
```csharp
this.SendEvent(new ChangedCountEvent { Count = Count });
```
这是 **事件驱动架构** 的核心:
- Model 只负责改变状态并发送事件
- **不关心谁在监听**
- **不直接调用 UI 更新**
这实现了完全解耦!
:::
### 3. 注册 Model
编辑 `scripts/module/ModelModule.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.module;
/// <summary>
/// 模型模块,负责注册所有的数据模型
/// </summary>
public class ModelModule : AbstractModule
{
/// <summary>
/// 安装模型到架构中
/// </summary>
public override void Install(IArchitecture architecture)
{
// 注册 CounterModel 实例
architecture.RegisterModel<ICounterModel>(new CounterModel());
}
}
```
::: warning 注意泛型参数
```csharp
architecture.RegisterModel<ICounterModel>(new CounterModel());
```
第一个泛型参数 `ICounterModel` 是接口类型,用于后续获取时指定类型。
:::
## 重构 Controller
### 1. 启用上下文感知
编辑 `App.cs`,添加 `IController` 接口和特性:
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.Core.extensions;
using GFramework.SourceGenerators.Abstractions.rule;
using Godot;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.app;
/// <summary>
/// 计数器应用的主控制器
/// </summary>
[ContextAware] // ← 启用上下文感知(由源码生成器处理)
public partial class App : Control, IController // ← 实现 IController 接口
{
// ... 其他代码
}
```
::: tip ContextAware 特性
`[ContextAware]` 特性由 **源码生成器** 处理,它会自动生成:
- `IArchitecture Architecture { get; }` 属性
- 依赖注入相关的辅助代码
这使得我们可以使用 `this.GetModel()` 等扩展方法。
:::
### 2. 获取 Model 并监听事件
修改 `_Ready` 方法:
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.Core.extensions;
using GFramework.SourceGenerators.Abstractions.rule;
using Godot;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.app;
[ContextAware]
public partial class App : Control, IController
{
private Button AddButton => GetNode<Button>("%AddButton");
private Button SubButton => GetNode<Button>("%SubButton");
private Label Label => GetNode<Label>("%Label");
private ICounterModel _counterModel = null!;
public override void _Ready()
{
// 从架构中获取 Model
_counterModel = this.GetModel<ICounterModel>()!;
// 监听 Model 的事件
this.RegisterEvent<CounterModel.ChangedCountEvent>(e =>
{
UpdateView(e.Count);
});
// 按钮点击时调用 Model 的方法
AddButton.Pressed += () =>
{
_counterModel.Increment();
};
SubButton.Pressed += () =>
{
_counterModel.Decrement();
};
// 初始化界面
UpdateView(_counterModel.Count);
}
/// <summary>
/// 更新界面显示
/// </summary>
private void UpdateView(int count)
{
Label.Text = $"Count: {count}";
}
}
```
### 3. 运行游戏
**F5** 运行游戏,测试功能:
![运行效果](../assets/basic-tutorial/image-20260211223654625.png)
功能依然正常!但现在架构更清晰了。
## 对比重构前后
### 重构前
```csharp
// ❌ 状态在 Controller 里
private int _count;
// ❌ UI 直接修改状态
AddButton.Pressed += () =>
{
_count++; // 修改状态
UpdateView(); // 更新 UI
};
// ❌ 状态和 UI 耦合
private void UpdateView()
{
Label.Text = $"Count: {_count}";
}
```
**问题**
- 状态、逻辑、UI 混在一起
- 无法复用状态
- 无法单独测试
### 重构后
```csharp
// ✅ 状态在 Model 里
private ICounterModel _counterModel;
// ✅ UI 只发起操作
AddButton.Pressed += () =>
{
_counterModel.Increment(); // 调用 Model 方法
};
// ✅ 通过事件更新 UI
this.RegisterEvent<CounterModel.ChangedCountEvent>(e =>
{
UpdateView(e.Count); // 响应事件
});
```
**优势**
- 关注点分离
- Model 可复用、可测试
- UI 只负责展示
## 理解事件驱动架构
### 数据流向
```
用户点击按钮
Controller: _counterModel.Increment()
Model: Count++
Model: SendEvent(ChangedCountEvent)
Controller: RegisterEvent<ChangedCountEvent>
Controller: UpdateView(e.Count)
界面更新
```
### 为什么不直接调用 UpdateView
**方式一:直接调用(❌ 不推荐)**
```csharp
public void Increment()
{
Count++;
_view.UpdateView(Count); // ← Model 依赖 View
}
```
**问题**
- Model 依赖 View强耦合
- Model 无法复用
- 无法扩展(如果有多个监听者怎么办?)
**方式二:事件驱动(✅ 推荐)**
```csharp
public void Increment()
{
Count++;
this.SendEvent(new ChangedCountEvent(Count)); // ← 发送事件
}
// View 订阅事件
this.RegisterEvent<ChangedCountEvent>(e => UpdateView(e.Count));
```
**优势**
- Model 不知道 View 的存在
- 可以有多个订阅者
- 完全解耦
### 事件系统的威力
假设将来需要:
**需求 1多个界面显示计数器**
```csharp
// Scene A
this.RegisterEvent<ChangedCountEvent>(e => LabelA.Text = $"Count: {e.Count}");
// Scene B
this.RegisterEvent<ChangedCountEvent>(e => LabelB.Text = $"Count: {e.Count}");
```
无需修改 Model
**需求 2计数变化时播放音效**
```csharp
this.RegisterEvent<ChangedCountEvent>(e =>
{
if (e.Count > 0) PlaySound("increment.wav");
else PlaySound("decrement.wav");
});
```
只需添加新的监听者!
## 解决的问题
回顾第 3 章提出的问题:
### ✅ 问题 1View-Controller 耦合
**解决方案**
```csharp
// 之前UI 直接修改状态
_count++;
UpdateView();
// 现在UI 通过 Model 操作
_counterModel.Increment();
// UI 通过事件更新
this.RegisterEvent<ChangedCountEvent>(e => UpdateView(e.Count));
```
现在的流程:`Button → Model → Event → View`,这是典型的 **单向数据流**
### ✅ 问题 2Model 未抽象
**解决方案**
```csharp
// 之前:状态在 Controller 里
private int _count;
// 现在:状态在独立的 Model
private ICounterModel _counterModel;
_counterModel = this.GetModel<ICounterModel>();
```
现在:
- Model 可复用
- Model 可替换(依赖接口)
- Controller 不关心具体实现
- **可以单独测试 Model**
### ✅ 问题 3缺乏上下文管理
**解决方案**
```csharp
[ContextAware]
this.GetModel<ICounterModel>()
this.RegisterEvent<ChangedCountEvent>(...)
```
现在:
- Model 由 Context 提供
- 事件系统由框架管理
- 生命周期由框架管理
- 不需要手动 new Model
- 不会出现跨 Controller 状态错乱
### ⚠️ 问题 4可测试性部分改善
**Model 层**
```csharp
// ✅ Model 现在完全可测试
var model = new CounterModel();
model.Increment();
Assert.AreEqual(1, model.Count);
```
**Controller 层**
```csharp
// ⚠️ Controller 仍然依赖 Godot 节点
App : Control
```
Controller 仍然不能在纯单元测试中实例化,但这是合理的:
- **View 本来就不应该被单元测试**
- **业务逻辑已经在 Model可以测试**
- **Controller 只是桥梁**
## 核心收获
通过这次重构,我们学到了:
| 概念 | 解释 |
|---------------|---------------------------------------|
| **Model 的职责** | 存储状态,提供接口,发送事件 |
| **事件驱动架构** | Model 不关心谁在监听,完全解耦 |
| **依赖注入** | 通过 `GetModel` 获取依赖,而不是 `new` |
| **单向数据流** | Button → Model → Event → View |
| **关注点分离** | 数据Model、逻辑Controller、展示View各司其职 |
## 下一步
虽然我们引入了 Model但 Controller 仍然承担着 **交互逻辑**
```csharp
AddButton.Pressed += () =>
{
_counterModel.Increment(); // ← 这是交互逻辑
};
```
当功能复杂化时(如需要保存、验证、异步操作),这些逻辑会让 Controller 变得臃肿。
在下一章中,我们将引入 **Command命令模式**,进一步解耦 Controller。
👉 [第 5 章:命令系统优化](./05-command-system.md)
---
::: details 本章检查清单
- [ ] ICounterModel 接口已创建
- [ ] CounterModel 实现已创建
- [ ] Model 已注册到 ModelModule
- [ ] App.cs 添加了 [ContextAware] 和 IController
- [ ] 通过事件系统更新 UI
- [ ] 运行游戏,功能正常
- [ ] 理解了事件驱动架构的优势
:::
::: tip 思考题
1. 如果要在 Model 中添加"最大值限制"功能,应该怎么做?
2. 如果需要持久化计数器(保存到文件),应该在哪一层实现?
3. 事件驱动和直接调用的主要区别是什么?
这些问题会在后续章节中继续探讨!
:::

View File

@ -0,0 +1,535 @@
---
prev:
text: '引入 Model 重构'
link: './04-model-refactor'
next:
text: 'Utility 与 System'
link: './06-utility-system'
---
# 第 5 章:命令系统优化
在上一章中,我们通过 Model 和事件系统实现了数据驱动的架构。但 Controller 仍然承担着交互逻辑,本章将引入 **Command命令模式
** 进一步优化。
## Controller 的职责问题
### 当前代码
```csharp
public override void _Ready()
{
_counterModel = this.GetModel<ICounterModel>()!;
AddButton.Pressed += () =>
{
_counterModel.Increment(); // ← 交互逻辑
};
SubButton.Pressed += () =>
{
_counterModel.Decrement(); // ← 交互逻辑
};
// ...
}
```
看起来很简洁,但这段代码同时承担着:
- **表现逻辑**View Binding`AddButton.Pressed +=`
- **交互逻辑**Interaction Logic`_counterModel.Increment()`
### 为什么这是问题?
现在只是简单的增减,但如果功能变复杂:
```csharp
AddButton.Pressed += async () =>
{
// 1. 验证状态
if (!CanIncrement()) return;
// 2. 执行业务逻辑
await DoSomethingAsync();
_counterModel.Increment();
// 3. 保存数据
await SaveToFileAsync();
// 4. 播放音效
PlaySound("increment.wav");
// 5. 统计埋点
LogAnalytics("counter_incremented");
// 6. 更新成就
UpdateAchievement();
};
```
**问题**
- Controller 迅速膨胀
- 逻辑难以复用(如果键盘快捷键也要增加计数?)
- 难以测试(需要 mock 按钮)
- 违反单一职责原则
## 理解 Command 模式
### Command 的作用
**Command命令** 是一种设计模式,它将"请求"封装成对象:
```
用户操作 → Command → Model
```
优势:
- **解耦**Controller 不关心如何增加计数,只负责"发送命令"
- **复用**:同一个命令可以被多个地方调用
- **扩展**:新增逻辑只需修改命令,不影响 Controller
- **可测试**:可以独立测试命令逻辑
### 职责划分
| 层级 | 职责 |
|----------------|------------|
| **Controller** | 将用户操作转换为命令 |
| **Command** | 封装具体的业务逻辑 |
| **Model** | 存储状态,发送事件 |
## 创建 Command
### 1. 创建增加命令
`scripts/command/` 创建 `IncreaseCountCommand.cs`
```csharp
using GFramework.Core.command;
using GFramework.Core.extensions;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.command;
/// <summary>
/// 增加计数器值的命令
/// </summary>
public class IncreaseCountCommand : AbstractCommand
{
/// <summary>
/// 执行命令的核心逻辑
/// </summary>
protected override void OnExecute()
{
// 获取 Model 并调用方法
var model = this.GetModel<ICounterModel>()!;
model.Increment();
}
}
```
::: tip AbstractCommand
`AbstractCommand` 是 GFramework 提供的基类,它:
- 自动注入 `Architecture` 上下文
- 提供 `GetModel``GetSystem``GetUtility` 等方法
- 管理命令的生命周期
:::
### 2. 创建减少命令
`scripts/command/` 创建 `DecreaseCountCommand.cs`
```csharp
using GFramework.Core.command;
using GFramework.Core.extensions;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.command;
/// <summary>
/// 减少计数器值的命令
/// </summary>
public class DecreaseCountCommand : AbstractCommand
{
/// <summary>
/// 执行命令的核心逻辑
/// </summary>
protected override void OnExecute()
{
var model = this.GetModel<ICounterModel>()!;
model.Decrement();
}
}
```
## 重构 Controller
### 使用命令替换直接调用
编辑 `App.cs`
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.Core.extensions;
using GFramework.SourceGenerators.Abstractions.rule;
using Godot;
using MyGFrameworkGame.scripts.command;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.app;
[ContextAware]
public partial class App : Control, IController
{
private Button AddButton => GetNode<Button>("%AddButton");
private Button SubButton => GetNode<Button>("%SubButton");
private Label Label => GetNode<Label>("%Label");
public override void _Ready()
{
// 监听事件
this.RegisterEvent<CounterModel.ChangedCountEvent>(e =>
{
UpdateView(e.Count);
});
// 使用命令替换直接调用
AddButton.Pressed += () =>
{
this.SendCommand(new IncreaseCountCommand());
};
SubButton.Pressed += () =>
{
this.SendCommand(new DecreaseCountCommand());
};
// 初始化界面
UpdateView();
}
private void UpdateView(int count = 0)
{
Label.Text = $"Count: {count}";
}
}
```
### 运行游戏
**F5** 运行游戏,功能依然正常!
## 对比重构前后
### 重构前(使用 Model
```csharp
AddButton.Pressed += () =>
{
_counterModel.Increment(); // ← 直接调用 Model
};
```
**问题**
- Controller 知道如何增加计数
- 如果逻辑复杂化Controller 会变臃肿
### 重构后(使用 Command
```csharp
AddButton.Pressed += () =>
{
this.SendCommand(new IncreaseCountCommand()); // ← 发送命令
};
```
**优势**
- Controller 不关心如何增加计数
- 逻辑封装在 Command 中
- Controller 只负责"转发用户意图"
## Command 的优势
### 1. 解耦 Controller
**之前**
```csharp
AddButton.Pressed += () =>
{
if (!CanIncrement()) return;
await SaveData();
_counterModel.Increment();
PlaySound();
LogAnalytics();
};
```
Controller 必须知道所有细节。
**现在**
```csharp
AddButton.Pressed += () =>
{
this.SendCommand(new IncreaseCountCommand());
};
```
所有逻辑在 Command 中:
```csharp
protected override void OnExecute()
{
if (!CanIncrement()) return;
await SaveData();
this.GetModel<ICounterModel>()!.Increment();
PlaySound();
LogAnalytics();
}
```
### 2. 逻辑复用
假设需要通过键盘快捷键增加计数:
**之前**
```csharp
AddButton.Pressed += () => { /* 逻辑 */ };
Input.IsActionPressed("increment") => { /* 复制相同逻辑 */ };
```
代码重复!
**现在**
```csharp
AddButton.Pressed += () => this.SendCommand(new IncreaseCountCommand());
Input.IsActionPressed("increment") => this.SendCommand(new IncreaseCountCommand());
```
逻辑只写一次!
### 3. 易于测试
**之前**
```csharp
// 无法测试,必须 mock 按钮
AddButton.Pressed += () => { /* 逻辑 */ };
```
**现在**
```csharp
// 可以直接测试命令
[Test]
public void IncreaseCommand_ShouldIncrementCount()
{
var model = new CounterModel();
var command = new IncreaseCountCommand();
command.Execute();
Assert.AreEqual(1, model.Count);
}
```
### 4. 支持撤销/重做(扩展)
Command 模式天然支持撤销功能:
```csharp
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
// 执行
this.GetModel<ICounterModel>()!.Increment();
}
public void Undo()
{
// 撤销
this.GetModel<ICounterModel>()!.Decrement();
}
}
```
## Command 的实际应用
让我们看一个更复杂的例子:
```csharp
/// <summary>
/// 更改语言命令
/// </summary>
public class ChangeLanguageCommand : AbstractAsyncCommand<ChangeLanguageInput>
{
protected override async Task OnExecuteAsync(ChangeLanguageInput input)
{
// 1. 获取设置 Model
var settingsModel = this.GetModel<ISettingsModel>()!;
// 2. 获取设置数据
var settings = settingsModel.GetData();
// 3. 修改语言配置
settings.Language = input.Language;
// 4. 应用设置(通过 System
await this.GetSystem<ISettingsSystem>()!.Apply();
}
}
```
如果这些逻辑都写在 Controller
```csharp
LanguageButton.Pressed += async () =>
{
var settingsModel = this.GetModel<ISettingsModel>()!;
var settings = settingsModel.GetData();
settings.Language = newLanguage;
await this.GetSystem<ISettingsSystem>()!.Apply();
};
```
**问题**
- Controller 臃肿
- 逻辑分散
- 难以复用
## 理解职责边界
### Controller vs Command
| 层级 | 职责 | 示例 |
|----------------|------------|-------------|
| **Controller** | 将用户操作转换为意图 | "用户点击了增加按钮" |
| **Command** | 封装业务逻辑 | "如何增加计数" |
| **Model** | 存储和管理状态 | "计数的值是多少" |
**类比**
- **Controller**:服务员(接收顾客点单)
- **Command**:厨师(制作菜品)
- **Model**:菜单(菜品信息)
### 何时使用 Command
**应该使用 Command**
- 逻辑超过 3 行
- 需要复用的操作
- 涉及多个 Model/System 的协作
- 需要异步操作
- 需要撤销/重做
**不需要 Command**
- 极简单的操作(如 `model.GetData()`
- 纯 UI 逻辑(如切换界面状态)
## 核心收获
通过这次重构,我们学到了:
| 概念 | 解释 |
|----------------|------------------------------|
| **Command 模式** | 将请求封装成对象 |
| **职责分离** | Controller 负责转发Command 负责执行 |
| **逻辑复用** | 同一命令可被多处调用 |
| **可测试性** | 命令可独立测试 |
| **单一职责** | 每个 Command 只做一件事 |
## 对比三个阶段
### 阶段 1基础实现
```csharp
private int _count;
AddButton.Pressed += () =>
{
_count++;
UpdateView();
};
```
**问题**状态、逻辑、UI 混在一起
### 阶段 2引入 Model
```csharp
private ICounterModel _counterModel;
AddButton.Pressed += () =>
{
_counterModel.Increment();
};
```
**改进**:状态抽离到 Model但交互逻辑仍在 Controller
### 阶段 3引入 Command
```csharp
AddButton.Pressed += () =>
{
this.SendCommand(new IncreaseCountCommand());
};
```
**完善**Controller 不再关心"如何",只负责"转发"
## 下一步
现在我们的架构已经很清晰了:
```
View → Controller → Command → Model → Event → View
```
但还有两个问题:
1. **业务规则**:如何实现"计数不能超过 20"
2. **状态响应**:如何实现"计数超过 10 时触发某个逻辑"
这些问题需要 **Utility****System** 来解决。
在下一章中,我们将:
- 引入 **Utility** 处理业务规则
- 引入 **System** 响应状态变化
- 完成完整的架构设计
👉 [第 6 章Utility 与 System](./06-utility-system.md)
---
::: details 本章检查清单
- [ ] IncreaseCountCommand 已创建
- [ ] DecreaseCountCommand 已创建
- [ ] App.cs 使用 SendCommand 替换了直接调用
- [ ] 运行游戏,功能正常
- [ ] 理解了 Command 的职责和优势
- [ ] 理解了 Controller、Command、Model 的职责边界
:::
::: tip 思考题
1. 如果需要实现"撤销"功能,应该如何修改 Command
2. 异步命令(如网络请求)应该如何实现?
3. 多个 Command 需要按顺序执行时,应该怎么做?
这些高级用法可以在后续深入学习!
:::

View File

@ -0,0 +1,712 @@
---
prev:
text: '命令系统优化'
link: './05-command-system'
next:
text: '总结与最佳实践'
link: './07-summary'
---
# 第 6 章Utility 与 System
本章将引入架构的最后两个核心概念:**Utility工具类** 和 **System系统**,完成我们的架构设计。
## 新需求
### 需求 1计数上限
计数器不能超过 **20**
### 需求 2阈值检查
- 当 Count > 10 时,输出提示信息
- 当 Count < -10 输出提示信息
## 方案一:在 Command 中实现(❌ 不推荐)
### 错误示范
```csharp
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<ICounterModel>()!;
// ❌ 在 Command 里写业务规则
if (model.Count >= 20)
{
return; // 超过上限,不执行
}
model.Increment();
}
}
```
### 问题
1. **规则写死在 Command 里**
- 如果别的地方也要用"最大 20"的限制怎么办?
- 规则变更(如改成 100需要修改业务代码
2. **无法单独测试规则**
- 只能通过 Command 测试,无法单独测试规则逻辑
3. **违反单一职责原则**
- Command 应该只负责"执行操作"
- 不应该负责"业务规则验证"
## 引入 Utility
### Utility 是什么?
**Utility工具类** 提供可复用的无状态逻辑,负责:
✅ 纯函数式的计算和验证
✅ 数据转换和格式化
✅ 业务规则的封装
Utility **不应该**
❌ 持有状态
❌ 依赖场景
❌ 直接修改 Model
### 特点
- **无状态**:只提供计算方法
- **可复用**:任何层都可以调用
- **可测试**:纯函数,易于测试
### 1. 定义 Utility 接口
`scripts/utility/` 创建 `ICounterUtility.cs`
```csharp
using GFramework.Core.Abstractions.utility;
namespace MyGFrameworkGame.scripts.utility;
/// <summary>
/// 计数器工具接口
/// </summary>
public interface ICounterUtility : IContextUtility
{
/// <summary>
/// 判断当前值是否可以增加
/// </summary>
bool CanIncrease(int current);
/// <summary>
/// 将值限制在有效范围内
/// </summary>
int Clamp(int value);
}
```
### 2. 实现 Utility 类
`scripts/utility/` 创建 `CounterUtility.cs`
```csharp
using System;
using GFramework.Core.utility;
namespace MyGFrameworkGame.scripts.utility;
/// <summary>
/// 计数器工具实现
/// </summary>
public class CounterUtility : AbstractContextUtility, ICounterUtility
{
private readonly int _maxCount;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="maxCount">最大值(默认 20</param>
public CounterUtility(int maxCount = 20)
{
_maxCount = maxCount;
}
/// <summary>
/// 初始化(可选)
/// </summary>
protected override void OnInit()
{
// 也可以通过上下文获取配置
}
/// <summary>
/// 判断是否可以继续增加
/// </summary>
public bool CanIncrease(int current)
{
return current < _maxCount;
}
/// <summary>
/// 将值限制在有效范围内
/// </summary>
public int Clamp(int value)
{
return Math.Clamp(value, 0, _maxCount);
}
}
```
::: tip 为什么用构造函数传参?
这里使用构造函数传递 `maxCount` 是为了灵活性。你也可以:
- 从配置文件读取
- 通过 `architecture.Context.GetUtility()` 传入
- 硬编码在类内部
选择取决于项目需求。
:::
### 3. 注册 Utility
编辑 `scripts/module/UtilityModule.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
using MyGFrameworkGame.scripts.utility;
namespace MyGFrameworkGame.scripts.module;
/// <summary>
/// 工具模块,负责注册所有的工具类
/// </summary>
public class UtilityModule : AbstractModule
{
public override void Install(IArchitecture architecture)
{
// 注册 CounterUtility最大值设为 20
architecture.RegisterUtility<ICounterUtility>(new CounterUtility(maxCount: 20));
}
}
```
### 4. 在 Command 中使用 Utility
编辑 `IncreaseCountCommand.cs`
```csharp
using GFramework.Core.command;
using GFramework.Core.extensions;
using MyGFrameworkGame.scripts.model;
using MyGFrameworkGame.scripts.utility;
namespace MyGFrameworkGame.scripts.command;
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<ICounterModel>()!;
var utility = this.GetUtility<ICounterUtility>()!;
// ✅ 使用 Utility 检查规则
if (!utility.CanIncrease(model.Count))
{
return; // 超过上限,不执行
}
model.Increment();
}
}
```
### 运行游戏
现在点击增加按钮,计数器最多只能到 **20**
## Utility 的优势
### 1. 规则复用
假设多个地方需要检查上限:
```csharp
// Command 中
if (!utility.CanIncrease(model.Count)) return;
// System 中
if (!utility.CanIncrease(value)) { /* ... */ }
// Controller 中
if (utility.CanIncrease(count)) { /* ... */ }
```
规则只写一次,到处可用!
### 2. 易于修改
需要改上限为 100
```csharp
// 只需修改注册时的参数
architecture.RegisterUtility<ICounterUtility>(new CounterUtility(maxCount: 100));
```
不需要改任何业务代码!
### 3. 易于测试
```csharp
[Test]
public void CanIncrease_WhenAtMax_ReturnsFalse()
{
var utility = new CounterUtility(maxCount: 20);
Assert.IsFalse(utility.CanIncrease(20));
Assert.IsTrue(utility.CanIncrease(19));
}
```
纯函数,测试简单!
## 引入 System
### System 是什么?
现在有个新需求:
- 当 Count > 10 时,输出提示信息 "Count 超过 10"
- 当 Count < -10 输出提示信息 "Count 小于 -10"
这些逻辑应该写在哪里?
**❌ 不应该在 Command 里**
```csharp
protected override void OnExecute()
{
model.Increment();
// ❌ Command 不应该关心这些"连锁反应"
if (model.Count > 10) { /* ... */ }
if (model.Count < -10) { /* ... */ }
}
```
**为什么?**
- Command 只应该关心"行为"
- 不应该关心"系统状态带来的连锁反应"
**✅ 应该在 System 里**
**System系统** 负责响应状态变化,执行系统级逻辑。
### System 的职责
✅ 监听状态变化
✅ 执行规则检查
✅ 触发系统级反应
✅ 协调多个 Model 的交互
System **不应该**
❌ 直接修改 Model应通过 Command
❌ 包含 UI 逻辑
### 1. 定义 System 接口
`scripts/system/` 创建 `ICounterThresholdSystem.cs`
```csharp
namespace MyGFrameworkGame.scripts.system;
/// <summary>
/// 计数器阈值检查系统接口
/// </summary>
public interface ICounterThresholdSystem
{
/// <summary>
/// 检查当前计数值是否超过阈值
/// </summary>
void CheckThreshold(int count);
}
```
### 2. 实现 System 类
`scripts/system/` 创建 `CounterThresholdSystem.cs`
```csharp
using GFramework.Core.extensions;
using GFramework.Core.system;
using Godot;
using MyGFrameworkGame.scripts.model;
namespace MyGFrameworkGame.scripts.system;
/// <summary>
/// 计数器阈值检查系统
/// </summary>
public class CounterThresholdSystem : AbstractSystem, ICounterThresholdSystem
{
/// <summary>
/// 初始化时注册事件监听
/// </summary>
protected override void OnInit()
{
// 监听计数变化事件
this.RegisterEvent<CounterModel.ChangedCountEvent>(e =>
{
CheckThreshold(e.Count);
});
}
/// <summary>
/// 检查阈值
/// </summary>
public void CheckThreshold(int count)
{
if (count > 10)
{
GD.Print("Count 超过 10");
}
if (count < -10)
{
GD.Print("Count 小于 -10");
}
}
}
```
::: tip System 的生命周期
`AbstractSystem` 会在注册时自动调用 `OnInit()`,所以事件监听会在系统初始化时完成。
:::
### 3. 注册 System
编辑 `scripts/module/SystemModule.cs`
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.architecture;
using MyGFrameworkGame.scripts.system;
namespace MyGFrameworkGame.scripts.module;
/// <summary>
/// 系统模块,负责注册所有的系统逻辑
/// </summary>
public class SystemModule : AbstractModule
{
public override void Install(IArchitecture architecture)
{
// 注册阈值检查系统
architecture.RegisterSystem<ICounterThresholdSystem>(new CounterThresholdSystem());
}
}
```
### 4. 运行游戏
启动游戏,当计数超过 10 或小于 -10 时,输出面板会显示提示信息:
![阈值提示](../assets/basic-tutorial/image-20260211234946253.png)
## System 的优势
### 1. 关注点分离
**Command 负责"做什么"**
```csharp
model.Increment(); // 执行操作
```
**System 负责"响应状态"**
```csharp
if (count > 10) { /* 触发反应 */ }
```
### 2. 逻辑集中
假设将来需求变更:
- 达到 5 播放音效
- 达到 15 触发警告
- 达到 20 锁定按钮
**❌ 如果写在 Command**
```csharp
// 在 N 个 Command 里重复相同逻辑
if (count == 5) PlaySound();
if (count == 15) ShowWarning();
if (count == 20) LockButton();
```
**✅ 集中在 System**
```csharp
public void CheckThreshold(int count)
{
if (count == 5) PlaySound();
if (count == 15) ShowWarning();
if (count == 20) LockButton();
}
```
只需修改一处!
### 3. 支持复杂规则
System 可以协调多个 Model
```csharp
protected override void OnInit()
{
// 同时监听多个事件
this.RegisterEvent<CounterChangedEvent>(e => { /* ... */ });
this.RegisterEvent<ScoreChangedEvent>(e => { /* ... */ });
// 当两个条件同时满足时触发
if (counter > 10 && score > 100)
{
UnlockAchievement();
}
}
```
## 完整架构总览
现在我们的架构已经完整:
```
┌─────────────┐
│ View │ Godot UI 节点
└──────┬──────┘
┌──────▼──────┐
│ Controller │ 将用户操作转为命令
└──────┬──────┘
┌──────▼──────┐
│ Command │ 封装业务逻辑
└──────┬──────┘
┌──────▼──────┐
│ Model │ 存储状态,发送事件
└──────┬──────┘
├─────────┐
│ │
┌──────▼──────┐ │
│ Event │ │
└──────┬──────┘ │
│ │
┌──────▼──────┐ │
│ System │ │ 响应状态变化
└──────┬──────┘ │
│ │
┌──────▼──────┐ │
│ Utility │◄─┘ 提供业务规则
└─────────────┘
```
### 数据流
```
用户点击按钮
Controller: SendCommand(IncreaseCommand)
Command: GetUtility().CanIncrease() → 检查规则
Command: GetModel().Increment() → 修改状态
Model: SendEvent(ChangedCountEvent) → 发送事件
├→ Controller: RegisterEvent → 更新 UI
└→ System: RegisterEvent → 检查阈值
```
## 各层职责总结
| 层级 | 职责 | 示例 |
|----------------|------------|-------------------|
| **View** | 呈现 UI接收输入 | Label、Button |
| **Controller** | 转发用户意图 | 将点击转为命令 |
| **Command** | 执行业务操作 | 增加计数 |
| **Model** | 存储状态,发送事件 | 计数器的值 |
| **Event** | 通知状态变化 | ChangedCountEvent |
| **System** | 响应状态,执行规则 | 阈值检查 |
| **Utility** | 提供业务规则 | 上限验证 |
## 设计原则
### 单向数据流
```
Action → Command → Model → Event → View/System
```
- 数据总是单向流动
- 没有循环依赖
- 易于理解和调试
### 关注点分离
```
Controller → "做什么"
Command → "怎么做"
Model → "状态是什么"
System → "状态变化后做什么"
Utility → "规则是什么"
```
### 依赖倒置
```
都依赖接口,不依赖具体实现
ICounterModel ← CounterModel
ICounterUtility ← CounterUtility
ICounterThresholdSystem ← CounterThresholdSystem
```
## 何时使用 Utility vs System
### Utility
**无状态的计算**
```csharp
utility.CanIncrease(count)
utility.Clamp(value)
utility.ValidateEmail(email)
```
**业务规则验证**
```csharp
if (!utility.CanPurchase(player, item)) return;
```
### System
**状态驱动的逻辑**
```csharp
if (count > 10) { /* 触发某些事情 */ }
```
**协调多个 Model**
```csharp
if (player.Level > 10 && achievement.Count > 5) { /* ... */ }
```
**系统级反应**
```csharp
PlaySound();
ShowNotification();
UpdateAchievement();
```
## 核心收获
通过本章,我们学到了:
| 概念 | 解释 |
|-------------|---------------|
| **Utility** | 无状态的业务规则和计算 |
| **System** | 响应状态变化,执行系统逻辑 |
| **关注点分离** | 每一层专注自己的职责 |
| **单向数据流** | 数据流向清晰可控 |
| **事件驱动** | 通过事件解耦组件 |
## 完整代码回顾
### 项目结构
```
MyGFrameworkGame/
├── scripts/
│ ├── architecture/
│ │ └── GameArchitecture.cs
│ ├── module/
│ │ ├── ModelModule.cs
│ │ ├── SystemModule.cs
│ │ └── UtilityModule.cs
│ ├── model/
│ │ ├── ICounterModel.cs
│ │ └── CounterModel.cs
│ ├── command/
│ │ ├── IncreaseCountCommand.cs
│ │ └── DecreaseCountCommand.cs
│ ├── utility/
│ │ ├── ICounterUtility.cs
│ │ └── CounterUtility.cs
│ ├── system/
│ │ ├── ICounterThresholdSystem.cs
│ │ └── CounterThresholdSystem.cs
│ └── app/
│ └── App.cs
├── global/
│ └── GameEntryPoint.cs
└── scenes/
└── App.tscn
```
### 架构层次
```
GameEntryPoint (入口)
GameArchitecture (架构)
Module (模块)
├── ModelModule → CounterModel
├── SystemModule → CounterThresholdSystem
└── UtilityModule → CounterUtility
```
## 下一步
恭喜!你已经完成了基础教程的核心内容,掌握了 GFramework 的完整架构设计。
在最后一章中,我们将:
- 回顾整个架构设计
- 总结最佳实践
- 解答常见问题
- 指引下一步学习方向
👉 [第 7 章:总结与最佳实践](./07-summary.md)
---
::: details 本章检查清单
- [ ] ICounterUtility 和 CounterUtility 已创建
- [ ] Utility 已注册到 UtilityModule
- [ ] IncreaseCountCommand 使用 Utility 检查规则
- [ ] ICounterThresholdSystem 和 CounterThresholdSystem 已创建
- [ ] System 已注册到 SystemModule
- [ ] 运行游戏,计数最大为 20
- [ ] 超过阈值时能看到提示信息
- [ ] 理解了 Utility 和 System 的职责区别
:::
::: tip 思考题
1. 如果需要在多个 System 之间通信,应该怎么做?
2. Utility 和 System 哪个应该先注册?为什么?
3. 如何实现"计数为偶数时播放音效"的功能?
这些问题会在进阶教程中探讨!
:::

View File

@ -0,0 +1,751 @@
---
prev:
text: 'Utility 与 System'
link: './06-utility-system'
---
# 第 7 章:总结与最佳实践
恭喜你完成了 GFramework 基础教程!本章将回顾整个架构设计,总结最佳实践,并解答常见问题。
## 架构演进回顾
### 阶段 1基础实现
**代码**
```csharp
private int _count;
AddButton.Pressed += () =>
{
_count++;
UpdateView();
};
```
**问题**
- ❌ 状态、逻辑、UI 混在一起
- ❌ 无法复用
- ❌ 难以测试
- ❌ 扩展困难
---
### 阶段 2引入 Model + 事件
**代码**
```csharp
private ICounterModel _counterModel;
AddButton.Pressed += () =>
{
_counterModel.Increment();
};
this.RegisterEvent<ChangedCountEvent>(e =>
{
UpdateView(e.Count);
});
```
**改进**
- ✅ 状态抽离到 Model
- ✅ 通过事件更新 UI
- ✅ Model 可复用、可测试
**剩余问题**
- ⚠️ 交互逻辑仍在 Controller
---
### 阶段 3引入 Command
**代码**
```csharp
AddButton.Pressed += () =>
{
this.SendCommand(new IncreaseCountCommand());
};
```
**改进**
- ✅ Controller 不关心"如何",只负责"转发"
- ✅ 逻辑封装在 Command 中
- ✅ 命令可复用、可测试
**剩余问题**
- ⚠️ 业务规则写在 Command 里
---
### 阶段 4引入 Utility + System
**代码**
```csharp
// Command 使用 Utility 检查规则
if (!utility.CanIncrease(model.Count)) return;
model.Increment();
// System 响应状态变化
this.RegisterEvent<ChangedCountEvent>(e =>
{
CheckThreshold(e.Count);
});
```
**最终架构**
- ✅ 完全的关注点分离
- ✅ 单向数据流
- ✅ 各层可测试、可复用
- ✅ 易于扩展和维护
## 完整架构图
```
┌──────────────────────────────────────────────┐
│ View (UI) │
│ Godot Nodes (Label, Button) │
└────────────┬─────────────────────────────────┘
│ 用户输入
┌────────────▼─────────────────────────────────┐
│ Controller │
│ - 接收用户输入 │
│ - 转发命令 │
│ - 监听事件更新 UI │
└────────────┬─────────────────────────────────┘
│ SendCommand
┌────────────▼─────────────────────────────────┐
│ Command │
│ - 获取 Utility 检查规则 │
│ - 调用 Model 修改状态 │
└────────────┬─────────────────────────────────┘
│ GetModel / GetUtility
┌─────────┼─────────┐
│ │
┌──▼──────────┐ ┌──────▼───────┐
│ Utility │ │ Model │
│ - 业务规则 │ │ - 存储状态 │
│ - 纯计算 │ │ - 发送事件 │
└─────────────┘ └──────┬───────┘
│ SendEvent
┌──────┴───────┐
│ │
┌───────▼─────┐ ┌──────▼──────┐
│ Controller │ │ System │
│ 更新 UI │ │ 响应状态 │
└─────────────┘ └─────────────┘
```
## 各层职责速查表
| 层级 | 职责 | 可以做 | 不能做 |
|----------------|-------|---------------------|-------------------------|
| **View** | UI 展示 | 渲染节点、接收输入 | 包含业务逻辑 |
| **Controller** | 协调层 | 转发命令、监听事件、更新 UI | 直接修改 Model |
| **Command** | 业务操作 | 调用 Model、使用 Utility | 持有状态、直接更新 UI |
| **Model** | 数据状态 | 存储数据、发送事件 | 知道 View、调用 Controller |
| **Utility** | 业务规则 | 无状态计算、验证 | 持有状态、依赖场景 |
| **System** | 系统逻辑 | 监听事件、协调 Model | 直接修改 Model应通过 Command |
## 设计原则
### 1. 单一职责原则SRP
每个类只做一件事:
```csharp
// ✅ Model 只负责状态
public class CounterModel : AbstractModel
{
public int Count { get; private set; }
public void Increment() { /* ... */ }
}
// ✅ Command 只负责操作
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute() { /* ... */ }
}
```
### 2. 依赖倒置原则DIP
依赖抽象,不依赖具体实现:
```csharp
// ✅ 依赖接口
private ICounterModel _counterModel;
// ❌ 依赖具体类
private CounterModel _counterModel;
```
### 3. 开闭原则OCP
对扩展开放,对修改封闭:
```csharp
// ✅ 新增功能不修改现有代码
architecture.RegisterSystem(new NewFeatureSystem());
// ❌ 修改现有类添加功能
public class CounterModel
{
// 每次新增功能都修改这个类
}
```
### 4. 事件驱动原则
通过事件解耦组件:
```csharp
// ✅ Model 不知道谁在监听
this.SendEvent(new ChangedCountEvent());
// ❌ Model 直接调用
_view.UpdateView();
```
### 5. 单向数据流
数据总是单向流动:
```
Action → Command → Model → Event → View/System
```
## 最佳实践
### 1. 接口设计
**✅ 推荐**
```csharp
public interface ICounterModel : IModel
{
int Count { get; }
void Increment();
}
```
**❌ 不推荐**
```csharp
public class CounterModel // 没有接口
{
public int Count { get; set; } // 可被外部直接修改
}
```
### 2. 事件命名
**✅ 推荐**
```csharp
public sealed record ChangedCountEvent // 描述事件
{
public int Count { get; init; }
}
```
**❌ 不推荐**
```csharp
public class CountEvent { } // 不清晰
public class Data { } // 太泛化
```
### 3. Command 职责
**✅ 推荐**
```csharp
protected override void OnExecute()
{
// 1. 获取依赖
var model = this.GetModel<ICounterModel>();
var utility = this.GetUtility<ICounterUtility>();
// 2. 检查规则
if (!utility.CanIncrease(model.Count)) return;
// 3. 执行操作
model.Increment();
}
```
**❌ 不推荐**
```csharp
protected override void OnExecute()
{
_count++; // 直接修改状态
UpdateUI(); // 直接更新 UI
PlaySound(); // 混入太多逻辑
}
```
### 4. Utility 设计
**✅ 推荐**
```csharp
public bool CanIncrease(int current)
{
return current < _maxCount; // 纯函数
}
```
**❌ 不推荐**
```csharp
private int _state; // 持有状态
public void Increment()
{
_state++; // 修改状态
}
```
### 5. System 使用
**✅ 推荐**
```csharp
protected override void OnInit()
{
// 监听事件
this.RegisterEvent<ChangedCountEvent>(e =>
{
CheckThreshold(e.Count);
});
}
```
**❌ 不推荐**
```csharp
public void UpdateCounter()
{
// 直接修改 Model
model.Count++; // 应该通过 Command
}
```
## 常见问题 FAQ
### Q1: Model 可以调用其他 Model 吗?
**❌ 不推荐**
```csharp
public class PlayerModel : AbstractModel
{
public void Attack()
{
// 直接调用其他 Model
this.GetModel<EnemyModel>().TakeDamage(10);
}
}
```
**✅ 推荐**:通过 Command 或 System 协调:
```csharp
public class AttackCommand : AbstractCommand
{
protected override void OnExecute()
{
var player = this.GetModel<IPlayerModel>();
var enemy = this.GetModel<IEnemyModel>();
enemy.TakeDamage(player.AttackPower);
}
}
```
---
### Q2: Command 可以嵌套调用吗?
**✅ 可以**
```csharp
protected override void OnExecute()
{
this.SendCommand(new SaveDataCommand());
this.SendCommand(new UpdateUICommand());
}
```
但要注意:
- 避免循环依赖
- 考虑使用 System 协调复杂流程
---
### Q3: 什么时候用 Utility什么时候用 System
| 场景 | 使用 |
|------------|---------|
| 无状态计算 | Utility |
| 业务规则验证 | Utility |
| 响应状态变化 | System |
| 协调多个 Model | System |
| 触发系统级反应 | System |
**示例**
```csharp
// Utility纯计算
utility.CanIncrease(count)
// System状态响应
if (count > 10) PlaySound();
```
---
### Q4: Controller 可以直接调用 Model 吗?
**部分场景可以**
```csharp
// ✅ 只读操作
var count = this.GetModel<ICounterModel>().Count;
// ❌ 修改操作(应通过 Command
this.GetModel<ICounterModel>().Increment();
```
**原则**
- 读取数据:可以直接调用
- 修改数据:应该通过 Command
---
### Q5: 如何处理异步操作?
使用 `AbstractAsyncCommand`
```csharp
public class SaveDataCommand : AbstractAsyncCommand
{
protected override async Task OnExecuteAsync()
{
var model = this.GetModel<ICounterModel>();
await SaveToFileAsync(model.Count);
}
}
```
---
### Q6: 如何在多个场景共享状态?
**Model 是全局的**
```csharp
// 场景 A
var count = this.GetModel<ICounterModel>().Count;
// 场景 B
var count = this.GetModel<ICounterModel>().Count;
// 两者是同一个 Model 实例
```
如果需要场景独立的状态,考虑:
- 为每个场景创建独立的 Model
- 使用场景参数传递数据
---
### Q7: 如何测试这些组件?
**Model 测试**
```csharp
[Test]
public void Increment_ShouldIncreaseCount()
{
var model = new CounterModel();
model.Increment();
Assert.AreEqual(1, model.Count);
}
```
**Utility 测试**
```csharp
[Test]
public void CanIncrease_WhenAtMax_ReturnsFalse()
{
var utility = new CounterUtility(maxCount: 20);
Assert.IsFalse(utility.CanIncrease(20));
}
```
**Command 测试**(需要 mock
```csharp
[Test]
public void ExecuteCommand_ShouldIncrementModel()
{
// 需要 mock IArchitecture
// 或使用集成测试
}
```
---
### Q8: 项目变大后如何组织代码?
**按功能模块划分**
```
scripts/
├── counter/
│ ├── model/
│ ├── command/
│ └── system/
├── player/
│ ├── model/
│ ├── command/
│ └── system/
└── inventory/
├── model/
├── command/
└── system/
```
**按层级划分**
```
scripts/
├── model/
│ ├── CounterModel.cs
│ ├── PlayerModel.cs
│ └── InventoryModel.cs
├── command/
│ ├── counter/
│ ├── player/
│ └── inventory/
└── system/
├── CounterSystem.cs
└── PlayerSystem.cs
```
选择适合团队的方式。
## 性能考虑
### 事件系统开销
**问题**:频繁发送事件会影响性能吗?
**答案**
- GFramework 的事件系统经过优化,开销很小
- 对于游戏逻辑级别的事件(如计数变化),完全没问题
- 如果是高频事件(如每帧更新),考虑批处理
**示例**
```csharp
// ❌ 高频事件(每帧发送)
public override void _Process(double delta)
{
this.SendEvent(new PositionChangedEvent());
}
// ✅ 批处理或降频
private float _eventTimer;
public override void _Process(double delta)
{
_eventTimer += (float)delta;
if (_eventTimer > 0.1f) // 每 100ms 发送一次
{
this.SendEvent(new PositionChangedEvent());
_eventTimer = 0;
}
}
```
### 依赖注入开销
**问题**`GetModel()` 会影响性能吗?
**答案**
- 第一次调用会查找,之后会缓存
- 建议在 `_Ready` 中获取并缓存
**示例**
```csharp
// ✅ 缓存引用
private ICounterModel _counterModel;
public override void _Ready()
{
_counterModel = this.GetModel<ICounterModel>();
}
public override void _Process(double delta)
{
var count = _counterModel.Count; // 使用缓存的引用
}
```
## 下一步学习
### 进阶主题
1. **高级命令模式**
- 异步命令
- 命令队列
- 撤销/重做
2. **复杂事件系统**
- 事件优先级
- 事件过滤
- 事件链
3. **高级 System**
- System 之间的通信
- System 生命周期管理
- System 优先级
4. **规则系统**
- 动态规则
- 规则链
- 规则引擎
5. **状态机**
- 使用 GFramework 实现状态机
- 分层状态机
- 状态转换规则
### 推荐资源
- **GFramework 文档**
- [Core 核心框架](../../core/)
- [Game 游戏模块](../../game/)
- [Godot 集成](../../godot/)
- [源码生成器](../../source-generators/)
- **设计模式**
- 命令模式Command Pattern
- 观察者模式Observer Pattern
- 依赖注入Dependency Injection
- **架构设计**
- Clean Architecture
- MVC / MVVM
- Event-Driven Architecture
## 项目示例
查看完整的示例项目:
- [基础教程(本教程)](https://github.com/GeWuYou/GFramework/tree/main/examples/Counter)
- [Godot 集成示例](https://github.com/GeWuYou/GFramework/tree/main/examples/GodotIntegration)
- [高级模式示例](https://github.com/GeWuYou/GFramework/tree/main/examples/AdvancedPatterns)
## 总结
通过本教程,你学到了:
### 核心概念
- ✅ Model存储状态发送事件
- ✅ Command封装业务逻辑
- ✅ Controller协调用户输入
- ✅ Utility提供业务规则
- ✅ System响应状态变化
### 设计原则
- ✅ 单一职责
- ✅ 依赖倒置
- ✅ 事件驱动
- ✅ 单向数据流
- ✅ 关注点分离
### 架构优势
- ✅ 可测试
- ✅ 可复用
- ✅ 可扩展
- ✅ 易维护
- ✅ 解耦合
## 结语
恭喜你完成了 GFramework 基础教程!🎉
你现在已经掌握了使用 GFramework 构建清晰、可维护的游戏架构的核心知识。
记住:
- **架构是为了解决问题,不是为了炫技**
- **从简单开始,逐步优化**
- **理解原则比记住代码更重要**
继续探索,享受编程的乐趣!
---
::: tip 反馈与支持
- 遇到问题?查看 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues)
- 有建议?欢迎提交 PR 或 Issue
- 加入社区,与其他开发者交流
:::
::: details 完整检查清单
**环境与项目**
- [ ] .NET SDK 和 Godot 已安装
- [ ] GFramework NuGet 包已引入
- [ ] 项目架构已搭建
**核心组件**
- [ ] Model 已创建并注册
- [ ] Command 已创建
- [ ] Utility 已创建并注册
- [ ] System 已创建并注册
- [ ] Controller 实现了 IController
**功能验证**
- [ ] 计数器功能正常
- [ ] 事件系统工作正常
- [ ] 上限限制生效
- [ ] 阈值检查触发
**理解验证**
- [ ] 理解了各层职责
- [ ] 理解了事件驱动架构
- [ ] 理解了单向数据流
- [ ] 理解了设计原则
:::
👏 再次恭喜你完成教程!期待看到你用 GFramework 创造出精彩的项目!

View File

@ -0,0 +1,192 @@
# 基础教程:从零开始使用 GFramework
欢迎来到 GFramework 的基础教程!本教程将带你从零开始,创建一个完整的计数器应用,逐步掌握 GFramework 的核心概念和最佳实践。
## 📚 教程概述
通过本教程,你将:
- 搭建完整的开发环境
- 创建一个基于 Godot 的项目并集成 GFramework
- 从基础实现到架构优化,体验完整的重构过程
- 理解 Model、Command、System、Utility 等核心概念
- 掌握事件驱动架构的设计思想
## 🎯 学习目标
完成本教程后,你将能够:
- ✅ 搭建 GFramework 开发环境
- ✅ 使用 GFramework 构建清晰的应用架构
- ✅ 理解并应用 MVC 架构模式
- ✅ 使用命令系统解耦业务逻辑
- ✅ 通过事件系统实现组件通信
- ✅ 合理使用 Utility 和 System 层
## ⏱️ 预计学习时间
- **快速浏览**30 分钟
- **完整实践**2-3 小时
- **深入理解**4-6 小时
## 📋 前置知识要求
### 必需
- C# 基础语法
- 面向对象编程概念
- 基本的命令行操作
### 推荐
- Godot 引擎基础(了解节点系统即可)
- MVC 架构基础概念
- 依赖注入的基本理解
## 📖 教程章节
### [1. 环境准备](./01-environment.md)
搭建 GFramework 开发环境,包括 .NET SDK 和 Godot 引擎的安装配置。
**关键内容**
- 系统要求检查
- .NET SDK 安装
- Godot 引擎配置
- 环境验证
**预计时间**15-30 分钟
---
### [2. 项目创建与初始化](./02-project-setup.md)
创建 Godot 项目,引入 GFramework并搭建基础架构。
**关键内容**
- 创建 Godot 项目
- 引入 GFramework NuGet 包
- 定义游戏架构类
- 创建三大模块Model、System、Utility
- 配置游戏入口点
**预计时间**30-45 分钟
---
### [3. 基础计数器实现](./03-counter-basic.md)
实现一个简单的计数器功能,了解传统实现方式的问题。
**关键内容**
- 搭建 UI 场景
- 实现基础计数器功能
- 分析代码中的耦合问题
- 理解为什么需要架构优化
**预计时间**20-30 分钟
---
### [4. 引入 Model 重构](./04-model-refactor.md)
将数据状态抽离到 Model 层,引入事件系统实现数据驱动。
**关键内容**
- 创建 Model 接口和实现
- 注册 Model 到架构
- 使用事件系统更新 UI
- 对比重构前后的改进
**预计时间**30-40 分钟
---
### [5. 命令系统优化](./05-command-system.md)
使用 Command 模式解耦交互逻辑,让 Controller 更加清晰。
**关键内容**
- 创建增减计数命令
- 重构 Controller 使用命令
- 理解 Command 的职责边界
- 对比直接调用的优缺点
**预计时间**30-40 分钟
---
### [6. Utility 与 System](./06-utility-system.md)
引入 Utility 处理业务规则,使用 System 响应状态变化。
**关键内容**
- 实现计数限制 Utility
- 创建阈值检查 System
- 理解各层的职责分工
- 完整架构总结
**预计时间**40-50 分钟
---
### [7. 总结与最佳实践](./07-summary.md)
回顾整个架构设计,总结最佳实践和常见问题。
**关键内容**
- 各层职责对照表
- 设计原则总结
- 常见问题 FAQ
- 下一步学习建议
**预计时间**15-20 分钟
---
## 🚀 快速开始
如果你已经熟悉基础概念,可以:
1. **只想看最终效果**:直接跳到 [第 6 章](./06-utility-system.md) 查看完整架构
2. **已有 Godot 项目**:从 [第 2 章](./02-project-setup.md) 开始集成 GFramework
3. **想快速上手**:按顺序完成 [第 2-4 章](./02-project-setup.md),掌握核心用法
## 💡 学习建议
- **边学边做**:每个章节都包含完整代码,建议跟着实践
- **理解原理**:不要只复制代码,思考每一步的设计意图
- **对比差异**:重构前后的代码对比是理解架构价值的关键
- **举一反三**:将学到的模式应用到自己的项目中
## 🔗 相关资源
- [GFramework 文档首页](../../)
- [Core 核心框架](../../core/)
- [Godot 集成](../../godot/)
- [源码生成器](../../source-generators/)
## ❓ 遇到问题?
- 查看 [常见问题 FAQ](./07-summary.md#常见问题-faq)
- 访问 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues)
- 加入社区讨论
---
::: tip 提示
本教程采用循序渐进的方式,每一步都基于前一步的代码。建议按顺序学习,这样能更好地理解架构演进的过程。
:::
::: warning 注意
教程中的代码示例使用 Godot 4.5.1+ 和 .NET 6.0+。如果你使用不同版本,可能需要适当调整。
:::
让我们开始吧!👉 [第 1 章:环境准备](./01-environment.md)

File diff suppressed because it is too large Load Diff