diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211210657387.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211210657387.png new file mode 100644 index 0000000..17e8161 Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211210657387.png differ diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211211756993.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211211756993.png new file mode 100644 index 0000000..9bb1c7a Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211211756993.png differ diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211214905664.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211214905664.png new file mode 100644 index 0000000..f7830d7 Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211214905664.png differ diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211215031192.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211215031192.png new file mode 100644 index 0000000..a43f350 Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211215031192.png differ diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211215626940.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211215626940.png new file mode 100644 index 0000000..1c0a7d3 Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211215626940.png differ diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211222402064.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211222402064.png new file mode 100644 index 0000000..b901fc2 Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211222402064.png differ diff --git a/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211223654625.png b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211223654625.png new file mode 100644 index 0000000..fa79483 Binary files /dev/null and b/docs/zh-CN/tutorials/assets/basic-tutorial/image-20260211223654625.png differ diff --git a/docs/zh-CN/tutorials/basic-tutorial.md b/docs/zh-CN/tutorials/basic-tutorial.md index b2d10e3..44aaf43 100644 --- a/docs/zh-CN/tutorials/basic-tutorial.md +++ b/docs/zh-CN/tutorials/basic-tutorial.md @@ -1,12 +1,12 @@ # 基础教程 -这是一个完整的从零开始的教程,将带领你创建一个使用 GFramework 的简单游戏项目。 +这是一个完整的从零开始的教程,将带领你创建一个使用 GFramework 的简单的计数器应用。 -## 📋 目录 +## 目录 - [环境准备](#环境准备) - [项目创建](#项目创建) -- [架构设计](#架构设计) +- [项目初始化](#项目初始化) - [功能实现](#功能实现) - [测试验证](#测试验证) @@ -33,7 +33,7 @@ dotnet --version ### 安装 Godot 1. 访问 [Godot 官网](https://godotengine.org/download) -2. 下载 Godot 4.5.1 +2. 下载 Godot (Mono版) 3. 解压到合适的位置并启动 4. 在编辑器设置中确认 .NET 支持 @@ -59,9 +59,14 @@ dotnet build ### 1. 创建新的 Godot 项目 1. 打开 Godot 编辑器 + 2. 点击 "新建项目" + 3. 创建项目文件夹,命名为 "MyGFrameworkGame" -4. 选择 C# 作为脚本语言 + +4. 创建项目后初始化C#项目 + + ![image-20260211210657387](assets/basic-tutorial/image-20260211210657387.png) ### 2. 配置项目结构 @@ -69,368 +74,1290 @@ dotnet build ``` MyGFrameworkGame/ -├── src/ -│ ├── Game/ # 游戏逻辑 -│ │ ├── Models/ # 数据模型 -│ │ ├── Systems/ # 业务系统 -│ │ ├── Controllers/ # 控制器 -│ │ └── Utilities/ # 工具类 -│ └── Game.Core/ # 核心游戏组件 +├── sripts/ # 脚本 +├── scenes/ # 场景 ├── assets/ # 游戏资源 -│ ├── scenes/ -│ ├── textures/ -│ ├── audio/ -│ └── ui/ -└── project.godot # Godot 项目文件 +├── global/ # 全局类 +└── project.godot ``` -### 3. 配置项目文件 +当然,你也可以选择你喜欢的代码组织方式 -创建 `src/Game/Game.csproj`: +### 3. 引入GFramework -```xml - +一般我们有两种引入方式 - - net6.0 - MyGFrameworkGame - enable - +第一种是使用命令的方式引入 - - - +```sh +# 核心能力(推荐最小起步) +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 集成(仅 Godot 项目需要) +dotnet add package GeWuYou.GFramework.Godot + +# 源码生成器(可选,但推荐) +dotnet add package GeWuYou.GFramework.SourceGenerators ``` -创建 `src/Game.Core/Game.Core.csproj`: +当然这样包太多了,太费劲了,因此可以通过下方的命令直接将Core和Game模块都引入到项目中,再通过另外两个引入godot支持和源代码生成器 -```xml - - - - net6.0 - MyGFrameworkGame.Core - enable - - - - - - - - - - +```sh +# Core+Game +dotnet add package GeWuYou.GFramework +# Godot +dotnet add package GeWuYou.GFramework.Godot +# 源码生成器 +dotnet add package GeWuYou.GFramework.SourceGenerators ``` -## 架构设计 +第二种方法可以通过ide(比如VS和Rider) + +它们都提供NuGet管理,允许你基于图形界面来搜索并下载相关的NuGet包 + +![image-20260211211756993](assets/basic-tutorial/image-20260211211756993.png) + +## 项目初始化 ### 1. 定义游戏架构 -创建 `src/Game.Core/Architecture/GameArchitecture.cs`: +创建 `scripts/architecture/GameArchitecture.cs`: ```csharp -using GFramework.Core.architecture; -using MyGFrameworkGame.Core.Models; -using MyGFrameworkGame.Core.Systems; +using GFramework.Godot.architecture; -namespace MyGFrameworkGame.Core.Architecture +namespace MyGFrameworkGame.scripts.architecture; + +public class GameArchitecture : AbstractArchitecture { - public class GameArchitecture : AbstractArchitecture + protected override void InstallModules() { - protected override void Init() - { - // 注册游戏模型 - RegisterModel(new PlayerModel()); - RegisterModel(new GameModel()); - - // 注册游戏系统 - RegisterSystem(new PlayerControllerSystem()); - RegisterSystem(new CollisionSystem()); - - // 注册工具类 - RegisterUtility(new StorageUtility()); - } + } } ``` -### 2. 创建核心模型 +游戏架构类提供一个InstallModules方法用于注册模块,模块可以看作是一个个相关的Model、System、Utility的集合,按照开发者的意愿进行组织。 -创建 `src/Game.Core/Models/PlayerModel.cs`: +这里以一个示例代码进行说明 + +```C# +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.Abstractions.environment; +using GFramework.Godot.architecture; +using GFrameworkGodotTemplate.scripts.module; + +namespace GFrameworkGodotTemplate.scripts.core; + +/// +/// 游戏架构类,负责安装和管理游戏所需的各种模块 +/// 继承自AbstractArchitecture,用于构建游戏的整体架构体系 +/// +public sealed class GameArchitecture(IArchitectureConfiguration configuration, IEnvironment environment) + : AbstractArchitecture(configuration, environment) +{ + public IArchitectureConfiguration Configuration { get; set; } = configuration; + + /// + /// 安装游戏所需的各个功能模块 + /// 该方法在架构初始化时被调用,用于注册系统、模型和工具模块 + /// + protected override void InstallModules() + { + // 安装数据模型相关的Godot模块 + InstallModule(new ModelModule()); + // 安装系统相关的Godot模块 + InstallModule(new SystemModule()); + // 安装工具类相关的Godot模块 + InstallModule(new UtilityModule()); + // 安装状态相关的Godot模块 + InstallModule(new StateModule()); + } +} +``` + +从这里可以看到我们把 Model、 System、 Utility 进行了拆分,这是一种方式,使用这种方式的前提是,项目依赖不复杂,如果项目依赖复杂可能就得拆分多个Module来控制初始化顺序 + +### 2. 创建模型模块 + +创建 `scripts/module/ModelModule.cs`: ```csharp -using GFramework.Core.model; +using GFramework.Core.Abstractions.architecture; +using GFramework.Game.architecture; -namespace MyGFrameworkGame.Core.Models +namespace MyGFrameworkGame.scripts.module; + +public class ModelModule: AbstractModule { - public class PlayerModel : AbstractModel + public override void Install(IArchitecture architecture) { - public BindableProperty Health { get; } = new(100); - public BindableProperty Score { get; } = new(0); - public BindableProperty IsAlive { get; } = new(true); - protected override void OnInit() - { - // 监听生命值变化 - Health.Register(health => { - if (health <= 0) + } +} +``` + +这个Install(IArchitecture architecture)方法用于注册所需要的类即各种model + +> Model在框架中表示游戏的状态和数据。模型是简单的数据容器,它们公开描述游戏实体当前状态的属性。 + +这里以一个示例代码进行说明 + +```C# +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.functional.pipe; +using GFramework.Game.Abstractions.data; +using GFramework.Game.architecture; +using GFramework.Game.setting; +using GFramework.Godot.setting; +using GFramework.Godot.setting.data; +using GFrameworkGodotTemplate.scripts.setting; + +namespace GFrameworkGodotTemplate.scripts.module; + + +/// +/// ModelModule 类继承自 AbstractModule,用于在架构中安装和注册模型。 +/// 该模块主要负责初始化设置相关的模型,并将其注册到架构中。 +/// +public class ModelModule : AbstractModule +{ + /// + /// 安装方法,用于将模型注册到指定的架构中。 + /// + /// IArchitecture 接口实例,表示当前的应用程序架构。 + public override void Install(IArchitecture architecture) + { + // 获取设置数据仓库的实例,用于后续模型的初始化 + var settingsDataRepository = architecture.Context.GetUtility()!; + + // 注册设置模型,并配置其应用器(Applicator) + architecture.RegisterModel( + new SettingsModel(new SettingDataLocationProvider(), settingsDataRepository) + .Also(it => { - IsAlive.Value = false; - SendEvent(new PlayerDeathEvent()); - } - }); - } - - public void TakeDamage(int damage) - { - if (IsAlive.Value) - { - Health.Value = Math.Max(0, Health.Value - damage); - } - } + // 注册音频设置应用器,用于处理音频相关配置 + it.RegisterApplicator(new GodotAudioSettings(it, new AudioBusMap())) + // 注册图形设置应用器,用于处理图形相关配置 + .RegisterApplicator(new GodotGraphicsSettings(it)) + // 注册本地化设置应用器,用于处理语言和区域相关配置 + .RegisterApplicator(new GodotLocalizationSettings(it, new LocalizationMap())); + }) + ); + } +} + +``` + +可以看到这里把设置数据模型注册进了架构中 + +### 3. 创建系统模块 + +创建 `scripts/module/SystemModule.cs`: + +```C# +using GFramework.Core.Abstractions.architecture; +using GFramework.Game.architecture; + +namespace MyGFrameworkGame.scripts.module; + +public class SystemModule: AbstractModule +{ + public override void Install(IArchitecture architecture) + { + } - - // 游戏事件 - public struct PlayerDeathEvent { } } ``` +这个Install(IArchitecture architecture)方法用于注册所需要的类即各种system + +> System 包含游戏的业务逻辑和规则。系统操作模型并实现核心机制,如战斗、物理、库存管理或进度系统。 + +这里以一个示例代码进行说明 + +```C# +using GFramework.Core.Abstractions.architecture; +using GFramework.Game.architecture; +using GFramework.Game.setting; +using GFrameworkGodotTemplate.scripts.core.scene; +using GFrameworkGodotTemplate.scripts.core.ui; + +namespace GFrameworkGodotTemplate.scripts.module; + +/// +/// 系统Godot模块类,负责安装和注册游戏所需的各种系统组件 +/// 继承自AbstractGodotModule,用于在游戏架构中集成系统功能 +/// +public class SystemModule : AbstractModule +{ + /// + /// 安装方法,用于向游戏架构注册各种系统组件 + /// + /// 游戏架构接口实例,用于注册系统 + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new UiRouter()); + architecture.RegisterSystem(new SceneRouter()); + architecture.RegisterSystem(new SettingsSystem()); + } +} +``` + +可以看到我们向框架里注册了ui路由,场景路由,设置系统,这便是系统模块的作用 + +### 4. 创建工具模块 + +创建 `scripts/module/UtilityModule.cs`: + +```C# +using GFramework.Core.Abstractions.architecture; +using GFramework.Game.architecture; + +namespace MyGFrameworkGame.scripts.module; + +public class UtilityModule: AbstractModule +{ + public override void Install(IArchitecture architecture) + { + + } +} +``` + +同理这个Install(IArchitecture architecture)方法用于注册所需要的类即各种utility + +> Utility 提供可在整个应用程序中使用的无状态辅助函数和算法——数学计算、路径查找、数据验证和其他纯函数。 + +这里以一个示例代码进行说明 + +```C# +using GFramework.Core.Abstractions.architecture; +using GFramework.Game.Abstractions.data; +using GFramework.Game.architecture; +using GFramework.Game.data; +using GFramework.Game.serializer; +using GFramework.Godot.scene; +using GFramework.Godot.storage; +using GFramework.Godot.ui; +using GFrameworkGodotTemplate.scripts.data; +using GFrameworkGodotTemplate.scripts.utility; +using Godot; + +namespace GFrameworkGodotTemplate.scripts.module; + +/// +/// 工具模块类,负责安装和管理游戏中的实用工具组件 +/// +public class UtilityModule : AbstractModule +{ + /// + /// 安装模块到指定的游戏架构中 + /// + /// 要安装模块的目标游戏架构实例 + public override void Install(IArchitecture architecture) + { + architecture.RegisterUtility(new GodotUiRegistry()); + architecture.RegisterUtility(new GodotSceneRegistry()); + architecture.RegisterUtility(new GodotTextureRegistry()); + architecture.RegisterUtility(new GodotUiFactory()); + var jsonSerializer = new JsonSerializer(); + architecture.RegisterUtility(jsonSerializer); + var storage = new GodotFileStorage(jsonSerializer); + architecture.RegisterUtility(storage); + architecture.RegisterUtility(new UnifiedSettingsDataRepository(storage, jsonSerializer, + new DataRepositoryOptions + { + BasePath = ProjectSettings.GetSetting("application/config/save/setting_path").AsString(), + AutoBackup = true + })); + architecture.RegisterUtility(new SaveStorageUtility()); + } +} +``` + +这里安装了非常多的工具,特别是对于工具之间相互依赖的情况下,集中处理是一种比较好的方式。 + +### 5. 创建游戏入口点 + +在Godot创建一个全局类GameEntryPoint.cs + +![image-20260211222402064](assets/basic-tutorial/image-20260211222402064.png) + +全局类代码 + +```C# +using GFramework.Core.Abstractions.architecture; +using Godot; +using MyGFrameworkGame.scripts.architecture; + +namespace MyGFrameworkGame.global; + +/// +/// 游戏入口点类,继承自Node类。 +/// 该类负责初始化游戏架构,并在节点首次进入场景树时执行相关逻辑。 +/// +public partial class GameEntryPoint : Node +{ + /// + /// 获取游戏架构的静态属性。 + /// 该属性在类初始化时被赋值,用于全局访问游戏架构实例。 + /// + public static IArchitecture Architecture { get; private set; } = null!; + + /// + /// 当节点首次进入场景树时调用此方法。 + /// 在此方法中,创建并初始化游戏架构实例。 + /// + public override void _Ready() + { + // 创建游戏架构实例 + Architecture = new GameArchitecture(); + // 初始化游戏架构 + Architecture.Initialize(); + } +} +``` + +### 6. 安装模块 + +```C# +using GFramework.Godot.architecture; +using MyGFrameworkGame.scripts.module; + +namespace MyGFrameworkGame.scripts.architecture; + +/// +/// 游戏架构类,继承自抽象架构类 AbstractArchitecture。 +/// 负责安装和管理游戏中的各个模块。 +/// +public class GameArchitecture : AbstractArchitecture +{ + /// + /// 安装游戏所需的模块。 + /// 该方法在架构初始化时被调用,用于注册模型、系统和工具模块。 + /// + protected override void InstallModules() + { + // 安装模型模块,负责处理游戏数据和业务逻辑 + InstallModule(new ModelModule()); + + // 安装系统模块,负责处理游戏的核心系统功能 + InstallModule(new SystemModule()); + + // 安装工具模块,提供通用的辅助功能和工具方法 + InstallModule(new UtilityModule()); + } +} +``` + +这样我们的准备工作就完成了 + ## 功能实现 -### 1. 创建主场景 +### 1. 实现基础功能 -创建 `src/Game/MainScene.cs`: +首先让我们搭建一个非常简单的场景 -```csharp +![image-20260211215031192](assets/basic-tutorial/image-20260211215031192.png) + +这个是大致结构 + +![image-20260211214905664](assets/basic-tutorial/image-20260211214905664.png) + +这个是效果图 + +接着我们创建对应的代码 + +```C# +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.logging; +using GFramework.SourceGenerators.Abstractions.rule; using Godot; -using GFramework.Godot.extensions; -using GFramework.Godot.architecture; -using MyGFrameworkGame.Core.Architecture; -using MyGFrameworkGame.Core.Models; -namespace MyGFrameworkGame +namespace MyGFrameworkGame.scripts.app; + +/// +/// App 类继承自 Control 并实现 IController 接口,用于管理应用程序的核心逻辑。 +/// 该类通过按钮控制计数器的增减,并更新界面上的标签显示。 +/// +[ContextAware] +[Log] +public partial class App : Control, IController { - [ContextAware] - [Log] - public partial class MainScene : Node2D + /// + /// 获取场景中的 AddButton 节点,用于增加计数器值。 + /// + private Button AddButton => GetNode