10. 3D物理系统
2026/4/14大约 4 分钟
3D物理系统
物理系统让你的游戏世界有"真实的运动规则"——物体会掉落、碰撞、弹开、滑动。这一章回答:刚体、碰撞体、物理材质怎么用?和 2D 物理有什么不同?
3D 物理和 2D 物理的区别
核心概念完全一样,只是多了一个维度(Z 轴):
| 2D 节点 | 3D 对应节点 | 作用 |
|---|---|---|
| CharacterBody2D | CharacterBody3D | 玩家角色(手动控制移动) |
| RigidBody2D | RigidBody3D | 受物理力影响的物体(球、箱子) |
| StaticBody2D | StaticBody3D | 不动的物体(地面、墙壁) |
| CollisionShape2D | CollisionShape3D | 定义碰撞区域的形状 |
三种物理体类型
1. StaticBody3D — 静态物体
不会移动的物体。比如地面、墙壁、固定不动的建筑。
- 不受力的影响
- 性能消耗最低
- 适合做游戏场景的"舞台"
2. RigidBody3D — 刚体
受力影响的物体。比如滚动的球、掉落的箱子、被炸飞的碎片。
- 受重力影响
- 会和其他物体碰撞
- 可以设置质量、弹性、摩擦力
3. CharacterBody3D — 角色体
用于玩家控制的角色。不受物理力的影响,但会检测碰撞。
- 不受力的影响(你手动控制移动)
- 会和静态物体、刚体碰撞
- 是做玩家角色和 NPC 的首选
碰撞体(CollisionShape3D)
光有物理体节点还不够,还需要给它添加一个"碰撞形状"来定义它的碰撞范围。
常用碰撞形状:
| 形状 | 节点 | 适用场景 |
|---|---|---|
| 盒子 | BoxShape3D | 墙壁、箱子、平台 |
| 球体 | SphereShape3D | 球、弹丸、简单角色 |
| 胶囊 | CapsuleShape3D | 人形角色 |
| 圆柱 | CylinderShape3D | 柱子、树干 |
| 凸包 | ConvexPolygonShape3D | 复杂但近似简化的物体 |
| 三角网格 | ConcavePolygonShape3D | 精确的复杂形状(仅用于 StaticBody3D) |
新手建议
用最简单的形状(球体、盒子、胶囊)做碰撞体。复杂形状性能差而且调试困难。人形角色用胶囊体就够了。
移动角色(CharacterBody3D)
3D 角色的移动方式和 2D 类似,但需要处理三个方向:
C#
// C#: 3D 角色移动
public override void _PhysicsProcess(double delta)
{
Vector3 velocity = Velocity;
// 获取输入方向
Vector3 inputDir = new Vector3(
Input.GetVector("move_left", "move_right", "move_forward", "move_backward").X,
0,
Input.GetVector("move_left", "move_right", "move_forward", "move_backward").Y
);
// 根据摄像机方向调整移动方向
Vector3 direction = (Transform.Basis * new Vector3(inputDir.X, 0, inputDir.Z)).Normalized();
if (direction != Vector3.Zero)
{
velocity.X = direction.X * Speed;
velocity.Z = direction.Z * Speed;
}
else
{
velocity.X = Mathf.MoveToward(velocity.X, 0, Speed);
velocity.Z = Mathf.MoveToward(velocity.Z, 0, Speed);
}
// 重力
if (!IsOnFloor())
velocity.Y -= Gravity * (float)delta;
// 跳跃
if (Input.IsActionJustPressed("jump") && IsOnFloor())
velocity.Y = JumpVelocity;
Velocity = velocity;
MoveAndSlide();
}GDScript
# GDScript: 3D 角色移动
extends CharacterBody3D
@export var speed = 5.0
@export var jump_velocity = 4.5
@export var gravity = 9.8
func _physics_process(delta):
var velocity = velocity
# 获取输入方向
var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
else:
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
# 重力
if not is_on_floor():
velocity.y -= gravity * delta
# 跳跃
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
velocity = velocity
move_and_slide()射线检测(RayCast3D)
射线检测是 3D 游戏中非常常用的技术——从一点发出一条看不见的线,检测它碰到了什么。
用途:
- 射击游戏的子弹命中检测
- 检测角色脚下是否是地面
- AI 的视线检测
- 鼠标点击 3D 物体
C#
// C#: 射线检测
var spaceState = GetWorld3D().DirectSpaceState;
var query = PhysicsRayQueryParameters3D.Create(from, to);
var result = spaceState.IntersectRay(query);
if (result.Count > 0)
{
// 命中了某个物体
var hitPosition = (Vector3)result["position"];
var hitCollider = (Node)result["collider"];
}GDScript
# GDScript: 射线检测
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(from, to)
var result = space_state.intersect_ray(query)
if result:
# 命中了某个物体
var hit_position = result["position"]
var hit_collider = result["collider"]物理材质
物理材质控制物体碰撞时的"感觉":
| 属性 | 作用 | 效果 |
|---|---|---|
Friction | 摩擦力 | 高=不滑,低=冰面 |
Bounce | 弹性 | 高=弹力球,低=铅球 |
碰撞层和碰撞掩码
3D 物理使用"层"来控制哪些物体之间可以碰撞:
- Collision Layer:这个物体在哪一层
- Collision Mask:这个物体能和哪些层碰撞
典型设置:
| 对象 | Layer | Mask |
|---|---|---|
| 玩家 | 1(玩家层) | 2,3,4(地面、敌人、道具) |
| 敌人 | 2(敌人层) | 1,3(玩家、地面) |
| 地面 | 3(环境层) | (什么都不碰) |
| 子弹 | 4(子弹层) | 2(只碰敌人) |
本章小结
- StaticBody3D = 不动的,RigidBody3D = 受力的,CharacterBody3D = 手动控制的
- 碰撞体用简单形状(盒子、球、胶囊)
- 移动角色用
MoveAndSlide() - 射线检测是 3D 开发的瑞士军刀
- 用碰撞层和掩码控制"谁能碰谁"
