GFramework/docs/zh-CN/tutorials/godot-complete-project.md
GeWuYou fb14d7122c docs(style): 更新文档中的命名空间导入格式
- 将所有小写的命名空间导入更正为首字母大写格式
- 统一 GFramework 框架的命名空间引用规范
- 修复 core、ecs、godot 等模块的命名空间导入错误
- 标准化文档示例代码中的 using 语句格式
- 确保所有文档中的命名空间引用保持一致性
- 更新 global using 语句以匹配正确的命名空间格式
2026-03-10 07:18:49 +08:00

21 KiB
Raw Blame History

title, description
title description
Godot 完整项目搭建 从零开始使用 GFramework 构建一个完整的 Godot 游戏项目

Godot 完整项目搭建

学习目标

完成本教程后,你将能够:

  • 在 Godot 项目中集成 GFramework
  • 创建完整的游戏架构
  • 实现场景管理和 UI 系统
  • 使用协程和事件系统
  • 实现游戏存档功能
  • 构建一个可运行的完整游戏

前置条件

项目概述

我们将创建一个简单的 2D 射击游戏,包含以下功能:

  • 主菜单和游戏场景
  • 玩家控制和射击
  • 敌人生成和 AI
  • 分数和生命值系统
  • 游戏存档和加载
  • 暂停菜单

步骤 1创建 Godot 项目并配置

首先创建 Godot 项目并添加 GFramework 依赖。

1.1 创建项目

  1. 打开 Godot创建新项目 "MyShooterGame"
  2. 选择 C# 作为脚本语言
  3. 创建项目后,在项目根目录创建 .csproj 文件

1.2 添加 NuGet 包

编辑 MyShooterGame.csproj

<Project Sdk="Godot.NET.Sdk/4.3.0">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <EnableDynamicLoading>true</EnableDynamicLoading>
    <LangVersion>12</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <!-- GFramework 包 -->
    <PackageReference Include="GFramework.Core" Version="1.0.0" />
    <PackageReference Include="GFramework.Game" Version="1.0.0" />
    <PackageReference Include="GFramework.Godot" Version="1.0.0" />
  </ItemGroup>
</Project>

1.3 创建项目结构

MyShooterGame/
├── Scripts/
│   ├── Architecture/
│   │   └── GameArchitecture.cs
│   ├── Models/
│   │   ├── PlayerModel.cs
│   │   └── GameModel.cs
│   ├── Systems/
│   │   ├── GameplaySystem.cs
│   │   └── SpawnSystem.cs
│   ├── Controllers/
│   │   └── PlayerController.cs
│   └── Data/
│       └── GameSaveData.cs
├── Scenes/
│   ├── Main.tscn
│   ├── Menu.tscn
│   ├── Game.tscn
│   ├── Player.tscn
│   └── Enemy.tscn
└── UI/
    ├── MainMenu.tscn
    ├── HUD.tscn
    └── PauseMenu.tscn

代码说明

  • 使用 Godot.NET.Sdk 4.3.0
  • 添加 GFramework 的三个核心包
  • 按功能组织代码结构

步骤 2创建游戏架构

实现游戏的核心架构和数据模型。

2.1 定义数据模型

// Scripts/Models/PlayerModel.cs
using GFramework.Core.Model;
using GFramework.Core.Abstractions.Property;

namespace MyShooterGame.Models
{
    public class PlayerModel : AbstractModel
    {
        public BindableProperty<int> Health { get; } = new(100);
        public BindableProperty<int> MaxHealth { get; } = new(100);
        public BindableProperty<int> Score { get; } = new(0);
        public BindableProperty<int> Lives { get; } = new(3);
        public BindableProperty<bool> IsAlive { get; } = new(true);

        protected override void OnInit()
        {
            // 监听生命值变化
            Health.RegisterOnValueChanged(health =>
            {
                if (health <= 0)
                {
                    IsAlive.Value = false;
                }
            });
        }

        public void Reset()
        {
            Health.Value = MaxHealth.Value;
            Score.Value = 0;
            Lives.Value = 3;
            IsAlive.Value = true;
        }

        public void TakeDamage(int damage)
        {
            Health.Value = Math.Max(0, Health.Value - damage);
        }

        public void AddScore(int points)
        {
            Score.Value += points;
        }

        public void LoseLife()
        {
            Lives.Value = Math.Max(0, Lives.Value - 1);
            if (Lives.Value > 0)
            {
                Health.Value = MaxHealth.Value;
                IsAlive.Value = true;
            }
        }
    }
}
// Scripts/Models/GameModel.cs
using GFramework.Core.Model;
using GFramework.Core.Abstractions.Property;

namespace MyShooterGame.Models
{
    public class GameModel : AbstractModel
    {
        public BindableProperty<bool> IsPlaying { get; } = new(false);
        public BindableProperty<bool> IsPaused { get; } = new(false);
        public BindableProperty<int> CurrentWave { get; } = new(1);
        public BindableProperty<int> EnemiesAlive { get; } = new(0);
        public BindableProperty<float> GameTime { get; } = new(0f);

        protected override void OnInit()
        {
            // 初始化
        }

        public void StartGame()
        {
            IsPlaying.Value = true;
            IsPaused.Value = false;
            CurrentWave.Value = 1;
            EnemiesAlive.Value = 0;
            GameTime.Value = 0f;
        }

        public void PauseGame()
        {
            IsPaused.Value = true;
        }

        public void ResumeGame()
        {
            IsPaused.Value = false;
        }

        public void EndGame()
        {
            IsPlaying.Value = false;
            IsPaused.Value = false;
        }
    }
}

2.2 定义存档数据

// Scripts/Data/GameSaveData.cs
using GFramework.Game.Abstractions.Data;
using System;

namespace MyShooterGame.Data
{
    public class GameSaveData : IVersionedData
    {
        public int Version { get; set; } = 1;
        public DateTime SaveTime { get; set; }

        // 玩家数据
        public int HighScore { get; set; }
        public int TotalKills { get; set; }
        public float TotalPlayTime { get; set; }

        // 设置
        public float MusicVolume { get; set; } = 0.8f;
        public float SfxVolume { get; set; } = 1.0f;
    }
}

2.3 创建游戏架构

// Scripts/Architecture/GameArchitecture.cs
using GFramework.Godot.Architecture;
using GFramework.Core.Abstractions.Architecture;
using GFramework.Game.Abstractions.Data;
using GFramework.Game.Abstractions.Storage;
using GFramework.Game.Data;
using GFramework.Game.Storage;
using MyShooterGame.Models;
using MyShooterGame.Systems;
using MyShooterGame.Data;
using Godot;

namespace MyShooterGame.Architecture
{
    public class GameArchitecture : AbstractArchitecture
    {
        public static GameArchitecture Interface { get; private set; }

        public GameArchitecture()
        {
            Interface = this;
        }

        protected override void InstallModules()
        {
            GD.Print("=== 初始化游戏架构 ===");

            // 注册存储系统
            var storage = new FileStorage("user://saves");
            RegisterUtility<IFileStorage>(storage);

            // 注册存档仓库
            var saveConfig = new SaveConfiguration
            {
                SaveRoot = "",
                SaveSlotPrefix = "save_",
                SaveFileName = "data.json"
            };
            var saveRepo = new SaveRepository<GameSaveData>(storage, saveConfig);
            RegisterUtility<ISaveRepository<GameSaveData>>(saveRepo);

            // 注册 Model
            RegisterModel(new PlayerModel());
            RegisterModel(new GameModel());

            // 注册 System
            RegisterSystem(new GameplaySystem());
            RegisterSystem(new SpawnSystem());

            GD.Print("游戏架构初始化完成");
        }
    }
}

代码说明

  • PlayerModel 管理玩家状态
  • GameModel 管理游戏状态
  • GameSaveData 定义存档结构
  • GameArchitecture 注册所有组件

步骤 3实现游戏系统

创建游戏逻辑系统。

3.1 游戏逻辑系统

// Scripts/Systems/GameplaySystem.cs
using GFramework.Core.System;
using GFramework.Core.Extensions;
using MyShooterGame.Models;
using Godot;

namespace MyShooterGame.Systems
{
    public class GameplaySystem : AbstractSystem
    {
        public void StartNewGame()
        {
            GD.Print("开始新游戏");

            var playerModel = this.GetModel<PlayerModel>();
            var gameModel = this.GetModel<GameModel>();

            // 重置数据
            playerModel.Reset();
            gameModel.StartGame();
        }

        public void GameOver()
        {
            GD.Print("游戏结束");

            var gameModel = this.GetModel<GameModel>();
            gameModel.EndGame();

            // 保存最高分
            SaveHighScore();
        }

        public void PauseGame()
        {
            var gameModel = this.GetModel<GameModel>();
            gameModel.PauseGame();
            GetTree().Paused = true;
        }

        public void ResumeGame()
        {
            var gameModel = this.GetModel<GameModel>();
            gameModel.ResumeGame();
            GetTree().Paused = false;
        }

        private void SaveHighScore()
        {
            // 实现最高分保存逻辑
        }

        private SceneTree GetTree()
        {
            return (SceneTree)Engine.GetMainLoop();
        }
    }
}

3.2 敌人生成系统

// Scripts/Systems/SpawnSystem.cs
using GFramework.Core.System;
using GFramework.Core.Extensions;
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine;
using MyShooterGame.Models;
using Godot;
using System.Collections.Generic;

namespace MyShooterGame.Systems
{
    public class SpawnSystem : AbstractSystem
    {
        private PackedScene _enemyScene;
        private Node2D _spawnRoot;
        private CoroutineHandle? _spawnCoroutine;

        public void Initialize(Node2D spawnRoot, PackedScene enemyScene)
        {
            _spawnRoot = spawnRoot;
            _enemyScene = enemyScene;
        }

        public void StartSpawning()
        {
            if (_spawnCoroutine.HasValue)
            {
                this.StopCoroutine(_spawnCoroutine.Value);
            }

            _spawnCoroutine = this.StartCoroutine(SpawnEnemiesCoroutine());
        }

        public void StopSpawning()
        {
            if (_spawnCoroutine.HasValue)
            {
                this.StopCoroutine(_spawnCoroutine.Value);
                _spawnCoroutine = null;
            }
        }

        private IEnumerator<IYieldInstruction> SpawnEnemiesCoroutine()
        {
            var gameModel = this.GetModel<GameModel>();

            while (gameModel.IsPlaying.Value)
            {
                // 等待 2 秒
                yield return CoroutineHelper.WaitForSeconds(2.0);

                // 生成敌人
                if (!gameModel.IsPaused.Value)
                {
                    SpawnEnemy();
                }
            }
        }

        private void SpawnEnemy()
        {
            if (_enemyScene == null || _spawnRoot == null)
                return;

            var enemy = _enemyScene.Instantiate<Node2D>();
            _spawnRoot.AddChild(enemy);

            // 随机位置
            var random = new Random();
            enemy.Position = new Vector2(
                random.Next(100, 900),
                -50
            );

            var gameModel = this.GetModel<GameModel>();
            gameModel.EnemiesAlive.Value++;

            GD.Print($"生成敌人,当前数量: {gameModel.EnemiesAlive.Value}");
        }
    }
}

代码说明

  • GameplaySystem 管理游戏流程
  • SpawnSystem 使用协程定时生成敌人
  • 系统之间通过 Model 共享数据

步骤 4创建玩家控制器

实现玩家的移动和射击。

// Scripts/Controllers/PlayerController.cs
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.Extensions;
using GFramework.SourceGenerators.Abstractions.Rule;
using MyShooterGame.Architecture;
using MyShooterGame.Models;
using Godot;

namespace MyShooterGame.Controllers
{
    [ContextAware]
    public partial class PlayerController : CharacterBody2D, IController
    {
        [Export] public float Speed = 300f;
        [Export] public PackedScene BulletScene;

        private float _shootCooldown = 0f;
        private const float ShootInterval = 0.2f;

        public override void _Ready()
        {
            // 监听玩家死亡(使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口))
            var playerModel = this.GetModel<PlayerModel>();
            playerModel.IsAlive.RegisterOnValueChanged(isAlive =>
            {
                if (!isAlive)
                {
                    OnPlayerDied();
                }
            });
        }

        public override void _Process(double delta)
        {
            _shootCooldown -= (float)delta;

            // 射击
            if (Input.IsActionPressed("shoot") && _shootCooldown <= 0)
            {
                Shoot();
                _shootCooldown = ShootInterval;
            }
        }

        public override void _PhysicsProcess(double delta)
        {
            // 移动
            var velocity = Vector2.Zero;

            if (Input.IsActionPressed("move_left"))
                velocity.X -= 1;
            if (Input.IsActionPressed("move_right"))
                velocity.X += 1;
            if (Input.IsActionPressed("move_up"))
                velocity.Y -= 1;
            if (Input.IsActionPressed("move_down"))
                velocity.Y += 1;

            Velocity = velocity.Normalized() * Speed;
            MoveAndSlide();

            // 限制在屏幕内
            var screenSize = GetViewportRect().Size;
            Position = new Vector2(
                Mathf.Clamp(Position.X, 0, screenSize.X),
                Mathf.Clamp(Position.Y, 0, screenSize.Y)
            );
        }

        private void Shoot()
        {
            if (BulletScene == null)
                return;

            var bullet = BulletScene.Instantiate<Node2D>();
            GetParent().AddChild(bullet);
            bullet.GlobalPosition = GlobalPosition + new Vector2(0, -20);

            GD.Print("发射子弹");
        }

        public void TakeDamage(int damage)
        {
            var playerModel = this.GetModel<PlayerModel>();
            playerModel.TakeDamage(damage);

            GD.Print($"玩家受伤,剩余生命: {playerModel.Health.Value}");
        }

        private void OnPlayerDied()
        {
            GD.Print("玩家死亡");

            var playerModel = this.GetModel<PlayerModel>();
            playerModel.LoseLife();

            if (playerModel.Lives.Value > 0)
            {
                // 重生
                Position = new Vector2(400, 500);
            }
            else
            {
                // 游戏结束
                var gameplaySystem = this.GetSystem<GameplaySystem>();
                gameplaySystem.GameOver();
            }
        }
    }
}

代码说明

  • 实现 IController 接口访问架构
  • 使用 Godot 的输入系统
  • 通过 Model 更新游戏状态
  • 监听属性变化响应事件

步骤 5创建游戏场景

5.1 主场景 (Main.tscn)

创建主场景并添加架构初始化脚本:

// Scripts/Main.cs
using Godot;
using MyShooterGame.Architecture;

public partial class Main : Node
{
    private GameArchitecture _architecture;

    public override async void _Ready()
    {
        GD.Print("初始化游戏");

        // 创建并初始化架构
        _architecture = new GameArchitecture();
        await _architecture.InitializeAsync();

        GD.Print("架构初始化完成,切换到菜单");

        // 加载菜单场景
        GetTree().ChangeSceneToFile("res://Scenes/Menu.tscn");
    }
}

5.2 菜单场景 (Menu.tscn)

创建菜单UI并添加控制脚本

// Scripts/UI/MenuController.cs
using Godot;
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.Extensions;
using GFramework.SourceGenerators.Abstractions.Rule;
using MyShooterGame.Architecture;
using MyShooterGame.Systems;

[ContextAware]
public partial class MenuController : Control, IController
{
    public override void _Ready()
    {
        // 连接按钮信号
        GetNode<Button>("VBoxContainer/StartButton").Pressed += OnStartPressed;
        GetNode<Button>("VBoxContainer/QuitButton").Pressed += OnQuitPressed;
    }

    private void OnStartPressed()
    {
        GD.Print("开始游戏");

        // 初始化游戏(使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口))
        var gameplaySystem = this.GetSystem<GameplaySystem>();
        gameplaySystem.StartNewGame();

        // 切换到游戏场景
        GetTree().ChangeSceneToFile("res://Scenes/Game.tscn");
    }

    private void OnQuitPressed()
    {
        GetTree().Quit();
    }
}

5.3 游戏场景 (Game.tscn)

创建游戏场景并添加控制脚本:

// Scripts/GameScene.cs
using Godot;
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.Abstractions.Architecture;
using GFramework.Core.Extensions;
using GFramework.SourceGenerators.Abstractions.Rule;
using MyShooterGame.Architecture;
using MyShooterGame.Systems;
using MyShooterGame.Models;

[ContextAware]
public partial class GameScene : Node2D, IController
{
    [Export] public PackedScene EnemyScene;

    private SpawnSystem _spawnSystem;

    public override void _Ready()
    {
        // 初始化生成系统(使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口))
        _spawnSystem = this.GetSystem<SpawnSystem>();
        _spawnSystem.Initialize(this, EnemyScene);
        _spawnSystem.StartSpawning();

        // 监听游戏状态
        var gameModel = this.GetModel<GameModel>();
        gameModel.IsPlaying.RegisterOnValueChanged(isPlaying =>
        {
            if (!isPlaying)
            {
                OnGameOver();
            }
        });

        GD.Print("游戏场景已加载");
    }

    public override void _Process(double delta)
    {
        // 更新游戏时间
        var gameModel = this.GetModel<GameModel>();
        if (gameModel.IsPlaying.Value && !gameModel.IsPaused.Value)
        {
            gameModel.GameTime.Value += (float)delta;
        }

        // 暂停
        if (Input.IsActionJustPressed("ui_cancel"))
        {
            TogglePause();
        }
    }

    private void TogglePause()
    {
        var gameplaySystem = this.GetSystem<GameplaySystem>();
        var gameModel = this.GetModel<GameModel>();

        if (gameModel.IsPaused.Value)
        {
            gameplaySystem.ResumeGame();
        }
        else
        {
            gameplaySystem.PauseGame();
        }
    }

    private void OnGameOver()
    {
        GD.Print("游戏结束,返回菜单");
        _spawnSystem.StopSpawning();

        // 延迟返回菜单
        GetTree().CreateTimer(2.0).Timeout += () =>
        {
            GetTree().ChangeSceneToFile("res://Scenes/Menu.tscn");
        };
    }
}

代码说明

  • Main 初始化架构
  • MenuController 处理菜单交互
  • GameScene 管理游戏场景
  • 所有脚本通过架构访问系统和模型

完整代码

项目结构和所有代码文件已在上述步骤中提供。

运行结果

运行游戏后,你将看到:

  1. 启动

    • 架构初始化
    • 自动进入主菜单
  2. 主菜单

    • 显示开始和退出按钮
    • 点击开始进入游戏
  3. 游戏场景

    • 玩家可以移动和射击
    • 敌人定时生成
    • HUD 显示分数和生命值
    • 按 ESC 暂停游戏
  4. 游戏结束

    • 玩家生命值为 0 时游戏结束
    • 显示最终分数
    • 自动返回主菜单

验证步骤

  1. 架构正确初始化
  2. 场景切换正常
  3. 玩家控制响应
  4. 敌人生成系统工作
  5. 数据模型正确更新
  6. 暂停功能正常

下一步

恭喜!你已经完成了一个基础的 Godot 游戏项目。接下来可以:

  • 添加更多游戏功能(道具、关卡等)
  • 实现完整的 UI 系统
  • 添加音效和音乐
  • 优化性能和体验
  • 发布到不同平台

相关文档