当前位置: 首页 > news >正文

Godot实战(一)—— 用C#构建2D躲避游戏的核心机制

1. 环境准备与项目初始化

第一次打开Godot引擎时,那个简洁的界面可能会让你有点不知所措。别担心,我们一步步来。点击"New Project"按钮,给你的游戏项目起个名字,比如"DodgeTheCreeps"。建议专门创建一个空文件夹来存放项目文件,这样管理起来更方便。

在项目设置中,有几个关键参数需要调整:

  • 进入Project -> Project Settings
  • 找到Display -> Window选项卡
  • 将Width设为480,Height设为720
  • 在Stretch选项中,Mode选择2d,Aspect选择keep

这些设置确保了游戏在不同尺寸的屏幕上都能保持正确的比例。我刚开始用Godot时经常忽略这个步骤,结果游戏在不同设备上显示效果千奇百怪,后来才发现是这里没设置好。

2. 创建玩家角色

2.1 构建玩家场景

在Scene面板中,我们首先需要创建一个Area2D节点作为玩家角色的根节点。为什么选择Area2D而不是Sprite?因为Area2D可以检测碰撞,这对我们的躲避游戏至关重要。

接下来添加这些子节点:

  1. AnimatedSprite - 用于显示角色动画
  2. CollisionShape2D - 定义碰撞区域

设置AnimatedSprite时,我建议先准备好角色素材。你可以自己绘制简单的角色动画,或者使用网上找到的免费素材。记得把Scale设为(0.5, 0.5),这样角色大小更合适。

2.2 编写玩家移动代码

玩家控制是游戏的核心,我们使用C#来实现。创建一个Player.cs脚本,核心代码如下:

[Export] public int Speed = 400; // 玩家移动速度 public override void _Process(double delta) { var velocity = Vector2.Zero; if (Input.IsActionPressed("move_right")) velocity.X += 1; if (Input.IsActionPressed("move_left")) velocity.X -= 1; if (Input.IsActionPressed("move_down")) velocity.Y += 1; if (Input.IsActionPressed("move_up")) velocity.Y += 1; if (velocity.Length() > 0) { velocity = velocity.Normalized() * Speed; GetNode<AnimatedSprite>("AnimatedSprite").Play(); } else { GetNode<AnimatedSprite>("AnimatedSprite").Stop(); } Position += velocity * (float)delta; Position = new Vector2( Mathf.Clamp(Position.x, 0, ScreenSize.x), Mathf.Clamp(Position.y, 0, ScreenSize.y) ); }

这段代码实现了基本的WASD或方向键控制。Normalized()确保斜向移动时速度不会叠加,Clamp()则防止角色跑出屏幕。

3. 敌人生成系统

3.1 创建敌人场景

敌人我们使用RigidBody2D作为根节点,这样可以利用物理引擎的特性。关键设置包括:

  • 将Gravity Scale设为0,敌人就不会下落
  • 取消勾选Mask属性的第一个框,防止敌人互相碰撞
  • 添加VisibilityNotifier2D,用于敌人离开屏幕时自动销毁

敌人的动画可以多样化一些,比如添加fly、swim、walk三种动画类型。在代码中随机选择一种:

public override void _Ready() { var animSprite = GetNode<AnimatedSprite>("AnimatedSprite"); string[] mobTypes = animSprite.Frames.GetAnimationNames(); animSprite.Animation = mobTypes[GD.Randi() % mobTypes.Length]; }

3.2 敌人生成逻辑

在主场景中,我们使用Path2D和PathFollow2D来生成敌人。这个设计很巧妙,Path2D定义生成路径,PathFollow2D可以在路径上随机定位。

关键代码:

public void OnMobTimerTimeout() { var mob = (Mob)MobScene.Instance(); var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation"); mobSpawnLocation.Offset = GD.Randi(); float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2; direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4); mob.Position = mobSpawnLocation.Position; mob.Rotation = direction; var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0); mob.LinearVelocity = velocity.Rotated(direction); AddChild(mob); }

这里有几个值得注意的点:

  1. 使用GD.Randi()实现随机位置
  2. 方向计算考虑了路径的切线方向
  3. 速度也加入了随机性,让游戏更有趣

4. 碰撞检测与游戏逻辑

4.1 玩家碰撞处理

当敌人碰到玩家时,我们需要触发游戏逻辑。在Player.cs中添加:

[Signal] public delegate void Hit(); public void OnPlayerBodyEntered(PhysicsBody2D body) { Hide(); EmitSignal(nameof(Hit)); GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true); }

这里使用了Godot的信号系统,当碰撞发生时发出Hit信号。SetDeferred确保在物理回调中安全地修改碰撞属性。

4.2 游戏状态管理

主场景需要管理游戏状态,包括开始、结束和计分:

public void NewGame() { Score = 0; var player = GetNode<Player>("Player"); player.Start(GetNode<Position2D>("StartPosition").Position); GetNode<Timer>("StartTimer").Start(); GetTree().CallGroup("mobs", "queue_free"); } public void GameOver() { GetNode<Timer>("MobTimer").Stop(); GetNode<Timer>("ScoreTimer").Stop(); }

NewGame()会重置分数、玩家位置,并清除所有现存敌人。GameOver()则停止所有计时器。

5. 用户界面实现

5.1 HUD设计

使用CanvasLayer创建游戏UI,包含以下元素:

  • 分数显示(ScoreLabel)
  • 游戏消息(Message)
  • 开始按钮(StartButton)

关键功能实现:

public async void ShowGameOver() { ShowMessage("Game Over"); await ToSignal(GetNode<Timer>("MessageTimer"), "timeout"); GetNode<Label>("Message").Text = "Dodge the\nCreeps!"; GetNode<Label>("Message").Show(); await ToSignal(GetTree().CreateTimer(1), "timeout"); GetNode<Button>("StartButton").Show(); }

这个异步方法实现了游戏结束时的UI流程:显示"Game Over" -> 短暂等待 -> 显示标题 -> 显示开始按钮。

5.2 分数系统

分数系统由ScoreTimer驱动,每秒增加1分:

public void OnScoreTimerTimeout() { Score++; GetNode<HUD>("HUD").UpdateScore(Score); }

HUD的UpdateScore方法简单更新Label文本:

public void UpdateScore(int score) { GetNode<Label>("ScoreLabel").Text = score.ToString(); }

6. 游戏测试与优化

完成所有功能后,我们需要进行测试。点击编辑器右上角的"Play"按钮,选择Main.tscn作为主场景。测试时注意以下几点:

  1. 玩家移动是否流畅
  2. 敌人生成位置是否合理
  3. 碰撞检测是否准确
  4. 游戏状态转换是否正确

如果发现敌人生成太密集,可以调整MobTimer的Wait Time属性。游戏难度也可以通过修改敌人速度范围来调整。

7. 常见问题解决

在实际开发中,我遇到过几个典型问题:

  1. C#脚本修改后不生效这是因为Godot需要重新构建项目程序集。点击编辑器右上方的Build按钮,或者使用底部的MSBuild工具。

  2. 信号连接失败C#中信号连接要特别注意方法签名匹配。如果遇到问题,可以尝试手动连接而不是依赖自动生成。

  3. 性能问题当敌人数量很多时,可能会影响性能。解决方案是:

    • 优化碰撞形状,使用简单几何体
    • 限制同时存在的敌人数量
    • 使用对象池技术重用敌人实例
  4. 跨平台问题如果计划发布到移动设备,需要考虑触控输入。可以添加虚拟摇杆或者将屏幕分为左右区域分别控制移动方向。

8. 扩展思路

基础版本完成后,可以考虑添加更多功能:

  1. 多种敌人类型不同敌人可以有不同速度、大小和分数价值。

  2. 道具系统添加临时加速、无敌等道具丰富游戏性。

  3. 音效和背景音乐Godot的AudioStreamPlayer很容易实现音效。

  4. 存档系统使用Godot的ConfigFile保存最高分等数据。

  5. 粒子效果玩家被击中或获得道具时添加视觉效果。

实现这些扩展时,建议保持代码模块化。比如把道具系统单独做成一个场景和脚本,这样不会影响现有功能的稳定性。

http://www.jsqmd.com/news/843246/

相关文章:

  • 海思SS928评估板开发全流程:从环境搭建到外设测试
  • 当RRT*遇见CNN:一份给路径规划新手的‘开箱即用’指南与避坑心得
  • AI为编程赋能增效:从“古法编程”到氛围编程的范式革命
  • 工业算力服务器一体机:智能制造的硬核算力底座
  • 别再死记硬背了!用STM32CubeMX配置GPIO,搞懂上拉下拉和推挽开漏到底怎么选
  • 植物树枝叶片果实检测数据集7220张VOC+YOLO格式
  • msvcrt库在pycharm中运行监控键盘操作无效解决办法
  • 别再只做毕设了!用ESP32+云平台DIY智能家居环境监测,实时提醒还能远程控制
  • OpenFOAM安装后必做的5件事:从图形界面到多版本切换(Win10/11通用)
  • 从特征稀缺到精准定位:基于HS-FPN与可变形注意力的白细胞检测新范式
  • 告别命令行!ESP32安全启动V2的图形化实战:Flash下载工具配置Secure Boot全记录
  • Linux Idle 调度器的 cpuidle_reflect:Idle 状态统计更新
  • 【Git】常用命令:commit提交,push推送,merge,branch添加分支
  • 利用taotoken为开源ai agent项目hermes提供稳定后端
  • C++ TinyWebServer实战:手把手教你用有限状态机解析HTTP请求(附正则表达式避坑指南)
  • FPGA+DDR3+千兆以太网:构建实时高清图像传输与显示系统(附源码)
  • 2026.5.18-要闻
  • 法学论文降AI工具免费推荐:2026年法学毕业论文知网AIGC超标免费4.8元一次过完整方案
  • MATLAB单双目标定实战:逐图解析重投影误差的提取与评估
  • Equalizer APO完整指南:免费系统级音频均衡器从零开始
  • SaaS ERP和传统ERP,到底差在哪?
  • LangGraph入门:构建有状态的AI Agent工作流
  • 外部半流式图算法:大规模图数据处理新突破
  • ArkTS 的 @StorageLink 和 @StorageProp,我混用了两周才发现区别在哪
  • Linux Ext 调度器核心原理:BPF 驱动的自定义调度革命
  • 高层次综合设计算法-常见问题记录(一)
  • 3个让你工作效率翻倍的macOS窗口管理技巧:Topit如何解决多任务处理的烦恼
  • 从密码学RSA到区块链:二次剩余(Cipolla算法)在CTF和加密实战中的妙用
  • AI + 低代码平台:工业互联网规模化落地的关键引擎
  • Webpack优化实战:从配置到性能调优