Godot 4 GDScript 教程 / 物理系统(Area/RigidBody)
物理系统(Area/RigidBody)
概述
Godot 4 的物理系统基于 Godot Physics 引擎(默认替代了 Bullet),提供了丰富的碰撞检测与刚体模拟能力。本节涵盖所有物理节点的使用方法与最佳实践。
| 节点 | 用途 |
|---|---|
RigidBody3D/2D | 受物理模拟控制的刚体 |
StaticBody3D/2D | 静止不动的碰撞体(墙壁、地板) |
CharacterBody3D/2D | 由代码控制的角色体 |
AnimatableBody3D/2D | 由动画/代码驱动的可碰撞体 |
Area3D/2D | 区域检测(触发器) |
RigidBody3D/2D
RigidBody 由物理引擎完全控制,适用于需要真实物理表现的对象(球、箱子、碎片等)。
基本用法
extends RigidBody3D
@export var bounce_force: float = 10.0
func _ready():
# 质量(千克)
mass = 2.0
# 重力缩放
gravity_scale = 1.0
# 线性阻尼
linear_damp = 0.1
# 角速度阻尼
angular_damp = 0.05
# 碰撞后是否持续旋转
can_sleep = true
# 通过施加力来推动刚体
func push(direction: Vector3):
apply_central_impulse(direction * bounce_force)
# 在指定位置施加力
func hit_at_position(hit_pos: Vector3, force: Vector3):
apply_impulse(force, hit_pos - global_position)
RigidBody 模式对比
| 模式 | 说明 | 适用场景 |
|---|---|---|
RIGID | 默认物理模拟 | 球、箱子 |
STATIC | 静止不动 | 由代码切换 |
KINEMATIC | 不受物理力影响 | 动画驱动物体 |
CHARACTER | 类似角色体 | 物理驱动的敌人 |
CHARACTER 2D | 2D 角色 | 物理驱动的 2D 敌人 |
extends RigidBody3D
# 切换模式
func freeze_body():
freeze = true # Godot 4 新属性,替代 mode 切换
func unfreeze():
freeze = false
⚠️ 注意:Godot 4 中不再用 mode 属性切换模式,而是使用 freeze 布尔属性冻结刚体。
StaticBody3D/2D
StaticBody 不会移动,用于墙壁、地板、平台等:
# StaticBody 通常不需要脚本
# 通过编辑器设置碰撞形状和物理材质即可
# 如果需要移动平台,使用 AnimatableBody3D
extends AnimatableBody3D
@export var move_speed: float = 2.0
@export var move_distance: float = 5.0
var start_pos: Vector3
var time: float = 0.0
func _ready():
start_pos = global_position
func _physics_process(delta):
time += delta
var offset = sin(time * move_speed) * move_distance
global_position = start_pos + Vector3(0, offset, 0)
💡 提示:移动平台请使用 AnimatableBody3D/2D(Godot 4 新增),它会自动将运动传递给站在上面的角色。
Area3D/2D 区域检测
Area 用于检测物体进入/离开区域,不参与物理碰撞。
拾取物实现
extends Area3D
@export var heal_amount: int = 25
func _ready():
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node):
if body.is_in_group("player"):
body.heal(heal_amount)
queue_free() # 拾取后消失
危险区域实现
extends Area3D
@export var damage_per_second: float = 10.0
var bodies_in_area: Array[Node3D] = []
func _ready():
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node3D):
if body.has_method("take_damage"):
bodies_in_area.append(body)
func _on_body_exited(body: Node3D):
bodies_in_area.erase(body)
func _process(delta):
for body in bodies_in_area:
if body.has_method("take_damage"):
body.take_damage(damage_per_second * delta)
Area 属性说明
| 属性 | 说明 |
|---|---|
monitoring | 是否监测其他物体进入 |
monitorable | 是否可被其他 Area 监测 |
priority | 碰撞处理优先级 |
gravity_space_override | 区域内重力覆盖模式 |
gravity | 区域内重力值 |
linear_damp_space_override | 线性阻尼覆盖 |
碰撞形状
每个物理节点都需要一个或 CollisionShape3D/2D 子节点:
# 代码创建碰撞形状
extends RigidBody3D
func _ready():
var shape = CollisionShape3D.new()
var box = BoxShape3D.new()
box.size = Vector3(1, 1, 1)
shape.shape = box
add_child(shape)
| 形状类型 | 说明 | 性能 |
|---|---|---|
BoxShape3D | 盒子形状 | ⭐⭐⭐ 最快 |
SphereShape3D | 球体 | ⭐⭐⭐ 快 |
CapsuleShape3D | 胶囊体 | ⭐⭐⭐ 快 |
CylinderShape3D | 圆柱体 | ⭐⭐ |
WorldBoundaryShape3D | 无限平面 | ⭐⭐⭐ |
ConvexPolygonShape3D | 凸多面体 | ⭐⭐ |
ConcavePolygonShape3D | 凹多面体 | ⭐ 慢 |
⚠️ 注意:ConcavePolygonShape3D 仅用于静态物体,不要用于 RigidBody,性能很差。
碰撞层与掩码
碰撞层(Layer)和掩码(Mask)决定了哪些物体会相互碰撞:
# 设置碰撞层
func setup_collision():
# 对象所在层(1~32)
collision_layer = 1 # 第 1 层:玩家
# 检测哪些层(掩码)
collision_mask = 0b110 # 第 2、3 层:敌人、地形
| 推荐层 | 用途 |
|---|---|
| 1 | 玩家 |
| 2 | 敌人 |
| 3 | 地形/墙壁 |
| 4 | 拾取物 |
| 5 | 子弹 |
| 6 | 触发器 |
💡 提示:在项目设置 Layer Names → 3D Physics 中为每层命名,方便在编辑器中管理。
物理材质(PhysicsMaterial)
物理材质控制摩擦力和弹性:
extends RigidBody3D
func _ready():
var phys_mat = PhysicsMaterial.new()
phys_mat.friction = 0.3 # 摩擦力(0~1)
phys_mat.bounce = 0.8 # 弹性(0~1)
phys_mat.rough = false # 粗糙表面(影响摩擦计算)
phys_mat.absorbent = false # 吸收能量
physics_material_override = phys_mat
射线检测 RayCast3D/2D
RayCast 用于检测射线路径上的碰撞体:
extends Node3D
@onready var ray: RayCast3D = $RayCast3D
func _physics_process(_delta):
# 强制更新(默认仅在 _physics_process 中自动更新)
ray.force_raycast_update()
if ray.is_colliding():
var hit_point = ray.get_collision_point()
var hit_normal = ray.get_collision_normal()
var hit_object = ray.get_collider()
print("击中: ", hit_object.name, " 位置: ", hit_point)
武器射击射线
extends Node3D
@export var damage: float = 25.0
@export var range_distance: float = 100.0
@onready var ray: RayCast3D = $RayCast3D
func shoot():
ray.target_position = Vector3(0, 0, -range_distance)
ray.force_raycast_update()
if ray.is_colliding():
var target = ray.get_collider()
if target.has_method("take_damage"):
target.take_damage(damage)
# 在弹孔位置生成特效
var hit_pos = ray.get_collision_point()
var hit_normal = ray.get_collision_normal()
spawn_bullet_hole(hit_pos, hit_normal)
形状查询 ShapeCast3D
ShapeCast 是 Godot 4 新增节点,用形状代替射线进行检测:
extends Node3D
@onready var shape_cast: ShapeCast3D = $ShapeCast3D
func _physics_process(_delta):
shape_cast.force_shapecast_update()
# 获取所有碰撞结果
for i in shape_cast.get_collision_count():
var collider = shape_cast.get_collider(i)
var point = shape_cast.get_point(i)
var normal = shape_cast.get_normal(i)
print("碰撞: ", collider.name)
💡 提示:ShapeCast 比 RayCast 更适合检测近距离的宽范围碰撞(如近战攻击判定)。
物理最佳实践
| 实践 | 说明 |
|---|---|
使用 CharacterBody | 而非 RigidBody 做角色控制 |
| 碰撞形状尽量简单 | 用 Box/Sphere 代替 Concave |
| 避免过小物体 | 小于 0.1 米的物体容易穿透 |
使用 freeze | 不需要物理模拟时冻结 |
| 固定物理帧率 | 保持默认 60 FPS |
| 使用层/掩码过滤 | 减少不必要的碰撞计算 |
子弹物理
高速子弹(射线方式)
extends Node3D
@export var speed: float = 500.0
@export var damage: float = 10.0
@export var max_range: float = 200.0
var direction: Vector3
var distance_traveled: float = 0.0
func _physics_process(delta):
var move_amount = speed * delta
var ray_result = direct_space_state.intersect_ray(
global_position,
global_position + direction * move_amount
)
if ray_result:
hit_target(ray_result)
queue_free()
return
global_position += direction * move_amount
distance_traveled += move_amount
if distance_traveled >= max_range:
queue_free()
慢速子弹(RigidBody 方式)
extends RigidBody3D
@export var damage: float = 15.0
func _ready():
gravity_scale = 0.5 # 有轻微重力
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node):
if body.has_method("take_damage"):
body.take_damage(damage)
queue_free()
游戏开发场景
| 场景 | 推荐方案 |
|---|---|
| 抛射物(火箭弹) | RigidBody3D + 物理材质 |
| 拾取物 | Area3D + body_entered 信号 |
| 触发机关 | Area3D + 区域覆盖 |
| 武器射击 | RayCast3D |
| 近战攻击 | ShapeCast3D |
| 爆炸碎片 | RigidBody3D + 施加冲量 |
⚠️ 常见陷阱
- 不要用 ConcavePolygon 做 RigidBody 的碰撞形状,性能极差
freeze替代了mode切换,Godot 4 API 变化- RigidBody 的位置不要直接设置
position,使用apply_impulse或linear_velocity - 高速物体可能穿透薄墙,使用 CCD 或射线检测
- Area 的
body_entered不会检测其他 Area