Godot 4.2入门实战:从零开发2D躲避游戏《Dodge the Creeps!》
1. 项目概述为什么选择Godot 4.2作为你的第一个游戏引擎如果你正在寻找一个免费、开源、功能强大且学习曲线相对平缓的游戏引擎来开启你的游戏开发之旅那么Godot 4.2绝对是一个值得你投入时间的选择。我接触过不少引擎从早期的Flash到Unity再到现在的Godot最终选择它作为教学和原型开发的主力原因很简单它足够轻量、设计理念清晰并且完全免费没有任何商业授权费用的后顾之忧。对于独立开发者、学生或者仅仅是出于兴趣的爱好者来说这无疑是最友好的起点。这次我们要做的项目是《Dodge the Creeps!》一个非常经典的2D躲避游戏。玩家控制一个角色在一个固定的场景中移动目标是躲避从屏幕四周不断生成的、向你移动的敌人。这个项目麻雀虽小五脏俱全它几乎涵盖了2D游戏开发的所有核心概念场景Scene与节点Node的构建、玩家输入处理、物理碰撞检测、敌人AI虽然简单、游戏状态管理开始、进行中、结束以及用户界面UI的搭建。通过完成这个项目你不仅能熟悉Godot编辑器的基本操作更能建立起对Godot核心工作流——“节点树”和“场景继承”——的深刻理解。网上有很多教程但很多是基于Godot 3.x的或者步骤跳跃太大让新手在配置环境、理解概念时就踩坑放弃。这篇指南的目标就是充当你的“保姆”我会基于最新的Godot 4.2稳定版带你一步步走完全程并重点标注那些我亲自踩过、或者新手最容易栽跟头的“坑”确保你能顺畅地从零到一看到自己的第一个游戏跑起来。2. 环境准备与项目初始化避开第一个大坑万事开头难而游戏开发的开头往往就难在环境配置和项目创建上。这一步走稳了后面会顺利很多。2.1 Godot 4.2引擎的下载与安装首先访问Godot引擎的官方网站。这里有个小技巧官网提供了多个版本对于新手我强烈建议下载“Standard”版本而不是“Mono”版本。Mono版本支持C#功能更强大但需要额外配置.NET环境对于纯粹的新手来说这无疑是增加了不必要的复杂度。我们先用GDScriptGodot自家的脚本语言语法类似Python非常易学来入门等熟悉了引擎再考虑C#也不迟。下载完成后你得到一个可执行文件Windows是.exemacOS是.appLinux是.x86_64。Godot是便携式的无需安装直接运行即可。我建议你为它创建一个专门的文件夹比如D:\GodotEngine然后把可执行文件放进去。这样做的好处是未来升级版本时你可以直接下载新版本放到同级目录互不干扰也方便管理。注意避免把Godot引擎放在路径包含中文或特殊字符的目录下虽然新版本对此兼容性好了很多但为了杜绝一切潜在问题使用纯英文路径是最稳妥的选择。2.2 创建你的第一个Godot项目运行Godot你会看到项目管理器。点击右上角的“New Project”按钮。项目名称与路径在“Project Name”里输入DodgeTheCreeps或者你喜欢的任何名字。在“Project Path”里点击“Browse”按钮创建一个全新的、空的文件夹来存放你的项目。例如D:\MyGames\DodgeTheCreeps。绝对不要选择一个已有文件的文件夹或者直接指向桌面。渲染器选择关键坑点这是Godot 4.x相比3.x最大的变化之一也是新手最容易迷惑的地方。你会看到“Renderer”选项默认可能是“Forward”或“Mobile”。对于我们的2D游戏《Dodge the Creeps!》选择 “Compatibility” 渲染器。这是为兼容性而设计的渲染器对硬件要求极低能确保你的2D游戏在几乎所有电脑上都能正常运行且渲染逻辑更接近Godot 3.x学习资料匹配度更高。避免在初期选择 “Forward”。它是为高端3D图形设计的虽然也能做2D但可能会引入一些不必要的复杂性和潜在的显示问题。先求稳再求好。其他设置剩下的选项如“Version Control”可以暂时不管直接点击窗口右下角的“Create Edit”按钮。项目创建成功后Godot编辑器主界面就会打开。如果你没看到预期的界面或者编辑器看起来很卡请立刻检查上一步的渲染器是否选错了。2.3 认识Godot编辑器界面花几分钟熟悉一下界面这对后续操作至关重要。主要面板包括场景面板左上以树形结构显示当前打开的场景的所有节点。这是我们工作的核心区域。文件系统面板左下显示项目文件夹中的所有文件。视图面板中间2D编辑器视图你可以在这里直观地布置你的游戏世界。检查器面板右侧当你选中场景面板或2D视图中的某个节点时这里会显示该节点的所有属性和参数供你调整。底部面板包含“输出”、“调试器”、“动画”等有用工具。3. 核心场景与节点构建理解Godot的“乐高”哲学Godot的核心思想是“节点Node树”。你可以把每个游戏对象比如玩家、敌人、墙壁、UI文字都想象成一个具有特定功能的节点。通过将这些节点像搭积木一样组合成一棵树就形成了一个“场景Scene”。游戏就是由一个或多个场景组成的。3.1 创建主游戏场景Main Scene我们的游戏需要一个主场景来容纳一切。在“场景”面板中你会看到一个默认的“Node2D”根节点。右键点击它选择“Rename”改名为Main。这将是我们的主场景根节点。在文件系统面板中右键点击res://代表项目根目录选择“新建” - “文件夹”创建一个名为scenes的文件夹。这是为了保持项目结构清晰。确保“场景”面板中的Main节点被选中然后点击编辑器菜单栏的“场景” - “保存场景”将它保存到scenes文件夹下命名为main.tscn。.tscn是Godot场景文件的格式。现在我们的主场景就创建好了。接下来我们要为它添加子节点。3.2 构建玩家Player场景玩家是一个独立的、可重用的游戏对象我们应该将它创建为一个单独的场景然后在主场景中“实例化”它。在“场景”面板点击左上角的“”号添加一个子节点。搜索并选择CharacterBody2D。为什么是CharacterBody2D而不是RigidBody2D或Area2D因为CharacterBody2D是专门为需要由代码精确控制移动如平台游戏角色、顶视角角色而设计的物理体它允许我们处理碰撞但运动逻辑完全由我们编写的脚本决定非常适合我们的玩家角色。将新节点重命名为Player。选中Player节点在检查器面板中点击“CollisionShape2D”属性旁边的“[空]”下拉框选择“新建 CircleShape2D”。这为玩家添加了一个圆形的碰撞形状。在2D视图中你可以看到一个白色的圆形轮廓拖动其边缘的橙色控制点可以调整大小。我们还需要一个视觉表现。右键点击Player节点选择“添加子节点”搜索并添加一个Sprite2D节点。Godot 4.2的Sprite2D节点默认没有纹理我们需要一张图片。准备素材你可以自己用画图工具画一个简单的圆形或方块或者从免费的素材网站如 Kenney.nl下载一个简单的角色精灵图。将图片文件如player.png拖拽到项目文件系统的res://目录下。我建议再创建一个art文件夹来存放所有美术资源。选中Sprite2D节点在检查器面板的“Texture”属性中点击“快速加载”按钮那个小文件夹图标选择你刚导入的player.png。现在你应该能在2D视图中看到玩家的样子了。最后将玩家场景保存为独立文件。确保Player节点是场景面板中的根节点如果不是右键它选择“设为根节点”然后点击“场景” - “保存场景为”保存到scenes文件夹下命名为player.tscn。实操心得在保存玩家场景前务必确保Player节点是根节点。一个常见的错误是保存时根节点还是Main这样你保存的就是整个主场景了。正确做法是在场景面板中右键Player节点选择“保存分支为场景”这样能自动将其设为新场景的根节点并保存。3.3 创建敌人Mob场景敌人的创建流程与玩家类似但有一些关键区别。新建一个场景根节点同样是CharacterBody2D命名为Mob。添加CollisionShape2D形状可以选择RectangleShape2D方形敌人或CapsuleShape2D胶囊形看你的喜好。添加Sprite2D节点并赋予一个不同的纹理如mob.png以便和玩家区分开。敌人需要自动移动和随机生成这需要脚本。但现在我们先保存场景。点击“场景” - “保存场景”保存为scenes/mob.tscn。3.4 布置游戏世界添加边界与生成点回到我们的主场景main.tscn。添加边界防止玩家跑出屏幕我们需要一个不可见的区域来限制玩家移动。添加一个Area2D节点作为Main的子节点命名为PlayArea。Area2D用于检测重叠我们可以用它来定义游戏区域。为PlayArea添加一个CollisionShape2D子节点。在检查器中将其形状设置为RectangleShape2D。在2D视图中将这个矩形拉大到覆盖整个你希望的游戏区域通常比屏幕稍大一点给敌人生成留出空间。添加入口生成点敌人需要从屏幕边缘生成。添加一个Marker2D节点作为Main的子节点命名为MobSpawnLocation。Marker2D是一个不可见的辅助节点仅代表一个位置。我们将用它来标记敌人生成的位置。不要只放一个我们需要多个生成点。你可以复制粘贴MobSpawnLocation节点在屏幕的左上、右上、左下、右下等边缘位置放置多个实例并分别命名为MobSpawnLocation1、MobSpawnLocation2等。更高效的做法是先创建一个MobSpawnLocation写好脚本逻辑然后通过代码随机选择这些预设位置中的一个。4. 编写游戏逻辑让一切动起来静态的场景没有意义我们需要用GDScript为节点注入生命。4.1 玩家控制脚本player.gd双击打开player.tscn场景选中根节点Player在检查器面板最下方点击“脚本”旁边的“新建脚本”按钮。保持默认名称player.gd和路径点击“创建”。现在你打开了脚本编辑器。删除默认的_ready()和_process()函数我们将从头编写。extends CharacterBody2D # 定义玩家移动速度单位像素/秒。你可以随时在检查器中调整这个值。 export var speed: float 400.0 func _physics_process(delta: float) - void: # 获取输入向量。Input.get_vector() 可以同时处理键盘和手柄输入非常方便。 # 参数分别是左动作右动作上动作下动作。 var direction: Vector2 Input.get_vector(quot;move_leftquot;, quot;move_rightquot;, quot;move_upquot;, quot;move_downquot;) # 计算本帧的速度。方向向量是单位向量乘以速度得到速度向量。 var velocity: Vector2 direction * speed # 使用 move_and_collide 或 move_and_slide 来移动角色。 # move_and_slide() 会自动处理斜坡、地面检测等对于有重力的游戏更合适。 # 我们的游戏是顶视角没有重力用 move_and_collide 更直接但 move_and_slide 也能用。 # 这里使用 move_and_slide它返回碰撞后的实际速度但我们这里不需要。 move_and_slide() # 注意我们直接设置了 velocity但 move_and_slide 会使用 CharacterBody2D 自带的 velocity 变量。 # 所以更标准的写法是 # velocity direction * speed # move_and_slide() # 但上面的代码也能工作因为 move_and_slide() 内部会使用节点的 velocity 属性。 # 让我们修正为更标准的写法 velocity direction * speed move_and_slide()保存脚本。现在我们需要设置输入映射。点击编辑器顶部菜单的“项目” - “项目设置”切换到“输入映射”标签页。在“动作”输入框中输入move_left点击“添加”按钮。选中新添加的move_left动作点击右下角的“”号添加事件选择“物理键”然后按下键盘上的A键或左方向键。重复这个过程为move_rightD/右方向键、move_upW/上方向键、move_downS/下方向键添加映射。回到主场景我们需要将玩家实例化进来。在“场景”面板中选中Main节点然后从文件系统面板中将player.tscn拖拽到2D视图或场景面板中Main节点的下方。现在运行游戏按F5你应该可以用WASD或方向键控制玩家移动了踩坑记录很多新手在这里会遇到玩家移动速度极快或不受控制的问题。原因通常是混淆了_process(delta)和_physics_process(delta)。_process每帧调用帧率不固定_physics_process在物理帧调用默认每秒60次。涉及物理移动和碰撞的代码务必写在_physics_process中否则移动速度会因帧率波动而变得不稳定。4.2 敌人脚本mob.gd打开mob.tscn为根节点Mob创建脚本mob.gd。敌人的逻辑是生成时随机选择一个方向指向屏幕中心或随机位置然后以恒定速度直线移动直到离开屏幕后被销毁。extends CharacterBody2D # 敌人的移动速度可以在检查器中为不同的敌人类型设置不同的值。 export var speed: float 150.0 func _ready() - void: # 敌人准备就绪时我们给它一个随机的旋转和移动方向让它看起来更多样。 # 随机旋转视觉Sprite $Sprite2D.rotation randf_range(0, TAU) # TAU 等于 2 * PI一个完整的弧度 # 随机选择一个移动方向一个单位向量 var direction: Vector2 Vector2.RIGHT.rotated(randf_range(0, TAU)) # 将速度设置为该方向乘以速度值 velocity direction * speed func _physics_process(delta: float) - void: move_and_slide() # 检测敌人是否已经移出屏幕。一个简单的方法是检查它的全局位置。 # 假设我们的游戏区域大致在 (-1000, -1000) 到 (1000, 1000) 的范围内。 # 你可以根据你的 PlayArea 大小调整这个值。 var screen_rect: Rect2 Rect2(Vector2(-1000, -1000), Vector2(2000, 2000)) if not screen_rect.has_point(global_position): # 如果敌人不在屏幕矩形内则销毁它释放资源。 queue_free()4.3 主场景控制脚本main.gd这是游戏的大脑负责生成敌人、计时、更新分数和判断游戏结束。回到main.tscn为根节点Main创建脚本main.gd。extends Node2D # 通过export将场景资源暴露在检查器方便拖拽赋值。 export var mob_scene: PackedScene export var player_scene: PackedScene # 游戏状态变量 var score: int 0 var game_running: bool false # 节点引用在 _ready 中获取避免硬编码路径。 onready var score_label: Label $HUD/ScoreLabel onready var timer: Timer $MobTimer onready var mob_spawn_locations: Array[Node2D] [ $MobSpawnLocation1, $MobSpawnLocation2, $MobSpawnLocation3, $MobSpawnLocation4 ] onready var hud: CanvasLayer $HUD onready var player: CharacterBody2D $Player func _ready() - void: # 初始化时确保游戏未开始。我们通过一个“开始游戏”按钮来触发。 # 先隐藏玩家直到游戏开始 if player: player.hide() # 连接计时器的超时信号 timer.timeout.connect(_on_mob_timer_timeout) func new_game() - void: # 重置游戏状态 score 0 score_label.text quot;Score: %dquot; % score game_running true # 显示并重置玩家位置 if player: player.show() player.position Vector2(400, 300) # 设置到屏幕中心或某个起始点 player.velocity Vector2.ZERO # 清除所有现有的敌人Mob节点 get_tree().call_group(quot;mobsquot;, quot;queue_freequot;) # 开始生成敌人的计时器 timer.start() # 更新HUD hud.update_score(score) hud.show_message(quot;Get Ready!quot;) func game_over() - void: game_running false timer.stop() hud.show_game_over() func _on_mob_timer_timeout() - void: # 只有在游戏运行时才生成敌人 if not game_running: return # 随机选择一个生成位置 var spawn_location: Node2D mob_spawn_locations[randi() % mob_spawn_locations.size()] # 实例化敌人场景 var mob: CharacterBody2D mob_scene.instantiate() # 设置敌人的位置为生成点的位置 mob.position spawn_location.position # 将敌人添加到主场景中 add_child(mob) # 可以给敌人一个随机的速度变化增加游戏性 mob.speed randf_range(100.0, 250.0) # 为了方便管理将敌人加入“mobs”组 mob.add_to_group(quot;mobsquot;) # 每生成一个敌人增加分数 score 1 score_label.text quot;Score: %dquot; % score hud.update_score(score) # 我们需要检测玩家与敌人的碰撞。这可以通过 Area2D 或直接处理信号来实现。 # 这里我们在主场景中通过连接玩家和敌人的“body_entered”信号来处理。 # 但更常见的做法是在玩家或敌人身上添加 Area2D 来检测碰撞。 # 让我们修改玩家场景为其添加一个 Area2D 用于检测被敌人碰到。上面的代码引用了一些我们尚未创建的节点比如$HUD、$MobTimer。我们需要先创建它们。4.4 创建用户界面HUDHUD平视显示器用于显示分数、游戏开始提示和游戏结束信息。在主场景中添加一个CanvasLayer节点作为Main的子节点命名为HUD。CanvasLayer可以确保其子节点始终绘制在最上层不受游戏世界坐标影响。在HUD节点下添加一个Label节点命名为ScoreLabel。在检查器中调整其“文本”为“Score: 0”并设置字体大小、位置如左上角。再添加一个Label节点命名为MessageLabel用于显示“Get Ready!”或“Game Over”。将其放在屏幕中央字体可以设置得大一些。添加一个Button节点命名为StartButton文本设为“Start Game”放在屏幕中央下方。为HUD创建脚本hud.gd。extends CanvasLayer # 信号用于通知主场景按钮被按下 signal start_game onready var score_label: Label $ScoreLabel onready var message_label: Label $MessageLabel onready var start_button: Button $StartButton func update_score(new_score: int) - void: score_label.text quot;Score: %dquot; % new_score func show_message(text: String) - void: message_label.text text message_label.show() func hide_message() - void: message_label.hide() func show_game_over() - void: show_message(quot;Game Overquot;) # 游戏结束后显示开始按钮让玩家可以重新开始 start_button.show() func _on_start_button_pressed() - void: start_button.hide() hide_message() start_game.emit() # 发射信号回到main.gd脚本我们需要连接HUD的信号并完善游戏流程。# 在 main.gd 的 _ready 函数中补充 func _ready() - void: if player: player.hide() timer.timeout.connect(_on_mob_timer_timeout) # 连接HUD的开始游戏信号 hud.start_game.connect(new_game) # 初始显示开始按钮和消息 hud.show_message(quot;Dodge the Creeps!quot;) hud.start_button.show() # 修改 new_game 函数开头隐藏按钮和消息 func new_game() - void: hud.hide_message() hud.start_button.hide() # ... 其余代码不变 ...4.5 添加计时器和碰撞检测计时器在主场景中添加一个Timer节点命名为MobTimer。在检查器中设置“等待时间”为1.0秒并勾选“自动开始”我们先不勾选由代码控制。在main.gd中我们已经引用了它。玩家碰撞检测打开player.tscn为Player节点添加一个Area2D子节点命名为HitBox。为HitBox添加一个CollisionShape2D形状可以设置为和玩家的视觉精灵差不多大或稍小。为HitBox连接body_entered信号。选中HitBox节点在检查器右侧的“节点”标签页找到“body_entered”信号双击它选择Player节点作为目标然后会弹窗让你在player.gd中创建一个新函数。命名为_on_hit_box_body_entered。# 在 player.gd 中添加 func _on_hit_box_body_entered(body: Node2D) - void: # 当有物体比如敌人进入 HitBox 区域时触发游戏结束。 # 我们需要通知主场景。可以通过多种方式比如发射一个信号。 # 这里我们假设主场景会监听玩家的一个自定义信号。 # 我们先在玩家脚本顶部定义信号 # signal hit # 然后在这里发射 hide() # 玩家消失或播放死亡动画 hit.emit()在player.gd顶部添加signal hit。然后回到主场景选中我们实例化的Player节点在检查器的“节点”标签页你应该能看到它有一个“hit”信号。连接这个信号到Main节点在main.gd中创建回调函数_on_player_hit。# 在 main.gd 中添加 func _on_player_hit() - void: game_over()5. 打磨与优化让游戏更像样基础功能完成后我们需要进行一些打磨提升游戏体验。5.1 添加音效和背景音乐声音对游戏氛围至关重要。准备音效文件寻找或制作简单的音效如玩家被击中时的“hurt.wav”、得分时的“score.wav”、游戏开始的“start.wav”。将文件放入项目例如audio文件夹。添加音频播放器在player.tscn中为Player节点添加一个AudioStreamPlayer2D子节点命名为HurtSound。在检查器中将“流”属性设置为你的“hurt.wav”文件。在_on_hit_box_body_entered函数中在发射hit信号前添加$HurtSound.play()。背景音乐在主场景Main节点下添加一个AudioStreamPlayer节点注意不是2D版本命名为BackgroundMusic。设置其流为你的背景音乐文件并勾选“自动播放”和“循环”。5.2 简单的视觉反馈玩家闪烁当玩家被击中时可以让他闪烁几下再消失增加反馈。这需要用到Tween节点或AnimationPlayer。AnimationPlayer更强大但对于简单闪烁Tween更快捷。在player.gd的_on_hit_box_body_entered函数中func _on_hit_box_body_entered(body: Node2D) - void: # 禁用碰撞防止连续触发 $HitBox/CollisionShape2D.set_deferred(quot;disabledquot;, true) $HurtSound.play() # 创建并运行一个闪烁动画 var tween create_tween() tween.tween_property($Sprite2D, quot;modulate:aquot;, 0.0, 0.5).set_trans(Tween.TRANS_SINE) tween.tween_callback(func(): hit.emit()) # 动画结束后发射信号敌人生成效果可以在敌人生成时播放一个简单的缩放动画。在mob.gd的_ready函数末尾添加func _ready() - void: # ... 之前的旋转和速度设置代码 ... # 缩放动画 var tween create_tween() scale Vector2(0.1, 0.1) # 从很小开始 tween.tween_property(self, quot;scalequot;, Vector2(1.0, 1.0), 0.3).set_trans(Tween.TRANS_BACK)5.3 游戏难度曲线一个不变的生成速度会让游戏后期变得无聊。我们可以让敌人生成速度随着时间或分数增加而加快。修改main.gd中的_on_mob_timer_timeout函数和new_game函数。# 在 main.gd 顶部添加一个变量 var mob_spawn_time: float 1.0 # 初始生成间隔 func new_game() - void: # ... 其他重置代码 ... mob_spawn_time 1.0 timer.wait_time mob_spawn_time timer.start() func _on_mob_timer_timeout() - void: # ... 生成敌人的代码 ... # 每生成10个敌人缩短一点生成间隔 if score 0 and score % 10 0: mob_spawn_time max(0.3, mob_spawn_time * 0.95) # 最快不低于0.3秒 timer.wait_time mob_spawn_time6. 常见问题与调试技巧实录即使跟着教程一步步做也难免会遇到问题。这里记录了一些我教学中学生最常遇到的坑和解决方法。6.1 运行游戏时黑屏或只有部分元素显示检查场景设置确保你运行的是主场景main.tscn。在编辑器顶部确认“当前场景”旁边显示的是main.tscn。你也可以在“项目设置” - “应用” - “运行”中将主场景设置为main.tscn。检查节点可见性在场景面板中确保关键节点如Player、HUD的“眼睛”图标是睁开的可见状态。有时脚本中的hide()操作可能意外执行了。检查渲染器如果你在非常老的集成显卡上运行且选择了“Forward”渲染器可能会出现问题。回退到“Compatibility”渲染器。6.2 玩家或敌人无法移动或者移动异常脚本未正确附加确保player.gd脚本确实附加到了Player节点上。在场景面板选中Player节点检查检查器底部的“脚本”属性是否指向了正确的文件。输入映射错误再次确认“项目设置” - “输入映射”中的动作名称是否与脚本中Input.get_vector()里使用的字符串完全一致包括大小写。“move_left”和“Move_Left”是不同的。物理帧函数错误重申一遍移动代码必须写在_physics_process(delta)中而不是_process(delta)。碰撞形状问题如果玩家卡住不动检查Player和Mob的CollisionShape2D是否大小合适以及它们是否与其他不可见的碰撞体如PlayArea的边界发生了意外的碰撞。可以暂时禁用其他碰撞体来排查。6.3 敌人不生成或者生成后立即消失计时器未启动检查MobTimer节点的“自动开始”是否被错误勾选或取消。我们的代码是在new_game()中手动启动的。确保new_game()函数被正确调用HUD的StartButton信号连接好了。生成位置无效检查mob_spawn_locations数组中的Marker2D节点路径是否正确。在_ready函数中打印一下数组长度和位置信息来调试print(mob_spawn_locations.size()),print(mob_spawn_locations[0].global_position)。屏幕边界判断过早在mob.gd的_physics_process中我们判断敌人是否离开屏幕。如果screen_rect设置得太小敌人可能一生成就被判定为离开屏幕而被queue_free()。一个更稳健的方法是使用VisibilityNotifier2D节点。为Mob添加一个VisibilityNotifier2D子节点并连接其screen_exited信号在信号回调中调用queue_free()。这是Godot推荐的做法。6.4 碰撞检测不工作碰撞层和掩码这是Godot碰撞系统中最容易混淆的部分。每个CollisionObject2D如Area2D,PhysicsBody2D都有“碰撞层”和“碰撞掩码”属性。简单理解层Layer这个物体自己在哪一层。掩码Mask这个物体会与哪些层的物体发生碰撞。默认情况下所有物体都在第1层且掩码也是第1层所以它们会相互碰撞。如果你修改了这些设置可能会导致碰撞失效。对于这个简单游戏保持默认值层1掩码1通常即可。如果做了修改请确保玩家的HitBoxArea2D的“掩码”包含了敌人所在层反之亦然。信号连接错误确保Area2D的body_entered信号正确连接到了脚本中的函数。在“节点”标签页检查连接线。也可以尝试在连接的函数里先写一个简单的print(“Hit!”)来测试信号是否被触发。节点未在场景树中确保发生碰撞的两个节点玩家和敌人都已作为子节点添加到了主场景树中。通过add_child()实例化的节点会自动加入。6.5 游戏性能突然下降卡顿敌人未及时销毁这是最常见的原因。如果敌人离开屏幕后没有被queue_free()它们会持续累积计算它们的移动和碰撞会严重消耗性能。务必确保你的销毁逻辑无论是矩形判断还是VisibilityNotifier2D正常工作。内存泄漏虽然GDScript有垃圾回收但不当的引用特别是对场景树的引用可能导致节点无法被释放。确保在移除节点时使用queue_free()而不是free()queue_free()更安全会在当前帧结束后释放。复杂的实时计算避免在_process或_physics_process中执行非常耗时的操作比如每帧遍历一个巨大的数组。对于我们的游戏这通常不是问题。调试是开发的一部分不要害怕使用print()函数输出变量值或者使用Godot内置的调试器底部面板的“调试器”标签页来设置断点和单步执行。遇到问题时先隔离问题单独测试玩家移动、单独测试敌人生成、单独测试碰撞一步步缩小范围总能找到原因。完成以上所有步骤后你的《Dodge the Creeps!》就应该是一个功能完整、有一定可玩性的小游戏了。你可以尝试调整各种参数玩家速度、敌人生成速度、敌人移动模式、添加更多敌人类型、设计关卡等等。这个项目就像一块敲门砖帮你熟悉了Godot的核心工作流。接下来你可以参考官方文档和社区教程去探索更复杂的2D功能比如动画状态机、粒子特效、TileMap地图编辑甚至尝试开发一个小型的平台跳跃或RPG游戏。记住动手做和不断试错是学习游戏开发最快的方式。

相关新闻

HTML演示页和PPTX到底怎么选

HTML演示页和PPTX到底怎么选

用AI做PPT的人越来越多,一个挺常见的问题是:导出的HTML在线演示页和PPTX文件到底选哪个? HTML和PPTX的核心差异 HTML存在着优势之处,那便是在进行交互以及动态效果的制作方面比较便捷。可是它有一个问题是无法回避的&#xff1a…

2026/7/24 16:02:42 阅读更多 →
ToastFish完整指南:利用Windows通知栏高效背单词的终极方案

ToastFish完整指南:利用Windows通知栏高效背单词的终极方案

ToastFish完整指南:利用Windows通知栏高效背单词的终极方案 【免费下载链接】ToastFish 一个利用摸鱼时间背单词的软件。 项目地址: https://gitcode.com/GitHub_Trending/to/ToastFish ToastFish是一款创新的Windows桌面应用,它巧妙地将单词学习…

2026/7/24 16:01:41 阅读更多 →
生产级RAG架构实战:从AIL框架到kzl工具链

生产级RAG架构实战:从AIL框架到kzl工具链

1. 项目概述最近在AI工程化领域,RAG(Retrieval-Augmented Generation)架构正在成为连接大语言模型与企业知识库的主流方案。今天要分享的是如何基于AIL(AI Layer)框架和kzl(Knowledge Zoo Library&#xff…

2026/7/24 16:01:41 阅读更多 →

最新新闻

告别限速!3分钟学会使用百度网盘解析工具实现高速下载

告别限速!3分钟学会使用百度网盘解析工具实现高速下载

告别限速!3分钟学会使用百度网盘解析工具实现高速下载 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 还在为百度网盘几十KB的下载速度而苦恼吗?今天我…

2026/7/24 16:11:45 阅读更多 →
生成式 UI 在个性化推荐界面的应用:模型驱动的动态布局与内容适配

生成式 UI 在个性化推荐界面的应用:模型驱动的动态布局与内容适配

生成式 UI 在个性化推荐界面的应用:模型驱动的动态布局与内容适配 一、传统推荐界面的局限:千人一面的困局 电商、内容平台与广告投放系统的推荐逻辑在过去五年取得了长足进步——协同过滤、深度兴趣网络、多任务学习模型让"推荐什么"越来越精…

2026/7/24 16:11:45 阅读更多 →
DRA75P/DRA74P SoC PCB设计实战:电源完整性、ESD防护与EMC布局指南

DRA75P/DRA74P SoC PCB设计实战:电源完整性、ESD防护与EMC布局指南

1. 项目概述:当高速SoC遇上PCB的“暗流涌动”在嵌入式系统,尤其是汽车电子和工业控制这类对可靠性要求极高的领域,设计一块能稳定驱动像DRA75P/DRA74P这样复杂SoC的PCB,远不止是把原理图上的线连起来那么简单。表面上看&#xff0…

2026/7/24 16:11:45 阅读更多 →
DLSS Swapper:超分辨率技术版本管理的架构设计与实现深度解析

DLSS Swapper:超分辨率技术版本管理的架构设计与实现深度解析

DLSS Swapper:超分辨率技术版本管理的架构设计与实现深度解析 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 在游戏性能优化领域,DLSS Swapper作为一款开源工具,通过创新的架构设计…

2026/7/24 16:11:45 阅读更多 →
论文降AI检测率7步实操与原理详解

论文降AI检测率7步实操与原理详解

1. 论文降AI检测率实战指南最近帮几位研究生朋友处理论文时,发现大家普遍面临一个棘手问题:用AI辅助写作后,查重系统显示的AI生成率居高不下。有位学弟的初稿AI率甚至高达58%,经过两周的系统性调整,我们成功将指标压到…

2026/7/24 16:11:45 阅读更多 →
AI应用架构师如何实现负责任AI的技术落地

AI应用架构师如何实现负责任AI的技术落地

1. AI应用架构师的角色定位与核心使命 在人工智能技术快速渗透各行各业的今天,AI应用架构师这一新兴职业角色正发挥着越来越关键的作用。与传统软件架构师不同,AI应用架构师需要同时具备技术架构能力与伦理判断能力,他们的核心使命是确保AI系…

2026/7/24 16:10:45 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻