Defold 物理引擎:碰撞体、回调、关节与性能优化

系统讲解 Defold 基于 Box2D 的物理引擎:碰撞体类型与形状、碰撞与触发器回调、关节与马达、物理层过滤及性能优化策略。

Defold 内置基于 Box2D 的 2D 物理引擎,同时支持基于 Bullet 的 3D 物理。(Defold physics docs) 物理在 Defold 中是「组件级」能力:给 Game Object 挂一个 CollisionObject 组件,就能参与世界模拟、碰撞检测与力/冲量交互。本文从组件配置讲到回调、关节与性能,是物理方向的深度实践篇。

前置知识建议先读 Defold 游戏引擎介绍,脚本部分参考 Defold Lua 脚本系统深入。

一、物理世界与碰撞体类型

1. 物理世界配置

物理世界在 game.project 的 [physics] 段配置:

[physics]
gravity = 0, -980        -- 重力(2D 中沿 Y 轴向下)
gravity_scale = 1.0      -- 全局重力缩放
max_sub_steps = 4        -- 每帧物理子步数
fixed_dt = 1.0 / 60.0    -- 物理固定步长
velocity_iterations = 8  -- 速度求解迭代
position_iterations = 1  -- 位置求解迭代

fixed_dt 决定物理模拟的精度与开销;max_sub_steps 越大越能容忍高帧率下的穿插,但更耗 CPU。

2. CollisionObject 的四种类型

一个 CollisionObject 组件必须指定 Type:

  • Dynamic(动态):受重力、力和碰撞影响,可移动、可旋转。用于玩家、敌人、可推动物体
  • Kinematic(运动学):不受力,但会推挤其他物体;由脚本显式移动。用于平台、升降梯
  • Static(静态):固定不动,最省性能。用于地面、墙壁
  • Trigger(触发器):只检测不碰撞,物体可穿透。用于区域检测、拾取范围

编辑器里选择类型后,再为其添加形状(Shape)。

3. 形状类型

CollisionObject 可挂多种形状:

  • Box:矩形,性能最好,用于方块平台
  • Sphere:圆形,滚动、投掷物常用
  • Capsule:胶囊形,适合人形角色
  • Convex Hull / Concave:凸/凹多边形,贴合不规则轮廓
  • Tile Grid:自动按 Tilemap 瓦片生成,关卡碰撞首选

二、碰撞体形状与属性

1. 添加形状的操作

在 Scene 面板选中 CollisionObject → 右键 → Add Shape → 选形状类型。Properties 面板可编辑:

  • Box:Width、Height
  • Sphere:Diameter
  • Convex Hull:各顶点 X/Y

形状位置可拖拽微调,建议形状略微小于可见像素(如玩家比贴图小 10%),减少「卡墙角」感。

2. 物理属性

CollisionObject 的关键物理属性:

  • Mass:质量,影响受力响应(动态体由密度自动推导)
  • Friction:摩擦系数(0-1),影响滑动阻力
  • Restitution:弹性(0-1),1 为完全弹性
  • Linear Damping / Angular Damping:线性/角向阻尼,模拟空气阻力
  • Linear Velocity / Angular Velocity:初始线/角速度
-- 脚本中读取/设置物理属性
local mass = go.get("#collisionobject", "mass")
go.set("#collisionobject", "friction", 0.5)

3. 用脚本施力与冲量

对动态体最常用的两类操作:

-- 持续力(每帧调用)
msg.post("#collisionobject", "apply_force", { force = vmath.vector3(0, 800, 0) })

-- 瞬时冲量(一次性)
msg.post("#collisionobject", "apply_impulse", { impulse = vmath.vector3(0, 500, 0) })

-- 直接设置线速度(运动学体常用)
go.set("#collisionobject", "linear_velocity", vmath.vector3(100, 0, 0))

注意 apply_force 是「每帧累计」的,若在 update 中调用应乘以 dt 修正,或改用冲量。

4. 运动学体的移动

Kinematic 物体由脚本驱动,常见模式:

function update(self, dt)
    local pos = go.get_position()
    pos.x = pos.x + self.speed * dt
    -- 反向
    if pos.x > self.max_x then self.speed = -math.abs(self.speed) end
    if pos.x < self.min_x then self.speed = math.abs(self.speed) end
    go.set_position(pos)
end

运动学体会推挤动态体但自己不受力,适合做「移动平台」「推箱机关」。

三、碰撞回调与消息

1. 碰撞响应消息

当两个物理体发生接触,双方会收到消息:

function on_message(self, message_id, message, sender)
    if message_id == hash("collision_response") then
        -- message.position:接触点
        -- message.normal:法线方向
        -- message.other_id:对方对象 id
        -- message.other_group:对方组名
        print("碰撞于", message.position, "来自组", message.other_group)
    end
end

碰撞消息的触发前提是两物体都挂 CollisionObject,且组间「有碰撞掩码」。默认每帧都触发(持续接触),如需「仅开始时触发一次」,可配合状态标记。

2. 触发器消息

Trigger 类型或勾选 Trigger 属性的碰撞体,收到的是 trigger_response:

function on_message(self, message_id, message, sender)
    if message_id == hash("trigger_response") then
        -- 物体进入/离开触发器
        if message.enter then
            print("进入区域:", message.other_id)
        end
    end
end

触发器典型用途:拾取范围、毒区、通关区域、敌人警戒圈。

3. 碰撞回调的精度控制

  • 物理回调基于固定步长(fixed_dt),与渲染帧率解耦;高速物体可启用 continuous 连续碰撞检测减少穿透
  • 触发器回调同样按固定步长触发,不要期望「精确到渲染帧」的进入/离开

4. 处理「碰撞后的事件」

常见的「落地检测」写法:

function init(self)
    self.on_ground = false
end

function on_message(self, message_id, message, sender)
    if message_id == hash("collision_response") then
        if message.other_group == hash("ground") then
            self.on_ground = true
        end
    end
end

function update(self, dt)
    if self.on_ground then
        -- 允许跳跃
    end
    self.on_ground = false   -- 每帧重置,依靠持续碰撞重新置真
end

四、关节与马达

1. 关节类型总览

Defold 通过 Joint 组件实现物体间的约束连接。(Defold joints) 常用关节:

  • Distance:弹簧距离约束,保持两点距离(可调频率/阻尼做柔度)
  • Revolute:旋转铰链,绕一点转动(可做门、摇杆)
  • Prismatic:棱柱滑动,沿一条轴直线移动
  • Weld:焊接,两物体固连
  • Friction:摩擦关节,模拟粗糙接触面
  • Wheel:轮子,车辆悬挂与滚动

2. 创建关节

编辑器操作:选中两个 Game Object → 右键 → Add Joint → 选类型 → 指定两个物体的 CollisionObject 与锚点。Properties 中可设 Enable Motor、Motor Speed、Max Motor Torque 等。

-- 脚本控制马达
msg.post("#joint", "set_motor_speed", { speed = 120 })
msg.post("#joint", "set_max_torque", { torque = 100 })

3. 关节脚本接口

常用关节消息:

-- 设置弹簧目标长度
msg.post("#distance_joint", "set_length", { length = 40 })

-- 启用/禁用关节
msg.post("#joint", "enable")
msg.post("#joint", "disable")

关节在编辑阶段配置静态结构(门、链条、车辆),运行期通过消息微调动态行为。

五、物理层与过滤

1. 组(Group)与掩码(Mask)

Defold 物理过滤基于 Group 与 Mask:

  • 每个 CollisionObject 属于一个 Group(如 player、enemy、ground)
  • Mask 声明「我要与哪些组碰撞」
  • 双方掩码都包含对方组时才产生碰撞响应

编辑器 Properties 中的 Group 下拉选择,Mask 勾选。

2. 用过滤矩阵管理交互

典型设置:

对象GroupMask
玩家子弹player_bulletenemy, obstacle
玩家身体playerground, enemy, trigger_item
敌人enemyplayer, ground, player_bullet
地面groundplayer, enemy, bullet

这样子弹不打队友、敌人吃子弹不吃友军,规则一目了然且零脚本成本。

3. 脚本动态修改

运行期可按需调整:

-- 无敌:清空掩码
go.set("#collisionobject", "mask", vmath.vector4(0, 0, 0, 0))

-- 恢复
go.set("#collisionobject", "mask", vmath.vector4(1, 1, 1, 1))

注意 Group/Mask 用 4 分量向量表示最多 4 个组(默认组数可在 game.project 中增加 max_collision_groups)。

六、性能优化

1. 减少模拟对象

物理是 CPU 热点之一,优化顺序:

  • 用 Static 而非 Dynamic 表示不动的物体
  • 小物体、装饰不要挂物理,用触发器或纯渲染
  • 远离玩家的物理体可 go.disable("#collisionobject"),进视野再启用
function on_message(self, message_id, message, sender)
    if message_id == hash("enter_view") then
        msg.post("#collisionobject", "enable")
    elseif message_id == hash("exit_view") then
        msg.post("#collisionobject", "disable")
    end
end

2. 形状选择与迭代参数

  • Box 与 Sphere 的求解最快,Convex Hull 次之,避免大尺寸凹多边形
  • velocity_iterations / position_iterations 过高会拖慢全场景,调试时从低到高尝试
  • 大量同构物体(如碎石)考虑合并成一个 CollisionObject + 多个 Shape,而非多个组件

3. 固定步长与表现层插值

物理按 fixed_dt 步进,渲染帧率更高时物体可能「一跳一跳」。用两帧插值缓解:

local prev_pos = go.get_position()
function update(self, dt)
    local cur_pos = go.get_position()
    local render_pos = vmath.lerp(0.5, prev_pos, cur_pos)
    go.set_position(render_pos)
    prev_pos = cur_pos
end

4. 命中判定性能

高频命中(机枪每帧多颗子弹)不要用物理碰撞,改用射线:

-- 射线检测(Box2D 世界)
local hit = physics.raycast(from, to)
if hit then
    print("命中对象:", hit.game_object, "距离:", hit.fraction)
end

射线比「生成碰撞体再检测」便宜得多,是 FPS/Top-down 射击的标准做法。

七、常见问题排查

1. 物体穿地

原因多为:速度过快 + 子步数不足、或动态体质量过大。处理:提高 max_sub_steps、给高速物体开启连续检测、或减小单步速度。

2. 碰撞没触发

检查顺序:双方是否都挂 CollisionObject → Group/Mask 是否互相包含 → 是否被 disable → 是否物理世界未启用(physics.enable 默认开)。Console 中搜索 collision 相关警告可快速定位。

3. 物体抖个不停

原因多为:Kinematic 每帧直接 set_position 与物理解算冲突、或 Restitution 过高。平台物体推荐用 Kinematic + go.set_position 并在接触帧内修正;弹性调低到 0-0.2。

八、总结

Defold 的物理能力虽然基于成熟稳定的 Box2D,但把「配置、过滤、关节、性能」串起来的实践才是关键:先用 Static/Trigger 分流、再用 Group/Mask 表达交互规则、关节只给必要的动态结构、最后用射线与禁用机制保住性能。这套思路与 3D 扩展 中 Bullet 物理的用法同源,可以举一反三。

配合 GUI/UI 开发 做血条与命中反馈,配合 脚本系统 做事件闭环,物理层就能平滑接入完整玩法。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold 音频系统:Sound 组件、背景音乐、3D 音效与声音管理
  2. Defold 精灵与动画:Sprite、Flipbook、缓动与程序动画
  3. Defold 着色器与后处理:GLSL 材质、特效与全屏后期