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. 用过滤矩阵管理交互
典型设置:
| 对象 | Group | Mask |
|---|---|---|
| 玩家子弹 | player_bullet | enemy, obstacle |
| 玩家身体 | player | ground, enemy, trigger_item |
| 敌人 | enemy | player, ground, player_bullet |
| 地面 | ground | player, 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 开发 做血条与命中反馈,配合 脚本系统 做事件闭环,物理层就能平滑接入完整玩法。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。