强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 2D2D 角色物理驱动的 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)

💡 提示ShapeCastRayCast 更适合检测近距离的宽范围碰撞(如近战攻击判定)。


物理最佳实践

实践说明
使用 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 + 施加冲量

⚠️ 常见陷阱

  1. 不要用 ConcavePolygon 做 RigidBody 的碰撞形状,性能极差
  2. freeze 替代了 mode 切换,Godot 4 API 变化
  3. RigidBody 的位置不要直接设置 position,使用 apply_impulselinear_velocity
  4. 高速物体可能穿透薄墙,使用 CCD 或射线检测
  5. Area 的 body_entered 不会检测其他 Area

扩展阅读