GFramework/docs/tutorials/getting-started.md
GeWuYou a8803f31be docs(GFramework.Game): 添加游戏功能模块完整文档
创建了 GFramework.Game 模块的详细 README 文档,涵盖以下核心内容:

- 模块概述和核心设计理念介绍
- 架构模块系统说明,包含 AbstractModule 使用示例
- 资产管理系统详解,包括资产目录和映射功能
- 存储系统实现,支持分层存储和缓存机制
- 序列化系统集成,基于 Newtonsoft.Json 的完整方案
- 丰富的代码示例,展示实际使用场景
- 最佳实践指南,涵盖数据模型设计和性能优化建议
- 性能特性说明和技术栈依赖关系
2026-01-12 13:40:36 +08:00

44 KiB
Raw Blame History

从零开始 - GFramework 游戏开发教程

这是一个完整的从零开始的教程,将带领你创建一个使用 GFramework 的简单游戏项目。

📋 目录

环境准备

系统要求

  • 操作系统: 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 官网
  2. 下载并安装 .NET 6.0 SDK
  3. 验证安装:
dotnet --version
# 应该显示 6.0.x 或更高版本

安装 Godot

  1. 访问 Godot 官网
  2. 下载 Godot 4.5.1
  3. 解压到合适的位置并启动
  4. 在编辑器设置中确认 .NET 支持

验证环境

创建一个测试项目验证环境:

# 创建测试项目
dotnet new console -n TestProject
cd TestProject

# 如果使用 Godot添加 Godot 引用
dotnet add package GeWuYou.GFramework.Core
dotnet add package GeWuYou.GFramework.Godot

# 编译测试
dotnet build

项目创建

1. 创建新的 Godot 项目

  1. 打开 Godot 编辑器
  2. 点击 "新建项目"
  3. 创建项目文件夹,命名为 "MyGFrameworkGame"
  4. 选择 C# 作为脚本语言

2. 配置项目结构

在项目根目录创建以下文件夹结构:

MyGFrameworkGame/
├── src/
│   ├── Game/           # 游戏逻辑
│   │   ├── Models/     # 数据模型
│   │   ├── Systems/    # 业务系统
│   │   ├── Controllers/ # 控制器
│   │   └── Utilities/  # 工具类
│   └── Game.Core/      # 核心游戏组件
├── assets/             # 游戏资源
│   ├── scenes/
│   ├── textures/
│   ├── audio/
│   └── ui/
└── project.godot       # Godot 项目文件

3. 配置项目文件

创建 src/Game/Game.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>MyGFrameworkGame</RootNamespace>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Game.Core\Game.Core.csproj" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="GeWuYou.GFramework.Core" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.Game" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.Godot" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.SourceGenerators" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.SourceGenerators.Attributes" Version="1.0.0" />
  </ItemGroup>

</Project>

创建 src/Game.Core/Game.Core.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>MyGFrameworkGame.Core</RootNamespace>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="GeWuYou.GFramework.Core" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.Core.Abstractions" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.Game" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.Game.Abstractions" Version="1.0.0" />
  </ItemGroup>

</Project>

4. 配置 Godot 项目

project.godot 中添加 C# 配置:

; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
;   [section] ; section goes between []
;   param=value ; assign values to parameters

[application]

config/name="My GFramework Game"
config/description="A game built with GFramework"
run/main_scene="res://src/Game/MainScene.tscn"
config/features=PackedStringArray("4.5", "Forward Plus")

[dotnet]

project/assembly_name="MyGFrameworkGame"
project/solution_directory="src/"

架构设计

1. 定义游戏架构

创建 src/Game.Core/Architecture/GameArchitecture.cs:

using GFramework.Core.architecture;
using MyGFrameworkGame.Core.Models;
using MyGFrameworkGame.Core.Systems;
using MyGFrameworkGame.Core.Utilities;

namespace MyGFrameworkGame.Core.Architecture
{
    public class GameArchitecture : AbstractArchitecture
    {
        protected override void Init()
        {
            // 注册游戏模型
            RegisterModel(new PlayerModel());
            RegisterModel(new GameModel());
            RegisterModel(new ScoreModel());
            
            // 注册游戏系统
            RegisterSystem(new PlayerControllerSystem());
            RegisterSystem(new EnemySpawnerSystem());
            RegisterSystem(new CollisionSystem());
            RegisterSystem(new ScoreSystem());
            
            // 注册工具类
            RegisterUtility(new StorageUtility());
            RegisterUtility(new AudioUtility());
            RegisterUtility(new AssetLoadUtility());
        }
        
        protected override void InstallModules()
        {
            // 如果需要 Godot 特定模块
            // InstallGodotModule(new AudioModule());
        }
    }
}

2. 创建核心模型

创建 src/Game.Core/Models/PlayerModel.cs:

using GFramework.Core.model;

namespace MyGFrameworkGame.Core.Models
{
    public class PlayerModel : AbstractModel
    {
        public BindableProperty<int> Health { get; } = new(100);
        public BindableProperty<int> MaxHealth { get; } = new(100);
        public BindableProperty<float> Speed { get; } = new(300.0f);
        public BindableProperty<int> Score { get; } = new(0);
        public BindableProperty<bool> IsAlive { get; } = new(true);
        public BindableProperty<bool> CanShoot { get; } = new(true);
        
        protected override void OnInit()
        {
            // 监听生命值变化
            Health.Register(health => {
                if (health <= 0)
                {
                    IsAlive.Value = false;
                    SendEvent(new PlayerDeathEvent());
                }
            });
            
            // 监听分数变化
            Score.Register(score => {
                if (score % 100 == 0)
                {
                    SendEvent(new MilestoneEvent { Score = score });
                }
            });
        }
        
        public void TakeDamage(int damage)
        {
            if (IsAlive.Value)
            {
                var newHealth = Math.Max(0, Health.Value - damage);
                Health.Value = newHealth;
            }
        }
        
        public void Heal(int amount)
        {
            var newHealth = Math.Min(MaxHealth.Value, Health.Value + amount);
            Health.Value = newHealth;
        }
        
        public bool CanShoot()
        {
            return IsAlive.Value && CanShoot.Value;
        }
    }
    
    // 游戏事件
    public struct PlayerDeathEvent { }
    public struct MilestoneEvent { public int Score; }
}

创建 src/Game.Core/Models/GameModel.cs:

using GFramework.Core.model;

namespace MyGFrameworkGame.Core.Models
{
    public class GameModel : AbstractModel
    {
        public BindableProperty<GameState> State { get; } = new(GameState.Menu);
        public BindableProperty<int> CurrentLevel { get; } = new(1);
        public BindableProperty<int> EnemyCount { get; } = new(0);
        public BindableProperty<float> GameTime { get; } = new(0.0f);
        public BindableProperty<bool> IsPaused { get; } = new(false);
        
        protected override void OnInit()
        {
            State.Register(state => {
                switch (state)
                {
                    case GameState.Playing:
                        SendEvent(new GameStartEvent());
                        break;
                    case GameState.Paused:
                        SendEvent(new GamePauseEvent());
                        break;
                    case GameState.GameOver:
                        SendEvent(new GameOverEvent());
                        break;
                    case GameState.Menu:
                        SendEvent(new GameMenuEvent());
                        break;
                }
            });
        }
        
        public void StartGame()
        {
            State.Value = GameState.Playing;
            EnemyCount.Value = 0;
            GameTime.Value = 0.0f;
        }
        
        public void PauseGame()
        {
            if (State.Value == GameState.Playing)
            {
                State.Value = GameState.Paused;
            }
        }
        
        public void ResumeGame()
        {
            if (State.Value == GameState.Paused)
            {
                State.Value = GameState.Playing;
            }
        }
        
        public void GameOver()
        {
            State.Value = GameState.GameOver;
        }
        
        public void ReturnToMenu()
        {
            State.Value = GameState.Menu;
        }
    }
    
    public enum GameState
    {
        Menu,
        Playing,
        Paused,
        GameOver
    }
    
    // 游戏事件
    public struct GameStartEvent { }
    public struct GamePauseEvent { }
    public struct GameResumeEvent { }
    public struct GameOverEvent { }
    public struct GameMenuEvent { }
}

3. 创建核心系统

创建 src/Game.Core/Systems/PlayerControllerSystem.cs:

using Godot;
using GFramework.Core.system;
using GFramework.Core.events;
using MyGFrameworkGame.Core.Models;

namespace MyGFrameworkGame.Core.Systems
{
    public class PlayerControllerSystem : AbstractSystem
    {
        private PlayerModel _playerModel;
        private GameModel _gameModel;
        
        protected override void OnInit()
        {
            _playerModel = GetModel<PlayerModel>();
            _gameModel = GetModel<GameModel>();
            
            // 监听输入事件
            RegisterEvent<PlayerInputEvent>(OnPlayerInput);
            RegisterEvent<GameStartEvent>(OnGameStart);
            RegisterEvent<GameOverEvent>(OnGameOver);
        }
        
        private void OnPlayerInput(PlayerInputEvent inputEvent)
        {
            if (_gameModel.State.Value != GameState.Playing)
                return;
                
            switch (inputEvent.Action)
            {
                case "move":
                    HandleMovement(inputEvent.Direction);
                    break;
                case "shoot":
                    HandleShoot();
                    break;
                case "pause":
                    _gameModel.PauseGame();
                    break;
            }
        }
        
        private void HandleMovement(Vector2 direction)
        {
            if (!_playerModel.IsAlive.Value)
                return;
                
            // 发送移动事件,由 Godot 控制器处理
            SendEvent(new PlayerMoveEvent { Direction = direction.Normalized() * _playerModel.Speed.Value });
        }
        
        private void HandleShoot()
        {
            if (!_playerModel.CanShoot())
                return;
                
            // 发送射击事件
            SendEvent(new PlayerShootEvent());
            _playerModel.CanShoot.Value = false;
            
            // 重置射击冷却
            this.StartCoroutine(ShootCooldown());
        }
        
        private System.Collections.IEnumerator ShootCooldown()
        {
            yield return new WaitForSeconds(0.3f);
            _playerModel.CanShoot.Value = true;
        }
        
        private void OnGameStart(GameStartEvent e)
        {
            // 重置玩家状态
            _playerModel.Health.Value = _playerModel.MaxHealth.Value;
            _playerModel.Score.Value = 0;
            _playerModel.IsAlive.Value = true;
            _playerModel.CanShoot.Value = true;
        }
        
        private void OnGameOver(GameOverEvent e)
        {
            // 游戏结束时的处理
            SendEvent(new SaveScoreEvent { Score = _playerModel.Score.Value });
        }
    }
    
    // 输入事件
    public struct PlayerInputEvent
    {
        public string Action { get; set; }
        public Vector2 Direction { get; set; }
    }
    
    // 移动事件
    public struct PlayerMoveEvent
    {
        public Vector2 Direction { get; set; }
    }
    
    // 射击事件
    public struct PlayerShootEvent { }
    
    // 保存分数事件
    public struct SaveScoreEvent { public int Score; }
    
    // 协程等待类(简化版)
    public class WaitForSeconds
    {
        public float Seconds { get; }
        public WaitForSeconds(float seconds) => Seconds = seconds;
    }
}

功能实现

1. 创建主场景

创建 src/Game/MainScene.cs:

using Godot;
using GFramework.Godot.extensions;
using GFramework.Godot.architecture;
using MyGFrameworkGame.Core.Architecture;
using MyGFrameworkGame.Core.Models;
using MyGFrameworkGame.Core.Systems;

namespace MyGFrameworkGame
{
    [ContextAware]
    [Log]
    public partial class MainScene : Node2D
    {
        private GameArchitecture _architecture;
        private Player _player;
        private UI _ui;
        private EnemySpawner _enemySpawner;
        
        public override void _Ready()
        {
            Logger.Info("Main scene ready");
            
            // 初始化架构
            InitializeArchitecture();
            
            // 创建游戏对象
            CreateGameObjects();
            
            // 注册事件监听
            RegisterEventListeners();
            
            // 开始游戏
            StartGame();
        }
        
        private void InitializeArchitecture()
        {
            Logger.Info("Initializing game architecture");
            
            _architecture = new GameArchitecture();
            _architecture.Initialize();
            
            // 设置上下文
            SetContext(_architecture.Context);
        }
        
        private void CreateGameObjects()
        {
            Logger.Info("Creating game objects");
            
            // 创建玩家
            var playerScene = GD.Load<PackedScene>("res://assets/scenes/Player.tscn");
            _player = playerScene.Instantiate<Player>();
            AddChild(_player);
            
            // 创建 UI
            var uiScene = GD.Load<PackedScene>("res://assets/scenes/UI.tscn");
            _ui = uiScene.Instantiate<UI>();
            AddChild(_ui);
            
            // 创建敌人生成器
            _enemySpawner = new EnemySpawner();
            AddChild(_enemySpawner);
        }
        
        private void RegisterEventListeners()
        {
            Logger.Info("Registering event listeners");
            
            // 游戏状态事件
            this.RegisterEvent<GameStartEvent>(OnGameStart)
                .UnRegisterWhenNodeExitTree(this);
                
            this.RegisterEvent<GameOverEvent>(OnGameOver)
                .UnRegisterWhenNodeExitTree(this);
                
            this.RegisterEvent<GamePauseEvent>(OnGamePause)
                .UnRegisterWhenNodeExitTree(this);
                
            // 玩家事件
            this.RegisterEvent<PlayerDeathEvent>(OnPlayerDeath)
                .UnRegisterWhenNodeExitTree(this);
                
            // 分数事件
            this.RegisterEvent<SaveScoreEvent>(OnSaveScore)
                .UnRegisterWhenNodeExitTree(this);
        }
        
        private void StartGame()
        {
            Logger.Info("Starting game");
            var gameModel = Context.GetModel<GameModel>();
            gameModel.StartGame();
        }
        
        private void OnGameStart(GameStartEvent e)
        {
            Logger.Info("Game started");
            _ui.ShowGameplayUI();
        }
        
        private void OnGameOver(GameOverEvent e)
        {
            Logger.Info("Game over");
            _ui.ShowGameOverScreen();
        }
        
        private void OnGamePause(GamePauseEvent e)
        {
            Logger.Info("Game paused");
            _ui.ShowPauseMenu();
            GetTree().Paused = true;
        }
        
        private void OnPlayerDeath(PlayerDeathEvent e)
        {
            Logger.Info("Player died");
            var gameModel = Context.GetModel<GameModel>();
            gameModel.GameOver();
        }
        
        private void OnSaveScore(SaveScoreEvent e)
        {
            Logger.Info($"Saving score: {e.Score}");
            _ui.UpdateFinalScore(e.Score);
        }
        
        public override void _Input(InputEvent @event)
        {
            // 处理退出游戏
            if (@event.IsActionPressed("ui_cancel"))
            {
                var gameModel = Context.GetModel<GameModel>();
                if (gameModel.State.Value == GameState.Playing)
                {
                    gameModel.PauseGame();
                }
                else if (gameModel.State.Value == GameState.Paused)
                {
                    gameModel.ResumeGame();
                    GetTree().Paused = false;
                }
            }
        }
        
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _architecture?.Destroy();
            }
            base.Dispose(disposing);
        }
    }
}

2. 创建玩家控制器

创建 src/Game/Player.cs:

using Godot;
using GFramework.Godot.extensions;
using GFramework.Core.events;
using MyGFrameworkGame.Core.Models;
using MyGFrameworkGame.Core.Systems;

namespace MyGFrameworkGame
{
    [ContextAware]
    [Log]
    public partial class Player : CharacterBody2D
    {
        [Export] public float Speed { get; set; } = 300.0f;
        
        private PlayerModel _playerModel;
        private GameModel _gameModel;
        private AnimatedSprite2D _animatedSprite;
        private CollisionShape2D _collisionShape;
        private Timer _invincibilityTimer;
        
        public override void _Ready()
        {
            Logger.Info("Player ready");
            
            // 获取组件引用
            _animatedSprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
            _collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
            _invincibilityTimer = GetNode<Timer>("InvincibilityTimer");
            
            // 设置上下文
            _playerModel = Context.GetModel<PlayerModel>();
            _gameModel = Context.GetModel<GameModel>();
            
            // 注册事件监听
            RegisterEventListeners();
            
            // 设置动画
            _animatedSprite.Play("idle");
        }
        
        private void RegisterEventListeners()
        {
            // 移动事件
            this.RegisterEvent<PlayerMoveEvent>(OnPlayerMove)
                .UnRegisterWhenNodeExitTree(this);
                
            // 射击事件
            this.RegisterEvent<PlayerShootEvent>(OnPlayerShoot)
                .UnRegisterWhenNodeExitTree(this);
                
            // 玩家状态变化
            _playerModel.Health.Register(OnHealthChanged)
                .UnRegisterWhenNodeExitTree(this);
                
            _playerModel.IsAlive.Register(OnAliveChanged)
                .UnRegisterWhenNodeExitTree(this);
        }
        
        public override void _Process(double delta)
        {
            // 处理输入并发送事件
            HandleInput();
            
            // 更新位置
            var collision = MoveAndCollide(Velocity * (float)delta);
            if (collision != null)
            {
                HandleCollision(collision);
            }
        }
        
        private void HandleInput()
        {
            var inputVector = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
            
            if (inputVector != Vector2.Zero)
            {
                // 发送移动事件
                SendEvent(new PlayerInputEvent
                {
                    Action = "move",
                    Direction = inputVector
                });
                
                // 更新动画
                UpdateAnimation(inputVector);
            }
            
            // 射击输入
            if (Input.IsActionJustPressed("shoot"))
            {
                SendEvent(new PlayerInputEvent { Action = "shoot" });
            }
        }
        
        private void OnPlayerMove(PlayerMoveEvent e)
        {
            Velocity = e.Direction * Speed;
        }
        
        private void OnPlayerShoot(PlayerShootEvent e)
        {
            Shoot();
        }
        
        private void OnHealthChanged(int newHealth)
        {
            Logger.Debug($"Player health changed: {newHealth}");
            
            // 受伤效果
            if (newHealth < _playerModel.MaxHealth.Value)
            {
                FlashRed();
                StartInvincibility();
            }
        }
        
        private void OnAliveChanged(bool isAlive)
        {
            if (!isAlive)
            {
                Die();
            }
        }
        
        private void Shoot()
        {
            Logger.Debug("Player shooting");
            
            // 创建子弹
            var bulletScene = GD.Load<PackedScene>("res://assets/scenes/Bullet.tscn");
            var bullet = bulletScene.Instantiate<Bullet>();
            
            // 设置子弹位置和方向
            bullet.Position = Position;
            bullet.Direction = Vector2.Up; // 向上射击
            
            // 添加到场景
            GetTree().Root.AddChild(bullet);
            
            // 播放射击音效
            PlayShootSound();
        }
        
        private void UpdateAnimation(Vector2 inputVector)
        {
            if (inputVector.Y < 0)
            {
                _animatedSprite.Play("up");
            }
            else if (inputVector.Y > 0)
            {
                _animatedSprite.Play("down");
            }
            else if (inputVector.X != 0)
            {
                _animatedSprite.Play("side");
                _animatedSprite.FlipH = inputVector.X < 0;
            }
            else
            {
                _animatedSprite.Play("idle");
            }
        }
        
        private void FlashRed()
        {
            _animatedSprite.Modulate = Colors.Red;
            this.CreateSignalBuilder(_invincibilityTimer.SignalName.Timeout)
                .Connect(() => _animatedSprite.Modulate = Colors.White)
                .UnRegisterWhenNodeExitTree(this);
        }
        
        private void StartInvincibility()
        {
            SetCollisionLayerValue(1, false); // 关闭碰撞
            _invincibilityTimer.Start();
        }
        
        private void HandleCollision(KinematicCollision2D collision)
        {
            var collider = collision.GetCollider();
            
            if (collider is Enemy enemy && _playerModel.IsAlive.Value)
            {
                // 受到伤害
                _playerModel.TakeDamage(enemy.Damage);
            }
        }
        
        private void Die()
        {
            Logger.Info("Player died");
            
            // 播放死亡动画
            _animatedSprite.Play("death");
            
            // 禁用碰撞
            _collisionShape.Disabled = true;
            
            // 发送玩家死亡事件
            SendEvent(new PlayerDeathEvent());
            
            // 延迟移除
            this.CreateSignalBuilder(_animatedSprite.SignalName.AnimationFinished)
                .WithFlags(ConnectFlags.OneShot)
                .Connect(() => QueueFreeX())
                .UnRegisterWhenNodeExitTree(this);
        }
        
        private void PlayShootSound()
        {
            var audioPlayer = new AudioStreamPlayer();
            var sound = GD.Load<AudioStream>("res://assets/audio/shoot.wav");
            audioPlayer.Stream = sound;
            AddChild(audioPlayer);
            audioPlayer.Play();
            
            // 音效播放完成后移除
            this.CreateSignalBuilder(audioPlayer.SignalName.Finished)
                .WithFlags(ConnectFlags.OneShot)
                .Connect(() => audioPlayer.QueueFreeX())
                .UnRegisterWhenNodeExitTree(this);
        }
    }
}

3. 创建 UI 系统

创建 src/Game/UI.cs:

using Godot;
using GFramework.Godot.extensions;
using GFramework.Core.events;
using MyGFrameworkGame.Core.Models;

namespace MyGFrameworkGame
{
    [ContextAware]
    [Log]
    public partial class UI : CanvasLayer
    {
        private Control _gameplayUI;
        private Control _pauseMenu;
        private Control _gameOverScreen;
        private Label _scoreLabel;
        private Label _healthLabel;
        private Label _finalScoreLabel;
        private Button _resumeButton;
        private Button _quitButton;
        private Button _restartButton;
        
        private PlayerModel _playerModel;
        private GameModel _gameModel;
        
        public override void _Ready()
        {
            Logger.Info("UI ready");
            
            // 获取 UI 组件引用
            InitializeUIComponents();
            
            // 设置上下文
            _playerModel = Context.GetModel<PlayerModel>();
            _gameModel = Context.GetModel<GameModel>();
            
            // 注册事件监听
            RegisterEventListeners();
            
            // 注册按钮事件
            RegisterButtonEvents();
            
            // 显示主菜单
            ShowMainMenu();
        }
        
        private void InitializeUIComponents()
        {
            // 游戏 UI
            _gameplayUI = GetNode<Control>("GameplayUI");
            _scoreLabel = GetNode<Label>("GameplayUI/ScoreLabel");
            _healthLabel = GetNode<Label>("GameplayUI/HealthLabel");
            
            // 暂停菜单
            _pauseMenu = GetNode<Control>("PauseMenu");
            _resumeButton = GetNode<Button>("PauseMenu/VBoxContainer/ResumeButton");
            _quitButton = GetNode<Button>("PauseMenu/VBoxContainer/QuitButton");
            
            // 游戏结束画面
            _gameOverScreen = GetNode<Control>("GameOverScreen");
            _finalScoreLabel = GetNode<Label>("GameOverScreen/CenterContainer/VBoxContainer/FinalScoreLabel");
            _restartButton = GetNode<Button>("GameOverScreen/CenterContainer/VBoxContainer/RestartButton");
        }
        
        private void RegisterEventListeners()
        {
            // 监听分数变化
            _playerModel.Score.Register(UpdateScoreDisplay)
                .UnRegisterWhenNodeExitTree(this);
                
            // 监听生命值变化
            _playerModel.Health.Register(UpdateHealthDisplay)
                .UnRegisterWhenNodeExitTree(this);
        }
        
        private void RegisterButtonEvents()
        {
            // 暂停菜单按钮
            this.CreateSignalBuilder(_resumeButton.SignalName.Pressed)
                .Connect(() => {
                    _gameModel.ResumeGame();
                    GetTree().Paused = false;
                    ShowGameplayUI();
                })
                .UnRegisterWhenNodeExitTree(this);
                
            this.CreateSignalBuilder(_quitButton.SignalName.Pressed)
                .Connect(() => {
                    GetTree().Quit();
                })
                .UnRegisterWhenNodeExitTree(this);
                
            // 重新开始按钮
            this.CreateSignalBuilder(_restartButton.SignalName.Pressed)
                .Connect(() => {
                    RestartGame();
                })
                .UnRegisterWhenNodeExitTree(this);
        }
        
        public void ShowMainMenu()
        {
            HideAllUI();
            // TODO: 实现主菜单
            Logger.Info("Showing main menu");
        }
        
        public void ShowGameplayUI()
        {
            HideAllUI();
            _gameplayUI.Visible = true;
            UpdateUI();
        }
        
        public void ShowPauseMenu()
        {
            _pauseMenu.Visible = true;
        }
        
        public void ShowGameOverScreen()
        {
            HideAllUI();
            _gameOverScreen.Visible = true;
        }
        
        private void HideAllUI()
        {
            _gameplayUI.Visible = false;
            _pauseMenu.Visible = false;
            _gameOverScreen.Visible = false;
        }
        
        private void UpdateUI()
        {
            UpdateScoreDisplay(_playerModel.Score.Value);
            UpdateHealthDisplay(_playerModel.Health.Value);
        }
        
        private void UpdateScoreDisplay(int score)
        {
            _scoreLabel.Text = $"Score: {score}";
        }
        
        private void UpdateHealthDisplay(int health)
        {
            _healthLabel.Text = $"Health: {health}";
            
            // 根据生命值改变颜色
            if (health <= 20)
            {
                _healthLabel.Modulate = Colors.Red;
            }
            else if (health <= 50)
            {
                _healthLabel.Modulate = Colors.Yellow;
            }
            else
            {
                _healthLabel.Modulate = Colors.White;
            }
        }
        
        public void UpdateFinalScore(int score)
        {
            _finalScoreLabel.Text = $"Final Score: {score}";
        }
        
        private void RestartGame()
        {
            Logger.Info("Restarting game");
            
            // 重新加载场景
            GetTree().CallDeferred(SceneTree.MethodName.ReloadCurrentScene);
        }
    }
}

测试验证

1. 创建测试项目

创建 tests/Game.Tests/Game.Tests.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>MyGFrameworkGame.Tests</RootNamespace>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
    <PackageReference Include="NUnit" Version="3.13.3" />
    <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
    <PackageReference Include="GeWuYou.GFramework.Core" Version="1.0.0" />
    <PackageReference Include="GeWuYou.GFramework.Core.Tests" Version="1.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\src\Game.Core\Game.Core.csproj" />
    </ItemGroup>

</Project>

2. 创建单元测试

创建 tests/Game.Tests/PlayerModelTests.cs:

using NUnit.Framework;
using GFramework.Core.Tests;
using MyGFrameworkGame.Core.Models;

namespace MyGFrameworkGame.Tests
{
    [TestFixture]
    public class PlayerModelTests : ArchitectureTestsBase<TestArchitecture>
    {
        private PlayerModel _playerModel;
        
        protected override TestArchitecture CreateArchitecture()
        {
            return new TestArchitecture();
        }
        
        [SetUp]
        public void Setup()
        {
            Architecture.Initialize();
            _playerModel = Architecture.Context.GetModel<PlayerModel>();
        }
        
        [Test]
        public void PlayerModel_InitialValues_ShouldBeCorrect()
        {
            // Assert
            Assert.That(_playerModel.Health.Value, Is.EqualTo(100));
            Assert.That(_playerModel.MaxHealth.Value, Is.EqualTo(100));
            Assert.That(_playerModel.Speed.Value, Is.EqualTo(300.0f));
            Assert.That(_playerModel.Score.Value, Is.EqualTo(0));
            Assert.That(_playerModel.IsAlive.Value, Is.True);
            Assert.That(_playerModel.CanShoot.Value, Is.True);
        }
        
        [Test]
        public void TakeDamage_ValidDamage_ShouldReduceHealth()
        {
            // Act
            _playerModel.TakeDamage(20);
            
            // Assert
            Assert.That(_playerModel.Health.Value, Is.EqualTo(80));
            Assert.That(_playerModel.IsAlive.Value, Is.True);
        }
        
        [Test]
        public void TakeDamage_LethalDamage_ShouldKillPlayer()
        {
            // Act
            _playerModel.TakeDamage(150);
            
            // Assert
            Assert.That(_playerModel.Health.Value, Is.EqualTo(0));
            Assert.That(_playerModel.IsAlive.Value, Is.False);
        }
        
        [Test]
        public void TakeDamage_WhenDead_ShouldNotChangeHealth()
        {
            // Arrange
            _playerModel.TakeDamage(100); // Kill player
            var healthAfterDeath = _playerModel.Health.Value;
            
            // Act
            _playerModel.TakeDamage(10);
            
            // Assert
            Assert.That(_playerModel.Health.Value, Is.EqualTo(healthAfterDeath));
        }
        
        [Test]
        public void Heal_ValidAmount_ShouldRestoreHealth()
        {
            // Arrange
            _playerModel.TakeDamage(30); // Health = 70
            
            // Act
            _playerModel.Heal(20);
            
            // Assert
            Assert.That(_playerModel.Health.Value, Is.EqualTo(90));
        }
        
        [Test]
        public void Heal_ExcessAmount_ShouldNotExceedMaxHealth()
        {
            // Arrange
            _playerModel.TakeDamage(50); // Health = 50
            
            // Act
            _playerModel.Heal(100); // Try to heal more than max
            
            // Assert
            Assert.That(_playerModel.Health.Value, Is.EqualTo(100)); // Should cap at max
        }
        
        [Test]
        public void CanShoot_WhenAliveAndNotOnCooldown_ShouldReturnTrue()
        {
            // Act & Assert
            Assert.That(_playerModel.CanShoot(), Is.True);
        }
        
        [Test]
        public void CanShoot_WhenDead_ShouldReturnFalse()
        {
            // Arrange
            _playerModel.TakeDamage(100);
            
            // Act & Assert
            Assert.That(_playerModel.CanShoot(), Is.False);
        }
        
        [Test]
        public void CanShoot_WhenOnCooldown_ShouldReturnFalse()
        {
            // Arrange
            _playerModel.CanShoot.Value = false;
            
            // Act & Assert
            Assert.That(_playerModel.CanShoot(), Is.False);
        }
    }
    
    // 测试架构
    public class TestArchitecture : Architecture
    {
        protected override void Init()
        {
            RegisterModel(new PlayerModel());
        }
    }
}

3. 运行测试

cd tests/Game.Tests
dotnet test

4. 集成测试

创建 tests/Game.Tests/GameIntegrationTests.cs:

using NUnit.Framework;
using Godot;
using MyGFrameworkGame;

namespace MyGFrameworkGame.Tests
{
    [TestFixture]
    public class GameIntegrationTests
    {
        private SceneTree _sceneTree;
        private MainScene _mainScene;
        
        [SetUp]
        public void Setup()
        {
            // 创建测试场景树
            _sceneTree = new SceneTree();
            _sceneTree.Root = new Node();
            
            // 创建主场景
            _mainScene = new MainScene();
            _sceneTree.Root.AddChild(_mainScene);
        }
        
        [TearDown]
        public void TearDown()
        {
            _mainScene?.QueueFree();
            _sceneTree?.Quit();
        }
        
        [Test]
        public void MainScene_Ready_ShouldInitializeCorrectly()
        {
            // Act
            _mainScene._Ready();
            
            // Wait a frame for initialization
            _sceneTree.ProcessFrame();
            
            // Assert
            Assert.That(_mainScene.GetChildCount(), Is.GreaterThan(0));
            // 验证架构已初始化
            var context = _mainScene.Context;
            Assert.That(context, Is.Not.Null);
        }
        
        [Test]
        public void Game_StartGame_ShouldChangeGameStateToPlaying()
        {
            // Arrange
            _mainScene._Ready();
            _sceneTree.ProcessFrame();
            
            var gameModel = _mainScene.Context.GetModel<GameModel>();
            
            // Act
            gameModel.StartGame();
            
            // Assert
            Assert.That(gameModel.State.Value, Is.EqualTo(GameState.Playing));
        }
    }
}

项目打包

1. 配置导出设置

  1. 在 Godot 编辑器中,点击 "项目" → "导出"
  2. 添加 Windows/macOS/Linux 导出预设
  3. 配置导出选项:
    • 目标: 选择目标平台
    • 调试信息: 发布版本选择 "无"
    • 优化级别: "速度优化"
    • PCK 加密: 可选的安全选项

2. 创建导出脚本

创建 scripts/export.py:

# Godot 导出脚本
import os

def export_game():
    # Windows 导出
    os.system("godot --headless --export-release \"Windows Desktop\" build/windows/game.exe")
    
    # macOS 导出
    os.system("godot --headless --export-release \"macOS\" build/macOS/game.zip")
    
    # Linux 导出
    os.system("godot --headless --export-release \"Linux/X11\" build/linux/game.x86_64")

if __name__ == "__main__":
    export_game()

3. 自动化构建

创建 .github/workflows/build.yml:

name: Build Game

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
    - uses: actions/checkout@v3
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: '6.0.x'
        
    - name: Restore dependencies
      run: dotnet restore src/Game/Game.csproj
      
    - name: Build
      run: dotnet build src/Game/Game.csproj --no-restore
      
    - name: Test
      run: dotnet test tests/Game.Tests/Game.Tests.csproj --no-build --verbosity normal
      
    - name: Setup Godot
      uses: chickensoft-games/setup-godot@v1
      with:
        version: 4.5.1
        
    - name: Export Game
      run: |
        mkdir -p build
        godot --headless --export-release "Windows Desktop" build/game.exe

进阶功能

1. 添加音效系统

创建 src/Game.Core/Systems/AudioSystem.cs:

using Godot;
using GFramework.Core.system;
using MyGFrameworkGame.Core.Models;

namespace MyGFrameworkGame.Core.Systems
{
    public class AudioSystem : AbstractSystem
    {
        private AudioStreamPlayer _musicPlayer;
        private AudioStreamPlayer _sfxPlayer;
        
        protected override void OnInit()
        {
            // 注册音频事件监听
            RegisterEvent<PlayerShootEvent>(OnPlayerShoot);
            RegisterEvent<PlayerDeathEvent>(OnPlayerDeath);
            RegisterEvent<GameStartEvent>(OnGameStart);
        }
        
        private void OnPlayerShoot(PlayerShootEvent e)
        {
            PlaySound("shoot");
        }
        
        private void OnPlayerDeath(PlayerDeathEvent e)
        {
            PlaySound("death");
        }
        
        private void OnGameStart(GameStartEvent e)
        {
            PlayMusic("background");
        }
        
        private void PlaySound(string soundName)
        {
            var sound = GD.Load<AudioStream>($"res://assets/audio/{soundName}.wav");
            _sfxPlayer.Stream = sound;
            _sfxPlayer.Play();
        }
        
        private void PlayMusic(string musicName)
        {
            var music = GD.Load<AudioStream>($"res://assets/audio/{musicName}.ogg");
            _musicPlayer.Stream = music;
            _musicPlayer.Play();
        }
    }
}

2. 添加存档系统

创建 src/Game.Core/Systems/SaveSystem.cs:

using System.IO;
using GFramework.Core.system;
using GFramework.Core.command;
using MyGFrameworkGame.Core.Models;
using Newtonsoft.Json;

namespace MyGFrameworkGame.Core.Systems
{
    public class SaveSystem : AbstractSystem
    {
        private const string SavePath = "user://saves/";
        
        protected override void OnInit()
        {
            // 创建保存目录
            if (!DirAccess.DirExistsAbsolute(SavePath))
            {
                DirAccess.Open("user://").MakeDir("saves");
            }
            
            // 注册保存命令
            RegisterCommand<SaveGameCommand>(SaveGame);
            RegisterCommand<LoadGameCommand>(LoadGame);
        }
        
        private void SaveGame(SaveGameCommand command)
        {
            var playerModel = GetModel<PlayerModel>();
            var gameModel = GetModel<GameModel>();
            
            var saveData = new SaveData
            {
                PlayerHealth = playerModel.Health.Value,
                PlayerScore = playerModel.Score.Value,
                CurrentLevel = gameModel.CurrentLevel.Value,
                Timestamp = System.DateTime.Now
            };
            
            var json = JsonConvert.SerializeObject(saveData, Formatting.Indented);
            var file = FileAccess.Open($"{SavePath}save_{command.SlotId}.json", FileAccess.ModeFlags.Write);
            file.StoreString(json);
            file.Close();
        }
        
        private void LoadGame(LoadGameCommand command)
        {
            var filePath = $"{SavePath}save_{command.SlotId}.json";
            
            if (!FileAccess.FileExists(filePath))
            {
                GD.PrintErr($"Save file not found: {filePath}");
                return;
            }
            
            var file = FileAccess.Open(filePath, FileAccess.ModeFlags.Read);
            var json = file.GetAsText();
            file.Close();
            
            var saveData = JsonConvert.DeserializeObject<SaveData>(json);
            
            // 恢复游戏状态
            var playerModel = GetModel<PlayerModel>();
            var gameModel = GetModel<GameModel>();
            
            playerModel.Health.Value = saveData.PlayerHealth;
            playerModel.Score.Value = saveData.PlayerScore;
            gameModel.CurrentLevel.Value = saveData.CurrentLevel;
        }
    }
    
    public class SaveData
    {
        public int PlayerHealth { get; set; }
        public int PlayerScore { get; set; }
        public int CurrentLevel { get; set; }
        public System.DateTime Timestamp { get; set; }
    }
    
    public class SaveGameCommand : AbstractCommand
    {
        public int SlotId { get; set; }
    }
    
    public class LoadGameCommand : AbstractCommand
    {
        public int SlotId { get; set; }
    }
}

3. 添加本地化支持

创建 src/Game.Core/Systems/LocalizationSystem.cs:

using System.Collections.Generic;
using Godot;
using GFramework.Core.system;

namespace MyGFrameworkGame.Core.Systems
{
    public class LocalizationSystem : AbstractSystem
    {
        private Dictionary<string, string> _translations = new();
        private string _currentLanguage = "en";
        
        protected override void OnInit()
        {
            LoadLanguage(_currentLanguage);
        }
        
        public void SetLanguage(string languageCode)
        {
            _currentLanguage = languageCode;
            LoadLanguage(languageCode);
        }
        
        public string GetText(string key)
        {
            return _translations.GetValueOrDefault(key, key);
        }
        
        private void LoadLanguage(string languageCode)
        {
            var translationFile = GD.Load<Translation>($"res://assets/locales/{languageCode}.po");
            if (translationFile != null)
            {
                TranslationServer.SetLocale(languageCode);
                GD.Print($"Loaded language: {languageCode}");
            }
            else
            {
                GD.PrintErr($"Failed to load language: {languageCode}");
            }
        }
    }
}

总结

恭喜!你已经成功创建了一个完整的 GFramework 游戏项目。这个项目包含了:

  • 完整的架构设计 - 使用 GFramework 的五层架构
  • 丰富的游戏功能 - 玩家控制、UI、音效、存档等
  • 完善的测试 - 单元测试和集成测试
  • 自动化构建 - CI/CD 流水线配置

下一步建议

  1. 扩展游戏内容:添加更多敌人、关卡、道具
  2. 优化性能:使用对象池、减少内存分配
  3. 添加网络功能:多人游戏、排行榜
  4. 完善 UI:更丰富的界面和动画
  5. 发布游戏:在各个平台发布你的作品

学习资源

享受游戏开发的乐趣吧!🎮


教程版本: 1.0.0
更新日期: 2026-01-12