ARTICLE DETAIL

资讯详情

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

Godot 4 投射物平台游戏教程:TileMapLayer与子弹系统实战

Godot 4 投射物平台游戏教程:TileMapLayer与子弹系统实战 大家好我是你们的老朋友。之前一直在折腾 Godot 3.x 的 2D 项目最近把目光转向了 Godot 4开始尝试做一款带投射物机制的 2D 平台游戏。说实话从 3.x 切到 4.x 确实有不少新东西要重新适应比如 TileMapLayer 的引入、物理系统的调整以及动画播放器的变化。正好有朋友也在问 Godot 4 中投射物平台游戏应该怎么做所以我就把这一段时间的踩坑和实操经验整理成一篇完整的教程。这篇文章适合有一定 Godot 基础、想从零开始做一个完整小游戏的开发者也适合刚接触 Godot 4、想了解 TileMapLayer 和投射物系统的同学。读完你可以掌握角色移动、跳跃、TileMap 关卡搭建、投射物生成与碰撞检测以及敌人和生命值系统的完整写法。1. 背景与核心概念1.1 为什么选择 Godot 4 做 2D 平台游戏Godot 4 是 Godot 引擎的一次大版本升级带来了全新的渲染器、物理系统、资源导入管线以及场景组织方式。对于 2D 平台游戏来说Godot 4 的 2D 渲染更高效光照和粒子效果也更强同时新增了 TileMapLayer 节点让瓦片地图的使用方式发生了明显变化。在 Godot 3.x 中TileMap 是单独一个节点所有瓦片数据直接挂在同一个节点下到 Godot 4 之后官方把 TileMap 升级为 TileMapLayer每个 TileMapLayer 专注于一层瓦片这样可以让地面、装饰、碰撞层、道具层分开管理结构更清晰也更容易做分层渲染和动态修改。刚开始从 3.x 转过来的时候很多人会在 TileMap 和 TileMapLayer 之间困惑后面我们会专门讲解。1.2 什么是投射物平台游戏投射物平台游戏指的是玩家角色在 2D 平台场景中移动、跳跃同时可以发射子弹、飞刀、能量球等投射物用于击败敌人或触发机关。典型的例子包括经典的魂斗罗风格关卡或是一些恶魔城like游戏中的远程攻击设定。这种游戏的核心玩点通常集中在三方面角色移动手感左右移动、跳跃、重力、空中控制。投射物系统发射方向、速度、生命周期、碰撞检测。关卡与敌人瓦片地图、敌人 AI、伤害判定、生命值系统。本文会通过一个简单的“主角 发射子弹 瓦片关卡 敌人”的小项目把这些系统完整串起来。用一个最简单的项目演示多个系统之间的协作比单独拆开讲更有参考价值。1.3 本文使用的技术栈模块技术方案引擎Godot 4.x以 Godot 4.2 为主低版本略有差异语言GDScript瓦片地图TileMapLayer 节点场景组织Node2D / CharacterBody2D / Area2D动画AnimatedSprite2D 或 Sprite2D 脚本控制物理碰撞碰撞形状、物理图层、碰撞掩码版本说明Godot 4 目前已经发布多个 4.x 小版本例如 4.0、4.1、4.2、4.3。本文示例以较为稳定的 Godot 4.2 为主写代码但大部分功能在 4.0 以上都能运行。如果你使用的是 4.3 或 4.4接口基本兼容直接照着写通常不会有大问题。如果某个属性名字报错优先检查引擎版本和文档。2. 环境准备与项目创建2.1 安装 Godot 4从 Godot 官网下载 Godot 4.x 标准版即可。Windows 用户可以直接下载 exe 文件macOS 用户下载 dmg 或 zip 版Linux 用户选择对应发行版。下载完成后无需安装直接把编辑器打开就行。Godot 4 项目与 Godot 3 项目不通用所以不要用 3.x 版本直接打开 4.x 工程文件。2.2 创建项目打开 Godot 4点击“新建项目”填写项目名称例如Godot4ProjectilePlatformer选择项目路径渲染器保持默认的 Forward 或 Mobile 都可以。2D 平台游戏对渲染要求不高使用默认设置即可。创建完成后得到如下结构Godot4ProjectilePlatformer/ ├── project.godot ├── icon.svg ├── scenes/ ├── scripts/ ├── assets/其中scenes放场景文件scripts放 GDScript 脚本assets放图片和音频。可以先手动创建这些文件夹也可以在后续保存场景时自动创建。2.3 准备素材为了不涉及版权问题本文不依赖外部图片素材直接用 Godot 内置的图标和颜色节点代替。你可以用ColorRect做临时角色或者用Sprite2D设置一张简单的 16x16 像素图片。如果你自己有原始素材包也可以把图片导入assets目录。本文重点不在资源导入所以用简单色块来表示角色、敌人和投射物。3. TileMapLayer 的使用详解3.1 TileMap 与 TileMapLayer 的区别刚接触 Godot 4 的朋友很容易掉进一个坑在节点创建列表里找不到旧版的 TileMap 节点只有 TileMapLayer。实际上这是 Godot 4 的重构结果。在 Godot 3.x 中TileMap 节点同时承担了瓦片存储、渲染、碰撞、坐标转换等功能。但当一个 TileMap 非常大、图层很多的时候节点会变得臃肿而且不方便动态控制某一层的显示和遮挡关系。Godot 4 拆出了 TileMapLayer每个 TileMapLayer 只负责一层。多个 TileMapLayer 可以控制不同的绘制顺序。碰撞、渲染、脚本访问都拆分得更清晰。如果你从 3.x 迁移项目在 Godot 4 中新建 TileMapLayer 后需要重新设置 TileSet旧项目不能直接无缝迁移。3.2 创建 TileSet 和 TileMapLayer首先准备一张类似下图的地图图集。假设我们有一张 128x128 的瓦片图集每个瓦片是 16x16。在 Godot 4 编辑器中操作在文件系统中选中图片资源。右键选择“新建 TileSet”。在 TileSet 资源中设置 Tile Size 为 16x16。添加图集选择瓦片图片设置切片大小。在 TileSet 的“物理层”中添加一个物理层并在需要的瓦片上绘制碰撞多边形。如果没有现成图片也可以直接在 TileSet 中用空瓦片创建几个方块地形。创建完 TileSet 之后新建一个 TileMapLayer 节点把 TileSet 资源拖到节点的 TileSet 属性中然后在编辑器中像画笔一样刷出地面、墙壁和平台地形。这里要注意Godot 4 的瓦片刷法比旧版更细腻右下角可以选择不同 Tile快捷键D切换绘制模式E切换橡皮擦模式。默认情况下刷出来的瓦片会同时使用你定义的物理层碰撞。3.3 使用脚本操作 TileMapLayer为了后面的游戏逻辑除了手动刷地图有时我们也需要通过代码动态修改地图比如生成随机平台、破坏地形、清除障碍物。在 Godot 4 中TileMapLayer 提供了set_cell和get_cell等接口。# 文件路径scripts/tilemap_helper.gd extends TileMapLayer # 在指定格子坐标放置瓦片 func place_tile(cell: Vector2i, tile_id: int): # 第一个参数是瓦片位置格子坐标第二个参数是图集ID第三个是图集坐标 set_cell(cell, 0, Vector2i(1, 0)) # 清除指定格子 func clear_tile(cell: Vector2i): set_cell(cell, -1) # -1 表示清空这里需要注意set_cell(cell: Vector2i, source_id: int, atlas_coords: Vector2i)是 Godot 4 TileMapLayer 的标准写法。如果你在旧教程中看到set_cellv或tilemap.set_cell(x, y, ...)那是 Godot 3 的用法不能直接照搬。另外在场景中引用 TileMapLayer 时$TileMapLayer如果节点名字是TileMapLayer直接在脚本里$TileMapLayer.place_tile(...)就能调用。3.4 碰撞层与图层顺序在 2D 平台游戏中我们通常有三层瓦片背景层只渲染不碰撞例如远景、天空。地面层既渲染又有物理碰撞。装饰层只渲染但绘制顺序在地面之上。每一个 TileMapLayer 都有自己的z_index和y_sort_enabled属性。多层之间通过调整节点顺序来控制遮挡关系节点在上面的会绘制在更前面。碰撞方面TileMapLayer 默认会把瓦片上的物理多边形加入刚体静态碰撞。玩家场景使用 CharacterBody2D敌人和投射物用 Area2D 或 CharacterBody2D 时需要确保碰撞层和掩码匹配否则会出现“穿墙”或者“打不到敌人”的问题。这一点会在后面实战中细讲。4. 主角移动与跳跃实现4.1 创建玩家场景在scenes目录下新建场景根节点选择CharacterBody2D命名为Player。为 Player 添加以下子节点Sprite2D显示角色外观。CollisionShape2D添加一个矩形碰撞形状。RayCast2D用于地面检测可选也可以直接用is_on_floor()。MuzzleMarker2D表示发弹点。先给玩家一个简单脚本player.gd实现水平移动和跳跃。# 文件路径scripts/player.gd extends CharacterBody2D export var speed: float 180.0 export var jump_velocity: float -300.0 export var gravity: float 600.0 func _physics_process(delta: float) - void: # 获取输入方向A/D 或 左/右 var direction : Input.get_axis(move_left, move_right) # 水平方向速度 if direction ! 0: velocity.x direction * speed else: # 简单摩擦效果 velocity.x move_toward(velocity.x, 0, speed * 0.2) # 重力 if not is_on_floor(): velocity.y gravity * delta # 跳跃 if Input.is_action_just_pressed(jump) and is_on_floor(): velocity.y jump_velocity move_and_slide()这段代码是平台游戏最基础的移动模板。Input.get_axis(move_left, move_right)会返回 -1 到 1 之间的值用于判断方向。move_toward让速度在停止输入时平滑减到 0避免滑冰感。注意gravity这里使用正数叠加到velocity.y时角色会向下加速。jump_velocity为负数表示向上跳。访问函数说明is_on_floor()依赖move_and_slide()自动计算着地状态。如果跳跃失效检查物理层和掩码确保玩家碰撞层能检测到地面瓦片。jump动作需要预先在输入映射中配置。4.2 配置输入映射点击编辑器顶部菜单“项目 - 项目设置 - 输入映射”添加以下动作动作名称按键1按键2move_leftA左箭头move_rightD右箭头jump空格W添加完成后脚本中的move_left、move_right、jump才能被正确识别。4.3 地面检测与碰撞设置Player 的CollisionShape2D可以是一个矩形尺寸根据你的角色大小设置。注意碰撞层默认是第 1 层掩码也默认是第 1 层。TileMapLayer 的瓦片碰撞默认在第 1 层。所以多数情况下默认值就能让角色站在瓦片上。如果在运行后角色直接掉穿地面请检查TileSet 中是否给瓦片画了物理多边形。TileMapLayer 是否开启了物理层。播放场景后在调试菜单中打开“可见碰撞形状”查看碰撞多边形。4.4 角色动画翻转如果没有动画素材可以用简单的左右翻转来模拟朝向# 在 _physics_process 里补充 if direction ! 0: $Sprite2D.flip_h direction 0如果使用AnimatedSprite2D翻转逻辑相同。5. 投射物系统设计投射物平台游戏的核心之一就是“打子弹”。这里我们用 Area2D 做子弹因为它可以检测进入碰撞区域的物体不会像 CharacterBody2D 那样受物理移动限制。5.1 投射物场景新建场景根节点为Area2D命名为Bullet。添加子节点Sprite2D显示子弹外观。CollisionShape2D圆形或矩形碰撞区域。VisibleOnScreenNotifier2D用于飞出屏幕后自动删除。脚本如下# 文件路径scripts/bullet.gd extends Area2D export var speed: int 500 var direction : Vector2.RIGHT func _ready() - void: # 连接信号 body_entered.connect(_on_body_entered) area_entered.connect(_on_area_entered) func setup(dir: Vector2) - void: direction dir.normalized() func _physics_process(delta: float) - void: # 子弹移动方式一直接使用全局位移 position direction * speed * delta func _on_body_entered(body: Node2D) - void: # 碰到玩家或墙壁时删除子弹 if body is Player: return queue_free() func _on_area_entered(area: Area2D) - void: # 碰到敌人伤害区域时让敌人扣血并删除子弹 if area.has_method(take_damage): area.take_damage(1) queue_free()需要注意的点body_entered处理子弹碰到 CharacterBody2D 或其他碰撞体的场景。area_entered处理子弹碰到 Area2D 敌人的场景。queue_free()会延迟到帧结束时删除节点比直接free()更安全。setup(dir)用于初始化方向不要在_ready里直接写死。另一种移动方式是使用move_and_collide但 Area2D 没有内建移动方法需要手动操作position或使用PhysicsServer2D。对大多数 2D 平台游戏来说直接改position简单也够用。如果你想更精确可以继承RigidBody2D但那种方案后续可控性会差一些。5.2 玩家发射逻辑回到player.gd添加发射逻辑# 在 Player 场景中添加发射点 Marker2D命名为 Muzzle # 在 player.gd 中补充 onready var muzzle : $Muzzle export var bullet_scene: PackedScene var facing_direction : Vector2.RIGHT func _physics_process(delta: float) - void: # ...之前的移动代码... # 更新朝向 if direction ! 0: facing_direction Vector2.RIGHT if direction 0 else Vector2.LEFT # 发射 if Input.is_action_just_pressed(shoot): shoot() func shoot() - void: if bullet_scene null: return var bullet : bullet_scene.instantiate() get_parent().add_child(bullet) bullet.global_position muzzle.global_position bullet.setup(facing_direction)这里有个重要的做法把子弹实例加到玩家的父节点而不是加到玩家节点下。因为如果子弹作为玩家的子节点它会跟随玩家的位移和旋转子弹的坐标系会变得混乱。放到同一个父节点下子弹就在世界坐标系中独立运动。输入映射中还需要添加自定义动作shoot例如按键J或鼠标左键。5.3 子弹碰到墙壁的处理如果地图瓦片有碰撞子弹作为 Area2D默认并不会被墙壁挡住因为它没有物理体。所以我们需要在子弹的脚本中额外检测是否碰到了静态瓦片。简单方案是使用PhysicsRayQuery2D做检测但更直观的是在子弹内部加一个RayCast2D指向运动方向并且设置collision_mask为瓦片层。还有一种常见做法利用body_entered信号。当子弹是 Area2D瓦片是静态碰撞体时body_entered是可以触发的但前提是瓦片所在的物体确实作为“body”存在。TileMapLayer 的碰撞会被引擎作为不可见静态体处理通常可以触发 body_entered。如果你发现子弹穿墙可以按以下顺序排查检查子弹的CollisionShape2D是否覆盖了当前位置。检查子弹的monitoring是否开启。检查子弹的碰撞掩码是否包含地面所在的物理层。检查瓦片物理层是否只对特定层生效。为了更稳定我会推荐在子弹脚本中增加一个简单的射线检测# 文件路径scripts/bullet.gd 添加射线检测 onready var ray: RayCast2D $RayCast2D func _ready() - void: ray.target_position direction * 16 ray.force_raycast_update() func _physics_process(delta: float) - void: position direction * speed * delta ray.target_position direction * 16 ray.force_raycast_update() if ray.is_colliding(): queue_free()这种方式比较直接但要注意RayCast2D的target_position是相对自身节点的局部坐标所以方向向量要乘以一个长度这个长度决定检测距离。如果子弹速度很快建议把检测距离设置得比你单帧移动距离更大不然可能漏检测。更好的方案是使用move_and_collide做扫掠检测但本文先不过度复杂化。6. 敌人与伤害系统6.1 创建敌人场景敌人同样可以使用CharacterBody2D或者Area2D。这里用一个简单的CharacterBody2D敌人它能左右巡逻碰到玩家会让玩家扣血。新建场景根节点CharacterBody2D命名Enemy。添加子节点Sprite2DCollisionShape2DTimer用于反向巡逻敌人脚本# 文件路径scripts/enemy.gd extends CharacterBody2D export var speed: float 40.0 var direction : Vector2.RIGHT func _physics_process(delta: float) - void: velocity direction * speed move_and_slide() # 碰到墙壁反向 if is_on_wall(): direction * -1 func take_damage(_amount: int) - void: queue_free()这里用is_on_wall()判断是否撞墙。如果敌人卡在边缘不掉落可以加一个 RayCast2D 向下检测但简单演示就先用撞墙反向。6.2 玩家受击逻辑在player.gd中添加take_damage方法export var max_health: int 3 var health: int func _ready() - void: health max_health func take_damage(amount: int) - void: health - amount print(玩家生命值, health) if health 0: # 死亡处理 queue_free()然后连接敌人与玩家的碰撞。在敌人脚本中func _on_body_entered(body: Node2D) - void: if body.has_method(take_damage): body.take_damage(1)但这里有个细节CharacterBody2D的body_entered信号需要通过move_and_slide()触发所以敌人场景里需要连接信号。你可以在_ready()中连接func _ready() - void: body_entered.connect(_on_body_entered)如果你的敌人也是 Area2D可以用area_entered或body_entered处理。6.3 子弹与敌人的伤害连接在bullet.gd中处理敌人伤害时需要保证敌人确实有take_damage方法。如果你用的敌人脚本是enemy.gd并且继承CharacterBody2D那么子弹的area_entered无法触发因为敌人不是 Area2D。所以更通用的做法是在子弹中同时连接body_entered和area_entered并统一判断# 文件路径scripts/bullet.gd func _physics_process(delta: float) - void: position direction * speed * delta func _on_body_entered(body: Node2D) - void: if body.has_method(take_damage): body.take_damage(1) queue_free() func _on_area_entered(area: Area2D) - void: if area.has_method(take_damage): area.take_damage(1) queue_free()这里需要注意的是玩家也属于CharacterBody2D如果玩家也有take_damage方法子弹打到玩家自己身上就会误伤。所以需要在碰撞检测中排除玩家if body ! owner and body is Player: return更好的做法是设置子弹的collision_mask不让子弹检测玩家所在的层或者通过碰撞层区分。工程上推荐使用碰撞层来避免误伤但演示项目里也可以直接通过节点类型判断。6.4 生命值 UI为了能看到伤害效果可以用一个简单的Label显示生命值。新建场景HUD添加Label节点然后在主场景中实例化 HUD。HUD 脚本# 文件路径scripts/hud.gd extends CanvasLayer onready var health_label : $HealthLabel func update_health(value: int) - void: health_label.text 生命值: %d % value玩家在受伤时调用 HUD 的更新方法即可# 在主场景中获取 HUD 实例 onready var hud : $HUD func take_damage(amount: int) - void: health - amount hud.update_health(health)7. 主场景整合与运行7.1 主场景结构在主场景Main.tscn中组织以下节点Main (Node2D) ├── Background (TileMapLayer) ├── Ground (TileMapLayer) ├── Player (CharacterBody2D) ├── Enemy (CharacterBody2D) ├── BulletContainer (Node2D) └── HUD (CanvasLayer)将Player场景实例拖入Player节点Enemy场景实例拖入Enemy节点。BulletContainer 是一个普通 Node2D用于存放所有子弹实例方便统一管理。在player.gd中设置bullet_scene为子弹场景资源发射时把子弹加到BulletContainer下而不是Player下。修改后的发射代码onready var bullet_container: Node2D get_node(/root/Main/BulletContainer)如果场景路径固定可以直接这样写。更稳妥的方式是导出一个节点引用然后在编辑器中拖入export var bullet_container: Node2D7.2 运行前检查清单在按 F5 运行前逐项检查输入映射是否添加了move_left、move_right、jump、shoot。玩家场景中是否有CollisionShape2D。子弹场景中CollisionShape2D的形状尺寸是否合理。TileMapLayer 的 TileSet 是否包含物理碰撞信息。主场景是否设置了当前场景。子弹脚本是否连接了信号或直接在_ready中连接。很多新手在运行时发现“按下按键没反应”大概率是输入映射没配置好。如果发现“角色可以走动但不能发射”多半是bullet_scene没有赋值。7.3 预期效果运行后你应该能实现左右移动和跳跃。按自定义射击键发射子弹。子弹在碰到敌人后消失敌人扣血并消失。子弹碰到墙壁后消失。敌人碰撞玩家后玩家扣血。生命值 UI 实时刷新。如果出现子弹没有碰到敌人就穿过去优先检查子弹和敌人的碰撞层是否在同一层。Godot 的物理层默认只有第一层启用如果你给敌人手动设置了第 2 层子弹的掩码也要包含第 2 层。这一点是很多平台游戏“打不到怪”的常见原因。8. 常见问题与排查思路8.1 角色无法移动问题现象常见原因解决思路按方向键无反应输入映射未配置检查“项目设置 - 输入映射”角色移动非常慢speed 太小或 delta 处理错误调整 speed 数值角色一直朝一个方向走输入映射冲突检查按键是否重复绑定8.2 跳跃失效或无法落地问题现象常见原因解决思路无法跳跃is_on_floor()一直为 false检查碰撞层、瓦片物理多边形跳跃后无法控制空中没有水平摩擦使用move_toward或允许空中控制角色穿地TileSet 未设置物理层打开 TileSet 添加物理多边形8.3 子弹未生效问题现象常见原因解决思路无法发射bullet_scene为空在导出变量中指定子弹场景子弹发射但没移动direction未初始化调用setup(dir)设置方向子弹穿墙射线检测距离太短增加RayCast2D或使用扫掠检测子弹打不中敌人碰撞层不匹配检查子弹和敌人的碰撞层掩码8.4 敌人无法碰撞玩家问题现象常见原因解决思路玩家穿过敌人敌人没有碰撞形状给敌人添加 CollisionShape2D敌人不触发伤害body_entered未连接在_ready中连接信号敌人撞到玩家不掉血玩家没有take_damage方法给玩家脚本添加该方法8.5 运行时性能问题如果地图较大子弹数量多建议限制同时存在的子弹数量。增加一个最大子弹数判断const MAX_BULLETS : 20 func shoot() - void: if bullet_container.get_child_count() MAX_BULLETS: return这样可以避免短时间内生成大量 Area2D 节点导致卡顿。9. 最佳实践与工程建议9.1 物理图层规划建议从一开始就规划好物理层不要所有物体都放在第 1 层。一个清晰的规划可以避免很多误伤和穿透问题。例如可以这样分配物理层内容用途1地面/墙壁静态碰撞2玩家角色碰撞3敌人敌人碰撞4子弹投射物碰撞然后在每个节点的Collision属性中设置对应 layer 和 mask。这个方法在大项目中尤其重要可以显著减少“子弹打到自己”“敌人重叠”之类的问题。9.2 场景与脚本分离不要把全部代码堆到player.gd或main.gd中。建议每个实体独立场景玩家、敌人、子弹、HUD。每个场景有自己的脚本职责单一。跨场景通信通过信号或直接引用有限度使用。这样后续扩展武器系统、Boss 战、道具掉落时不需要改动原有基础代码。9.3 使用信号解耦在player.gd中可以定义信号让 HUD 或其他系统响应而不需要玩家直接持有 HUD 引用signal health_changed(current: int) func take_damage(amount: int) - void: health - amount health_changed.emit(health)然后在主场景中把 HUD 绑定到信号上$Player.health_changed.connect($HUD.update_health)这种设计更符合 Godot 场景树解耦的思想也更容易维护。9.4 关于跳跃手感平台游戏中跳跃手感非常影响体验。调整几个参数jump_velocity负数绝对值越大跳得越高。gravity越大下落越快跳跃更“重”。空中水平控制可以添加air_control系数让空中转向更灵活或更迟钝。跳跃缓冲在按下跳跃后的一小段时间内即使角色还没落地也预存跳跃动作手感会更好。这里给出一个简单的跳跃缓冲示例var jump_buffer_time : 0.0 func _physics_process(delta: float) - void: if Input.is_action_just_pressed(jump): jump_buffer_time 0.1 else: jump_buffer_time - delta if is_on_floor() and jump_buffer_time 0.0: velocity.y jump_velocity jump_buffer_time 0.09.5 代码注释与命名Godot 的 GDScript 不需要强制类型标注但推荐给关键节点和方法添加注释。项目越往后越大注释和命名清晰能节省大量调试时间。例如# 朝朝向方向发射子弹 func shoot() - void:9.6 版本控制与备份使用 Git 管理项目.gitignore建议忽略.godot/文件夹。这个目录是编辑器本地缓存提交到仓库容易造成冲突。Godot 4 项目中project.godot是核心配置文件需要纳入版本控制。10. 总结与学习路线本文围绕 Godot 4 的投射物平台游戏从 TileMapLayer 的基本用法开始完整实现了角色移动、跳跃、投射物发射、敌人碰撞、生命值 UI 等核心系统。你可以通过这个小项目掌握Godot 4 的 TileMapLayer 与 TileSet 配置方式。CharacterBody2D 的移动与物理交互。Area2D 投射物的生成、移动、碰撞检测。信号在跨场景通信中的使用。物理层规划与常见性能优化思路。如果想继续深入可以按下面的路线学习添加更多武器类型霰弹、三连发、蓄力攻击。增加敌人 AI巡逻、追击、攻击动画。制作 BOSS 战攻击模式、状态机、伤害阶段。引入动画播放器和动画树让角色动作更丰富。添加音效、粒子和屏幕震动提升打击感。对于实际项目来说最重要的是先保证核心循环稳定也就是“移动、跳跃、射击、反馈”这套基础体验没问题再去扩展内容。否则在复杂的敌人 AI 和多层地图下排错会指数级增加。如果你在跟着本文实践时遇到问题建议打开 Godot 编辑器的“调试 - 可见碰撞形状”先确认所有碰撞体都在正确位置。这是一个被低估但极其有效的调试技巧。希望这篇 Godot 4 投射物平台游戏实战笔记能帮到你也欢迎在评论区交流你遇到的坑我会根据反馈继续整理更多 Godot 4 实战内容。
返回列表