从2D到3D:用Godot4做你的第一个跳跃踩怪游戏(上)
从2D到3D:用Godot4打造你的首个跳跃踩怪游戏(上)
1. 3D游戏开发的基础准备
当你第一次从2D转向3D游戏开发时,最直观的变化就是坐标系统的扩展。在2D中我们熟悉的(x,y)坐标系,在3D中变成了(x,y,z)的三维空间。这种维度提升带来的不仅是技术层面的差异,更是一种思维方式的转变。
关键概念对比:
| 特性 | 2D环境 | 3D环境 |
|---|---|---|
| 坐标系 | (x,y) | (x,y,z) |
| 摄像机 | 固定视角或简单跟随 | 需要明确设置投影方式 |
| 碰撞检测 | 基于矩形或圆形 | 基于立方体或球体 |
| 移动逻辑 | 通常只考虑平面移动 | 需要考虑重力加速度 |
在Godot4中,3D节点的命名通常以"3D"结尾,这与2D节点形成明显区别。例如:
Sprite→MeshInstance3DArea2D→Area3DCollisionShape2D→CollisionShape3D
# 2D移动代码示例 velocity = Vector2(100, 0) move_and_slide() # 3D移动代码对应修改 velocity = Vector3(100, 0, 0) move_and_slide()提示:在3D开发中,Y轴通常代表垂直方向,这与2D中Y轴向下为正的约定不同,需要特别注意。
2. 搭建基础游戏场景
2.1 创建3D游戏世界
首先我们需要建立一个基本的3D环境。与2D不同,3D场景必须包含以下几个核心元素:
- 地面碰撞体:防止角色掉落
- 光照系统:让3D模型可见
- 摄像机:决定玩家视角
创建地面的步骤:
- 添加
StaticBody3D节点(重命名为Ground) - 为其添加
CollisionShape3D子节点 - 设置碰撞形状为
BoxShape3D,尺寸调整为(60,2,60) - 添加
MeshInstance3D作为视觉表现,同样使用BoxMesh
# 地面节点结构 Main (Node3D) └── Ground (StaticBody3D) ├── CollisionShape3D └── MeshInstance3D2.2 设置3D摄像机
3D摄像机比2D复杂得多,需要考虑投影方式(透视/正交)、视野角度和位置。对于平台跳跃游戏,正交投影往往更适合:
# 摄像机设置建议 Camera3D: Projection: Orthogonal Size: 19 Position: (0, 19, 19) Rotation: (-45, 0, 0)注意:正交投影消除了透视变形,使距离判断更准确,特别适合需要精确跳跃的游戏。
3. 创建可控制的3D角色
3.1 角色场景构建
3D角色通常使用CharacterBody3D作为根节点,这与2D中的CharacterBody2D对应。关键组件包括:
- 视觉表现:通过
MeshInstance3D显示3D模型 - 碰撞形状:
CollisionShape3D定义物理边界 - 控制脚本:处理移动和交互逻辑
# 玩家节点结构 Player (CharacterBody3D) └── Pivot (Node3D) └── Character (MeshInstance3D) └── CollisionShape3D3.2 输入系统配置
Godot的输入映射系统在3D中同样适用,但移动逻辑需要调整:
在项目设置中创建输入动作:
- move_left/move_right (对应x轴)
- move_forward/move_back (对应z轴)
- jump (对应y轴)
绑定按键时考虑多设备支持:
- 键盘:WASD+空格
- 手柄:左摇杆+按键A
3.3 3D移动逻辑实现
3D移动代码需要考虑重力影响和地面检测:
extends CharacterBody3D @export var speed = 14 @export var jump_force = 20 @export var fall_acceleration = 75 func _physics_process(delta): var direction = Vector3.ZERO # 获取输入方向 if Input.is_action_pressed("move_right"): direction.x += 1 if Input.is_action_pressed("move_left"): direction.x -= 1 if Input.is_action_pressed("move_back"): direction.z += 1 if Input.is_action_pressed("move_forward"): direction.z -= 1 # 处理移动和跳跃 if direction != Vector3.ZERO: direction = direction.normalized() $Pivot.look_at(position + direction) velocity.x = direction.x * speed velocity.z = direction.z * speed # 跳跃和重力 if is_on_floor() and Input.is_action_just_pressed("jump"): velocity.y = jump_force elif not is_on_floor(): velocity.y -= fall_acceleration * delta move_and_slide()技巧:使用
is_on_floor()检测地面接触,这是实现跳跃机制的关键。
4. 设计3D敌人系统
4.1 敌人场景配置
敌人同样使用CharacterBody3D,但行为模式与玩家不同:
- 基本结构与玩家类似
- 添加
VisibleOnScreenNotifier3D用于离屏销毁 - 移动逻辑改为自动向玩家方向移动
# 敌人节点结构 Mob (CharacterBody3D) └── Pivot (Node3D) └── Character (MeshInstance3D) └── CollisionShape3D └── VisibleOnScreenNotifier3D4.2 敌人行为脚本
敌人需要实现以下功能:
- 生成时面向玩家
- 随机移动方向和速度
- 离开屏幕后自动销毁
extends CharacterBody3D @export var min_speed = 10 @export var max_speed = 18 func initialize(start_position, player_position): look_at_from_position(start_position, player_position, Vector3.UP) rotate_y(randf_range(-PI/4, PI/4)) # 随机偏移角度 var random_speed = randi_range(min_speed, max_speed) velocity = Vector3.FORWARD * random_speed velocity = velocity.rotated(Vector3.UP, rotation.y) func _physics_process(delta): move_and_slide() func _on_visible_on_screen_notifier_3d_screen_exited(): queue_free()4.3 碰撞检测优化
3D碰撞检测比2D更消耗资源,可以通过以下方式优化:
- 使用简单的碰撞形状(球体或立方体)
- 合理设置物理层(Physics Layers)
- 及时销毁不可见的敌人
# 碰撞层设置示例 CollisionLayer: - 1: Player - 2: Enemy - 3: Ground CollisionMask: Player: 2,3 # 只与敌人和地面碰撞 Enemy: 1,3 # 只与玩家和地面碰撞在游戏测试过程中,我发现3D碰撞盒的大小需要特别注意 - 太大玩家会觉得不公平,太小又会导致难以命中。经过多次调整,最终确定碰撞盒比视觉模型小约10%效果最佳。
