刚接触Godot4的3D物理系统时,我花了整整三天才搞明白那些看似简单的碰撞体设置。记得第一次测试角色移动时,角色直接穿墙而过,那种挫败感至今难忘。Godot4的物理引擎其实非常强大,但需要理解几个核心概念才能玩转。
CharacterBody3D是3D角色控制的灵魂节点。与RigidBody3D不同,它不会自动响应物理力,而是完全由代码控制移动。这种设计让开发者可以精确控制角色行为,特别适合平台跳跃类游戏。实测下来,它的碰撞检测精度相当可靠,即使用高速移动也不会出现穿模问题。
碰撞形状的设置往往让新手头疼。我的经验是:对于角色,使用胶囊体(CapsuleShape3D)比球体更合适。这样角色在斜坡移动时会更稳定。记得有次项目deadline前,就因为用了球体碰撞导致角色在楼梯上不停打滑,熬夜重做碰撞体的经历实在难忘。
物理层的管理是另一个关键点。Godot4允许通过Layer和Mask系统精细控制哪些物体应该发生交互。比如设置玩家层(第1层)与敌人层(第2层)的碰撞关系时,一定要在项目设置里预先规划好各层的交互规则。我建议在项目初期就建立完整的层对应表,不然后期调整会非常痛苦。
重力系统的实现也有讲究。官方文档建议使用9.8米/秒²的标准重力加速度,但在平台游戏中,适当增大这个值会让跳跃手感更干脆。我的常用配置是12-15之间,具体数值需要反复测试调整。记得保存不同场景的重力预设,方便快速切换测试。
提示:调试物理时按F3打开Debug菜单,勾选Visible Collision Shapes,可以实时查看碰撞体状态
八方向移动看似简单,但要让手感顺滑需要处理很多细节。去年做的一个商业项目中,我们迭代了7个版本才确定最终移动方案。基础移动代码虽然官方文档有提供,但实际开发中需要考虑更多边界情况。
首先来看基础移动代码的优化版本:
gdscript复制extends CharacterBody3D
@export var move_speed := 5.0
@export var acceleration := 10.0
@export var deceleration := 15.0
@export var air_control := 0.3
var _velocity := Vector3.ZERO
var _snap_vector := Vector3.DOWN
func _physics_process(delta: float) -> void:
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
# 地面移动控制
if is_on_floor():
if direction.length() > 0.1:
_velocity = _velocity.lerp(direction * move_speed, acceleration * delta)
else:
_velocity = _velocity.move_toward(Vector3.ZERO, deceleration * delta)
else:
# 空中控制
if direction.length() > 0.1:
_velocity.x = move_toward(_velocity.x, direction.x * move_speed, air_control * acceleration * delta)
_velocity.z = move_toward(_velocity.z, direction.z * move_speed, air_control * acceleration * delta)
# 应用重力
_velocity.y -= gravity * delta
# 移动执行
var was_on_floor := is_on_floor()
move_and_slide()
# 落地检测
if !was_on_floor and is_on_floor():
_snap_vector = Vector3.DOWN
这段代码改进点包括:
斜坡处理是另一个常见痛点。Godot4的move_and_slide()默认可以处理斜坡,但对于陡坡需要额外检查。我通常会增加如下代码:
gdscript复制var max_slope_angle := deg_to_rad(45.0)
if is_on_floor_only() and get_floor_normal().angle_to(Vector3.UP) > max_slope_angle:
_velocity.y = -gravity * delta
摄像机跟随同样影响移动体验。第三人称摄像机应该采用弹簧臂(SpringArm)模式,这里有个实用实现:
gdscript复制extends SpringArm3D
@export var mouse_sensitivity := 0.1
@export var min_pitch := -30.0
@export var max_pitch := 70.0
func _ready() -> void:
set_as_top_level(true)
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
rotation_degrees.x -= event.relative.y * mouse_sensitivity
rotation_degrees.x = clamp(rotation_degrees.x, min_pitch, max_pitch)
rotation_degrees.y -= event.relative.x * mouse_sensitivity
实现一个让人上瘾的跳跃系统需要平衡真实性和游戏性。马里奥之父宫本茂曾说过:"游戏物理不必真实,但要感觉正确"。在Godot4中实现这种"感觉正确"的跳跃,需要考虑多个参数协同工作。
基础跳跃实现:
gdscript复制@export var jump_height := 1.5
@export var jump_time_to_peak := 0.4
@export var jump_time_to_descent := 0.3
var _jump_velocity := 0.0
var _gravity := 0.0
func _ready() -> void:
# 根据跳跃高度和时间计算初速度和重力
_gravity = (2 * jump_height) / pow(jump_time_to_peak, 2)
_jump_velocity = _gravity * jump_time_to_peak
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("jump") and is_on_floor():
_velocity.y = _jump_velocity
_snap_vector = Vector3.ZERO
# 短按长按跳跃
if Input.is_action_just_released("jump") and _velocity.y > 0:
_velocity.y *= 0.5
这个系统通过jump_height和jump_time_to_peak自动计算合适的初速度和重力值。实测发现,跳起到最高点时间控制在0.3-0.5秒,下落时间稍长于上升时间,手感最佳。
二段跳实现需要状态跟踪:
gdscript复制var _can_double_jump := false
func _physics_process(delta: float) -> void:
if is_on_floor():
_can_double_jump = true
if Input.is_action_just_pressed("jump"):
if is_on_floor():
_velocity.y = _jump_velocity
elif _can_double_jump:
_velocity.y = _jump_velocity * 0.9
_can_double_jump = false
蹬墙跳是平台游戏常见机制,实现要点:
gdscript复制var _wall_normal := Vector3.ZERO
func _check_wall() -> bool:
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(
global_position,
global_position + Vector3(0, 0.5, 0),
collision_mask
)
var result := space_state.intersect_ray(query)
if result:
_wall_normal = result["normal"]
return true
return false
func _do_wall_jump() -> void:
if Input.is_action_just_pressed("jump") and _check_wall():
_velocity = _wall_normal * move_speed
_velocity.y = _jump_velocity
游戏世界的沉浸感很大程度上来自角色与环境的物理交互。在最近的一个项目中,我们通过简单的物理反馈使游戏评分提升了27%。Godot4提供了多种实现环境交互的方式。
推箱子是经典的物理交互案例。实现要点:
gdscript复制# 在玩家脚本中添加推动逻辑
func _push_objects() -> void:
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
if collision.get_collider() is RigidBody3D:
var body := collision.get_collider() as RigidBody3D
var push_dir := -collision.get_normal()
var push_pos := collision.get_position()
body.apply_impulse(push_dir * move_speed * 0.1, push_pos - body.global_position)
可破坏物体实现方案:
gdscript复制extends RigidBody3D
@export var intact_mesh: MeshInstance3D
@export var broken_mesh: Node3D
@export var break_force := 5.0
func _on_body_entered(body: Node) -> void:
if body.linear_velocity.length() > break_force:
intact_mesh.visible = false
broken_mesh.visible = true
set_collision_layer_value(1, false)
for child in broken_mesh.get_children():
if child is RigidBody3D:
child.apply_impulse(
Vector3(randf_range(-1,1), randf_range(0,1), randf_range(-1,1)) * 2.0,
child.global_position - global_position
)
物理材质对游戏手感影响巨大。建议为不同表面类型创建不同物理材质:
gdscript复制# 创建物理材质资源
var ice_material := PhysicsMaterial.new()
ice_material.friction = 0.1
ice_material.bounce = 0.7
# 应用到碰撞形状
$CollisionShape3D.shape.material = ice_material
动态物理修改可以创造有趣玩法。比如减速区域实现:
gdscript复制extends Area3D
@export var speed_factor := 0.5
func _on_body_entered(body: Node) -> void:
if body is CharacterBody3D:
body.set_meta("speed_factor", speed_factor)
func _on_body_exited(body: Node) -> void:
if body is CharacterBody3D and body.has_meta("speed_factor"):
body.remove_meta("speed_factor")
然后在角色移动代码中加入:
gdscript复制var speed_modifier := 1.0
if has_meta("speed_factor"):
speed_modifier = get_meta("speed_factor")
_velocity = _velocity.lerp(direction * move_speed * speed_modifier, acceleration * delta)