ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Godot 4 C# 游戏开发实战:从脚本编程到项目打包全流程

Godot 4 C# 游戏开发实战:从脚本编程到项目打包全流程 在上一篇文章中我们搭建了 Godot 4 的 C# 开发环境并初步探索了引擎界面和 GDScript 脚本。本篇我们将深入核心聚焦于 C# 脚本编程、节点系统与物理交互并通过七个由浅入深的实战项目带你从零到一掌握 Godot 4 与 C# 的 2D 游戏开发全流程。无论你是 Unity 开发者转型还是 C# 初学者入门游戏开发这篇万字长文都将提供一条清晰的实践路径。1. 环境准备与项目创建回顾在开始编码之前确保你的开发环境已就绪。Godot 4 对 .NET 的支持已非常成熟但正确的配置是成功的第一步。1.1 环境确认Godot 4 版本建议使用最新的稳定版如 4.2.1。在启动器或官网下载时请选择标有“.NET”或“Mono”的版本这是支持 C# 的必要条件。.NET SDK需要安装 .NET 6.0 或 .NET 8.0 SDK。你可以在命令行输入dotnet --version来验证。Godot 4.2 通常与 .NET 8.0 兼容性更好。IDE虽然 Godot 内置编辑器可用但为了获得更好的 C# 代码补全、调试和重构体验强烈推荐使用Visual Studio 2022社区版免费或JetBrains Rider。确保安装了相关的 Godot 插件或支持。1.2 创建你的第一个 C# 项目打开 Godot 4点击“新建项目”。输入项目名称和路径渲染器选择“兼容性”更稳定或“移动端”均可。关键步骤在“项目创建”对话框的底部找到并勾选“使用 .NET”选项。点击“创建并编辑”Godot 会自动生成一个带有.csproj文件的项目结构。创建成功后在“场景”面板中你会看到一个默认的Node2D根节点。右键点击它选择“附加脚本”在语言下拉菜单中务必选择“C#”然后创建脚本。这将是你的第一个 C# 脚本文件如Main.cs。2. Godot C# 脚本编程核心Godot 的 C# API 与 GDScript 在概念上高度对应但语法遵循 C# 规范。理解其核心模式至关重要。2.1 脚本基本结构一个典型的 Godot C# 脚本继承自 Godot 的节点类。// 文件路径src/MainCharacter.cs using Godot; // 引入 Godot 命名空间 // 继承自 CharacterBody2D用于处理2D角色物理运动 public partial class MainCharacter : CharacterBody2D { // 通过 [Export] 属性将变量暴露到编辑器面板方便调整 [Export] public int Speed { get; set; } 300; // 在节点进入场景树时调用用于初始化 public override void _Ready() { GD.Print(角色节点已就绪); } // 每帧调用用于处理物理过程。delta 是上一帧到当前帧的时间间隔 public override void _PhysicsProcess(double delta) { // 1. 获取输入 Vector2 inputDirection Input.GetVector(ui_left, ui_right, ui_up, ui_down); // 2. 计算速度 Velocity inputDirection * Speed; // 3. 执行移动并处理碰撞 MoveAndSlide(); } }关键点解析partial classGodot 会自动生成部分类代码来与引擎内部连接。[Export]这是 Godot C# 中最重要的特性之一。它将 C# 属性或字段公开到编辑器的“检查器”面板实现无需修改代码即可调整参数如速度、生命值。_Ready()类比 Unity 的Start()用于一次性初始化。_PhysicsProcess(double delta)在固定的物理帧率下调用默认60Hz所有与物理、移动相关的代码都应放在这里。delta用于实现帧率无关的运动。MoveAndSlide()CharacterBody2D类的方法用于移动角色并自动处理沿碰撞体表面的滑动是制作平台游戏、顶视角角色的核心。2.2 节点与信号Godot 的核心架构Godot 采用严格的场景-节点树结构。C# 脚本通过操作节点来实现功能。获取节点// 方法1使用 GetNodeT 路径获取强类型推荐 private Sprite2D _mySprite; private Timer _attackTimer; public override void _Ready() { // 获取当前节点下名为“Sprite2D”的子节点 _mySprite GetNodeSprite2D(Sprite2D); // 获取位于“Timers”子节点下的“AttackTimer”节点 _attackTimer GetNodeTimer(Timers/AttackTimer); } // 方法2使用特性简化需Godot 4.2并在编辑器赋值 [Export] private Sprite2D MySprite { get; set; } [Export] private Timer AttackTimer { get; set; } // 在编辑器检查器中将对应的节点拖拽到这些属性上即可完成关联。连接信号 信号是 Godot 实现节点间松耦合通信的利器。例如连接按钮的按下信号。// 文件路径src/UI/StartButton.cs using Godot; public partial class StartButton : Button { public override void _Ready() { // 将按钮的“Pressed”信号连接到本脚本的 OnButtonPressed 方法 Pressed OnButtonPressed; } private void OnButtonPressed() { GD.Print(游戏开始); GetTree().ChangeSceneToFile(res://Levels/MainLevel.tscn); } }你也可以在编辑器中可视化地连接信号代码会自动生成。C# 中的信号连接使用操作符非常直观。3. 实战项目一可移动角色与基础碰撞目标创建一个可由键盘控制移动的方形角色并与场景中的墙壁发生碰撞。场景搭建创建新场景根节点为CharacterBody2D命名为Player。为Player添加一个CollisionShape2D子节点并为其形状分配一个RectangleShape2D调整大小代表角色。添加一个Sprite2D子节点为其纹理分配一个简单的颜色矩形图或图片。编写玩家脚本// 文件路径src/Player.cs using Godot; public partial class Player : CharacterBody2D { [Export] public int Speed { get; set; } 200; public override void _PhysicsProcess(double delta) { Vector2 inputDirection Input.GetVector(move_left, move_right, move_up, move_down); Velocity inputDirection * Speed; MoveAndSlide(); } }配置输入映射进入“项目” - “项目设置” - “输入映射”。添加“move_left”、“move_right”、“move_up”、“move_down”四个动作。为它们分别绑定键盘按键如 A/D/W/S 或方向键。创建墙壁创建新场景根节点为StaticBody2D命名为Wall。添加CollisionShape2D并设置矩形形状。在主场景中实例化多个Wall节点摆放在角色周围。运行测试运行场景控制角色移动应能顺利与墙壁碰撞并停止。4. 实战项目二平台跳跃与重力模拟目标实现一个带有重力、跳跃和二段跳能力的经典平台游戏角色。场景与节点同上一个项目但角色碰撞体形状可以调整为胶囊形更合适。编写平台角色脚本// 文件路径src/PlatformerPlayer.cs using Godot; public partial class PlatformerPlayer : CharacterBody2D { [Export] public float Speed { get; set; } 300.0f; [Export] public float JumpVelocity { get; set; } -400.0f; [Export] public float DoubleJumpVelocity { get; set; } -300.0f; // 获取重力值从项目设置中读取 private float _gravity ProjectSettings.GetSetting(physics/2d/default_gravity).AsSingle(); private bool _canDoubleJump false; public override void _PhysicsProcess(double delta) { Vector2 velocity Velocity; // 1. 应用重力如果不在空中则重置重力 if (!IsOnFloor()) { velocity.Y _gravity * (float)delta; } else { _canDoubleJump true; // 落地后重置二段跳能力 } // 2. 处理跳跃 if (Input.IsActionJustPressed(ui_accept)) // 假设跳跃键为空格 { if (IsOnFloor()) { velocity.Y JumpVelocity; } else if (_canDoubleJump) { velocity.Y DoubleJumpVelocity; _canDoubleJump false; } } // 3. 处理水平移动 float direction Input.GetAxis(move_left, move_right); if (direction ! 0) { velocity.X direction * Speed; } else { // 简单的地面摩擦力模拟 velocity.X Mathf.MoveToward(Velocity.X, 0, Speed * (float)delta * 5); } Velocity velocity; MoveAndSlide(); } }创建平台使用StaticBody2D或AnimatableBody2D创建平台。确保平台的CollisionShape2D足够宽并且法线方向向上。关键概念IsOnFloor()CharacterBody2D的方法用于检测角色是否站在碰撞体上。Input.IsActionJustPressed()在动作被按下的那一帧返回true非常适合跳跃、攻击等一次性输入。5. 实战项目三发射子弹与 Area2D 交互目标实现角色发射子弹子弹击中敌人Area2D后双方消失并触发效果。子弹场景根节点为Area2D命名为Bullet。添加Sprite2D子弹图片和CollisionShape2D圆形作为子节点。为Area2D附加脚本。子弹脚本// 文件路径src/Bullet.cs using Godot; public partial class Bullet : Area2D { [Export] public float Speed { get; set; } 500.0f; [Export] public Vector2 Direction { get; set; } Vector2.Right; public override void _Ready() { // 子弹在2秒后自动销毁防止内存泄漏 Timer lifespanTimer new Timer(); AddChild(lifespanTimer); lifespanTimer.WaitTime 2.0; lifespanTimer.Timeout QueueFree; // 超时后释放节点 lifespanTimer.Start(); } public override void _PhysicsProcess(double delta) { // 沿方向移动 Position Direction * Speed * (float)delta; } // 当子弹进入另一个 Area2D 或 PhysicsBody2D 时触发 private void OnBodyEntered(Node2D body) { GD.Print($子弹击中了{body.Name}); // 触发击中效果如播放动画、音效 // ... QueueFree(); // 销毁子弹自身 if (body is Enemy enemy) // 如果击中的是敌人 { enemy.TakeDamage(1); } } }在编辑器中将Area2D的body_entered信号连接到脚本的OnBodyEntered方法。敌人场景根节点为Area2D或CharacterBody2D命名为Enemy。添加精灵和碰撞形状。编写敌人脚本包含TakeDamage(int damage)方法。玩家发射逻辑在玩家脚本中监听攻击按键实例化子弹并设置其方向和位置。if (Input.IsActionJustPressed(shoot)) { PackedScene bulletScene GD.LoadPackedScene(res://Scenes/Bullet.tscn); Bullet bullet bulletScene.InstantiateBullet(); bullet.GlobalPosition GlobalPosition new Vector2(50, 0); // 从角色右侧发射 bullet.Direction Vector2.Right; GetParent().AddChild(bullet); // 通常添加到当前场景根节点下 }6. 实战项目四UI 系统构建 - 生命值与分数目标创建显示玩家生命值和游戏分数的 UI 界面并实现数据更新。UI 场景创建新场景根节点为CanvasLayer确保 UI 显示在最前。添加Control节点作为容器然后添加Label节点显示分数HBoxContainer节点配合多个TextureRect节点显示生命值图标。UI 脚本// 文件路径src/UI/GameUI.cs using Godot; public partial class GameUI : CanvasLayer { [Export] private Label _scoreLabel; [Export] private HBoxContainer _healthContainer; private int _score 0; private int _health 3; private Texture2D _heartFull; private Texture2D _heartEmpty; public override void _Ready() { // 加载纹理资源 _heartFull GD.LoadTexture2D(res://Assets/UI/heart_full.png); _heartEmpty GD.LoadTexture2D(res://Assets/UI/heart_empty.png); UpdateUI(); } public void AddScore(int points) { _score points; _scoreLabel.Text $分数: {_score}; } public void UpdateHealth(int newHealth) { _health Mathf.Clamp(newHealth, 0, _healthContainer.GetChildCount()); for (int i 0; i _healthContainer.GetChildCount(); i) { TextureRect heart _healthContainer.GetChildTextureRect(i); heart.Texture (i _health) ? _heartFull : _heartEmpty; } } private void UpdateUI() { _scoreLabel.Text $分数: {_score}; UpdateHealth(_health); } }全局访问可以使用Autoload单例或信号来让游戏中的其他节点如玩家、敌人更新 UI。方法一信号在玩家脚本中定义HealthChanged信号UI 脚本订阅该信号。方法二Autoload将GameUI脚本添加到“项目设置”-“Autoload”中命名为UI这样在任何脚本中都可以通过UI.AddScore(10)直接调用。7. 实战项目五动画状态机与 AnimatedSprite2D目标为角色创建 idle待机、run奔跑、jump跳跃的动画并通过代码控制切换。准备精灵图需要一张包含角色所有动画帧的精灵图集Sprite Sheet或单独的帧图片。创建 AnimationPlayer在角色场景中添加AnimatedSprite2D节点。在检查器中点击“SpriteFrames”属性打开动画编辑器。创建三个动画“idle”、“run”、“jump”。为每个动画添加对应的帧并设置播放速度FPS。编写动画控制逻辑在角色脚本中根据状态播放对应动画。// 在角色脚本如 PlatformerPlayer.cs中添加 [Export] private AnimatedSprite2D _animatedSprite; public override void _PhysicsProcess(double delta) { // ... (原有的移动和物理计算逻辑) // 动画控制 if (!IsOnFloor()) { _animatedSprite.Play(jump); } else { if (Mathf.Abs(Velocity.X) 0.1f) { _animatedSprite.Play(run); // 根据移动方向翻转精灵 _animatedSprite.FlipH Velocity.X 0; } else { _animatedSprite.Play(idle); } } }AnimationPlayer节点功能更强大可以控制多个属性的动画但AnimatedSprite2D对于简单的帧动画更加轻便直接。8. 实战项目六场景管理与全局数据持久化目标实现游戏开始菜单、主关卡、游戏结束场景之间的切换并保存最高分。场景切换// 切换到主关卡 GetTree().ChangeSceneToFile(res://Levels/MainLevel.tscn); // 带过渡效果的切换需使用 SceneTreeTween 或自定义逻辑 // 例如可以先播放淡出动画然后在动画完成时切换场景。使用 Autoload 作为游戏管理器创建一个名为GameManager.cs的脚本。在“项目设置”-“Autoload”中将其添加为单例路径为res://src/GameManager.cs名称设为GameManager。// 文件路径src/GameManager.cs using Godot; public partial class GameManager : Node { public int HighScore { get; private set; } public int CurrentScore { get; set; } public static GameManager Instance { get; private set; } public override void _Ready() { // 简单的单例模式确保只有一个实例 if (Instance ! null Instance ! this) { QueueFree(); return; } Instance this; LoadHighScore(); } public void SaveHighScore() { using var config new ConfigFile(); config.SetValue(Game, HighScore, HighScore); config.Save(user://game_save.cfg); } private void LoadHighScore() { using var config new ConfigFile(); Error err config.Load(user://game_save.cfg); if (err Error.Ok) { HighScore (int)config.GetValue(Game, HighScore, 0); } } public void CheckAndUpdateHighScore() { if (CurrentScore HighScore) { HighScore CurrentScore; SaveHighScore(); } } }在任何脚本中都可以通过GameManager.Instance.CurrentScore 100;来修改和访问全局数据。9. 实战项目七打包与发布目标将完成的游戏导出为可执行文件分享给他人。配置导出预设进入“项目” - “导出...”。点击“添加...”选择目标平台如“Windows 桌面”。在“资源”选项卡通常选择“导出所有资源”。在“功能”选项卡可以设置应用图标、名称等。对于 .NET 项目在“文件系统”选项卡确保包含所有必要的.dll文件Godot 通常会自动处理。处理 .NET 依赖Godot 的 .NET 导出会将运行时和依赖项打包。确保在导出预设的“架构”中选择正确的目标如 x86_64。如果使用了第三方 NuGet 包需要确保它们支持目标平台并且导出时包含。执行导出点击“导出项目”选择一个输出目录和文件名如MyGame.exe。Godot 会生成一个可执行文件和一个MyGame.pck数据文件或整合成一个文件。测试在非开发环境的电脑上运行导出的可执行文件确保所有功能正常。10. 常见问题与排查思路在 Godot 4 C# 开发中你可能会遇到以下典型问题问题现象常见原因解决思路C# 脚本无法附加或编译错误1. 项目创建时未勾选“.NET”。2. .NET SDK 未安装或版本不匹配。3. IDE 的 Godot 插件未正确配置。1. 检查项目根目录是否有.csproj文件。2. 命令行运行dotnet --version确认。3. 重启 Godot 和 IDE检查输出面板的错误信息。[Export]变量在编辑器中不显示1. 变量不是public属性或字段。2. 脚本编译错误。3. 使用了不支持的复杂类型。1. 确保是public且带有{ get; set; }。2. 修复所有编译错误。3. 初始只使用基础类型int, float, string, NodePath。角色移动时抖动或穿墙1. 移动逻辑写在_Process而非_PhysicsProcess中。2. 碰撞形状未正确对齐或设置。3. 帧率不稳定。1.所有物理和移动代码务必放在_PhysicsProcess。2. 在编辑器中仔细调整碰撞形状。3. 使用delta进行与帧率无关的移动计算。信号连接后不触发1. 连接的目标方法签名不匹配。2. 节点在连接信号时尚未就绪。3. 连接的节点路径错误或为空。1. 确保方法参数与信号定义一致可在编辑器中连接生成模板。2. 在_Ready或之后进行连接。3. 使用GetNode或[Export]确保正确引用节点。导出后游戏运行崩溃1. 缺少动态链接库DLL。2. 使用了特定平台的 API。3. 资源路径错误。1. 检查导出日志确保所有.dll已包含。2. 使用OS.GetName()进行平台判断。3. 使用res://相对路径避免绝对路径。11. 最佳实践与工程建议项目组织清晰的目录结构创建如Scenes/、Scripts/、Assets/Sprites/、Assets/Audio/、Assets/UI/等文件夹。命名规范场景文件用PascalCase.tscn脚本用PascalCase.cs节点在场景树中也使用清晰的名字。代码质量善用[Export]将需要调整的数值速度、血量、伤害暴露给编辑器提高迭代效率。使用信号解耦避免节点间直接强引用。玩家受伤时发出HealthChanged信号由 UI 或其他系统监听而不是让玩家直接调用 UI 的方法。资源预加载对于频繁使用的场景如子弹、敌人使用GD.LoadPackedScene在_Ready中预加载避免运行时卡顿。合理使用Autoload用于全局管理器游戏状态、音效、存档但不要滥用避免变成“上帝对象”。性能考量节点数量Godot 节点虽轻量但数量过多如数千个仍会影响性能。对于大量重复对象子弹、粒子考虑使用MultiMeshInstance2D或自定义绘制。物理层优化合理使用碰撞层Layer和掩码Mask减少不必要的碰撞检测计算。纹理图集将多个小纹理打包成图集可以减少绘制调用提升渲染性能。C# 特有技巧使用using语句对于实现IDisposable的对象如FileAccess使用using确保资源释放。异步支持Godot 4 C# 支持async/await可以优雅地处理加载、延时等操作。public async void PerformAttack() { PlayAnimation(Attack); await ToSignal(GetTree().CreateTimer(0.5), SceneTreeTimer.SignalName.Timeout); SpawnProjectile(); }通过这七个实战项目的循序渐进的练习你应该已经对 Godot 4 的 C# 开发核心流程有了扎实的体验。从节点操作、物理交互、UI 构建到场景管理和最终打包这些项目覆盖了 2D 游戏开发的基础闭环。记住理解引擎的节点和信号设计哲学比记忆 API 更重要。接下来你可以尝试将这些模块组合起来添加更多游戏机制如敌人 AI、道具系统、关卡设计打造属于你自己的完整游戏。Godot 官方文档和活跃的社区是你继续探索的强大后盾。
返回列表