物理入门
PlayCanvas 物理基于 ammo.js,即开源 Bullet 物理引擎的 WebAssembly 移植。加载后,为实体添加决定运动方式的 rigidbody 和提供形状的 collision 即可构建模拟。本页介绍加载库、全局设置及本节其他页面依赖的基础规则。
启用物理
物理需要主动启用。ammo.js 有数百 KB,因此不会自动加载。它包含三个文件:WebAssembly 模块 ammo.wasm.wasm、JavaScript 胶水代码 ammo.wasm.js,以及供不支持 WebAssembly 的浏览器使用的 asm.js 回退 ammo.js。库加载前,物理组件只是占位,添加它们不会生效,也不会报错。
- 引擎
- 编辑器
- React
- Web Components
创建应用前先加载模块。三个文件位于引擎仓库 examples/assets/wasm/ammo/,也包含在 sync-ammo npm 包中。
pc.WasmModule.setConfig('Ammo', {
glueUrl: 'ammo/ammo.wasm.js',
wasmUrl: 'ammo/ammo.wasm.wasm',
fallbackUrl: 'ammo/ammo.js'
});
// Wait for the module, then create the application as usual
await new Promise((resolve) => {
pc.WasmModule.getInstance('Ammo', () => resolve());
});
const app = new pc.Application(canvas);
应用启动时,刚体系统查找全局 Ammo 并安装 ammo.js 后端。pc.Application 自动注册全部组件系统;使用 pc.AppBase 和 pc.AppOptions 时,需要向 componentSystems 添加 pc.CollisionComponentSystem、pc.RigidBodyComponentSystem,使用关节时还需 pc.JointComponentSystem。Falling Shapes 示例展示完整流程。
打开场景设置,展开 PHYSICS 并点击 IMPORT AMMO。编辑器会从 PlayCanvas Store 将三个文件加入资源,引擎在场景启动前加载。

库尚未导入时,Collision 和 Rigid Body 检查器显示 Ammo module not found 警告和相同导入按钮。若使用自定义 ammo.js 构建,将其作为 WASM 模块资源添加,代替商店版本。
较早项目使用由启动器自动注入的旧版库,因此即使没有 ammo.js 资源,物理也可能运行。点击 IMPORT AMMO 会添加模块资源,同时关闭旧版加载,避免同时加载两份。导入版本更新、更小且更快,建议适时切换。
安装模块,并在根 <Application> 上设置 usePhysics:
npm install sync-ammo
import { Application } from '@playcanvas/react';
<Application usePhysics>
{/* entities with <RigidBody> and <Collision> components */}
</Application>
设置 usePhysics 后,ammo.js 按需加载。场景可先渲染,库就绪后物理组件才激活。如需等待,usePhysics Hook 提供 isPhysicsLoaded。详情见 React 物理指南。
在 <pc-app> 中通过 <pc-wasm> 声明模块:
<pc-app>
<pc-wasm name="Ammo"
glue="https://developer.playcanvas.com/assets/modules/ammo/ammo.wasm.js"
wasm="https://developer.playcanvas.com/assets/modules/ammo/ammo.wasm.wasm"
fallback="https://developer.playcanvas.com/assets/modules/ammo/ammo.js"></pc-wasm>
<pc-scene>
<!-- entities with <pc-rigid-body> and <pc-collision> components -->
</pc-scene>
</pc-app>
应用会等待模块就绪后启动,因此物理标签从首帧即可工作。
重力
重力是施加到所有动态刚体的恒定加速度,默认沿世界 Y 轴向下,值为 -9.81,近似地球。太空游戏可设为零,月球场景可降低。单独改变刚体受重力程度,可使用重力缩放。
- 引擎
- 编辑器
- React
- Web Components
// Lunar gravity. The older setGravity() method is deprecated in favor of this property.
app.systems.rigidbody.gravity = new pc.Vec3(0, -1.62, 0);
在场景设置的 PHYSICS 中设置 Gravity。
没有 gravity prop,需要在 <Application> 内的组件中设置应用:
import { useEffect } from 'react';
import { useApp } from '@playcanvas/react/hooks';
function LunarGravity() {
const app = useApp();
useEffect(() => {
app.systems.rigidbody.gravity.set(0, -1.62, 0);
}, [app]);
return null;
}
<pc-scene gravity="0 -1.62 0">
<!-- ... -->
</pc-scene>
测量单位
物理引擎将一个单位解释为一米,质量以千克计。场景应使用相应比例,例如 1.8 米高的角色应高 1.8 个单位。相差很大的比例虽然仍能模拟,但重力看起来会过强或过弱,极小形状也容易互相穿透。
模拟如何运行
引擎驱动物理模拟的方式决定以下基础规则:
- **固定频率步进。**每帧以 1/60 秒固定步长推进,按帧时间执行所需步数,受最大步数限制。因此 30 FPS 和 144 FPS 的物理行为一致。高于 60 Hz 时,部分帧没有物理步,通过变换插值保持平滑。
- **物理控制动态刚体。**每步后将运动刚体的位置和旋转写回实体。设置实体变换不会移动刚体,因此应使用力、速度或
teleport,参见刚体。 - **你控制运动学刚体。**每步开始从实体读取变换,因此像普通实体一样移动即可。
- **静态刚体保持创建时位置。**创建时读取一次位置和旋转,之后不再跟踪实体。运行中需要移动的对象应使用运动学类型。
- **刚体没有缩放。**实体只传递位置和旋转,基本几何碰撞形状始终保持自身尺寸,参见缩放。
- **每步发出事件。**碰撞和触发事件按物理步而非渲染帧触发。静置在地板上的刚体每步都会产生
contact。
暂停与慢动作
刚体系统的 timeScale 缩放每帧推进的模拟时间,默认为 1,小于 1 为慢动作,大于 1 为加速,0 为暂停。暂停时刚体保留速度并冻结,运动学刚体停止跟随实体,也不发出碰撞或触发事件。脚本和动画等其他部分继续运行,适合保持暂停菜单交互。
- 引擎
- 编辑器
- React
- Web Components
// Freeze physics while the pause menu is open
app.systems.rigidbody.timeScale = 0;
// Run physics at quarter speed for a slow motion effect
app.systems.rigidbody.timeScale = 0.25;
编辑器没有对应设置,请从脚本设置,其中 this.app 为应用:
// Freeze physics while the pause menu is open
this.app.systems.rigidbody.timeScale = 0;
没有对应 prop,需要在 <Application> 内组件中设置:
import { useEffect } from 'react';
import { useApp } from '@playcanvas/react/hooks';
function PhysicsTimeScale({ scale }) {
const app = useApp();
useEffect(() => {
app.systems.rigidbody.timeScale = scale;
}, [app, scale]);
return null;
}
// Freeze physics while the pause menu is open
<PhysicsTimeScale scale={paused ? 0 : 1} />
设置 <pc-app> 的 physics-time-scale,运行时可修改该特性或元素的 physicsTimeScale 属性:
<pc-app physics-time-scale="0">
<!-- ... -->
</pc-app>
// Resume at quarter speed for a slow motion effect
document.querySelector('pc-app').physicsTimeScale = 0.25;
模拟仍采用 1/60 秒固定步长。慢动作可能每隔数帧才执行一步,中间通过插值保持平滑。加速受单帧最大步数限制:60 FPS 时最多加速十倍,更高值无法达到要求。
物理时间缩放还会叠加在应用的 timeScale 上,后者缩放脚本、动画和物理接收的 delta time。应用时间缩放设为 0,会停止所有随时间推进的内容;刚体系统设为 0,只暂停物理。
两者都不会停止脚本方法调用。update 和 postUpdate 每帧仍运行,应用时间缩放为 0 时只是 dt 为 0,因此输入、生成对象及其他未乘 dt 的逻辑仍会执行。玩法代码需要独立暂停判断,包括物理调用:暂停期间设置冲量和速度会立即生效,恢复后按新速度运动;物理时间缩放为 0 时,applyForce 的力会一直累积到下一物理步,因此每帧持续施力可能在恢复时一次释放巨大推力。
手动步进
app.systems.rigidbody.step(dt) 立即推进 dt 秒,完成与自动更新相同的流程:运动学刚体和触发器跟随实体,执行所需固定步,动态刚体写回实体,并触发事件。dt 不应用时间缩放。
时间缩放大于 0 时,自动步进继续执行,若每帧再调用 step 就会重复推进。完全接管前先将时间缩放设为 0。这样也可用于调试,例如每按一次键推进一步,观察碰撞过程:
const physics = app.systems.rigidbody;
physics.timeScale = 0;
window.addEventListener('keydown', (event) => {
// Advance the paused simulation by one fixed step each time the period key is pressed
if (event.key === '.') {
physics.step(1 / 60);
}
});
传入 1/60 正好执行一步。更短 dt 可能只更新插值位置而不执行物理步;更长值会执行多步,每次最多十步,超出时间丢弃。连续调用可快速推进,例如在玩家看到场景前让物体堆稳定。
各开发方式调用相同,区别只在应用引用:编辑器脚本用 this.app,React 用 useApp(),Web Components 的 whenReady('pc-app') 返回 <pc-app> 元素,通过其 app 属性访问。
另请参阅
- 刚体:类型和属性,建议接着阅读。
- 碰撞形状:为刚体提供形状。
- RigidBodyComponentSystem:重力、时间缩放和手动步进 API。
- 物理设置:编辑器场景设置中的 PHYSICS。
- 碰撞与触发器:构建完整物理场景的教程。