关节
关节组件处于 Alpha 阶段,行为和 API 仍可能变化。所有开发方式都可通过代码使用,Web Components 提供标签,编辑器从 2.32 起提供检查器。PlayCanvas React 暂无对应组件。
关节约束两个刚体的相对运动,例如绕铰链摆动的门、滑轨上的抽屉、链条、弹簧上的箱子,以及受到足够冲击才断开的焊接积木塔。
关节工作方式
关节是独立实体上的 JointComponent,该实体自身不受约束。其世界变换定义关节坐标系:位置为锚点,局部 X 为主轴。铰链绕 X 旋转,滑块沿 X 平移,球关节绕 X 扭转。旋转关节实体即可调整方向。
通过 entityA 和 entityB 指定两个刚体,均需有刚体组件。entityB 留空可将 A 约束到世界固定点。常见做法是将关节实体作为 A 的子实体放在支点上,使支点随所属部件移动。
两个容易误解的细节:
- 关节坐标系只捕获一次,发生在约束创建时,通常是组件启用且两刚体均进入模拟时。之后移动关节实体无效,需调用
refreshFrames(),从当前变换重新捕获。与刚体一样,全程忽略实体缩放。 - 限位从初始姿态计量,并非关节实体的绝对角度。约束创建时两个关节坐标系重合,各自由度都为零。铰链 0 到 110 度表示从刚体初始位置再转最多 110 度,而非绝对角度。
关节类型
type 选择约束类型,也决定哪些属性有效:
| 类型 | 常量 | 约束 | 使用属性 |
|---|---|---|---|
fixed | JOINTTYPE_FIXED | 将两个刚体刚性固定在一起 | 无 |
ball | JOINTTYPE_BALL | 球窝关节,绕锚点自由旋转,可限制摆动与扭转 | swingLimitY, swingLimitZ, twistLimit |
hinge | JOINTTYPE_HINGE | 绕关节坐标系 X 轴旋转,可设置限位和马达 | limits, motorSpeed, maxMotorForce |
slider | JOINTTYPE_SLIDER | 沿关节坐标系 X 轴平移,可设置限位和马达 | limits, motorSpeed, maxMotorForce |
6dof | JOINTTYPE_6DOF | 各线性与旋转轴独立设为锁定、限位或自由,可配置弹簧 | linearMotionX/Y/Z, angularMotionX/Y/Z, linearLimitsX/Y/Z, angularLimitsX/Y/Z, linearStiffness, angularStiffness, linearDamping, angularDamping, linearEquilibrium, angularEquilibrium |
所有类型都支持 enableCollision,决定两刚体是否仍相互碰撞,默认关闭;也都支持 breakImpulse。
6dof 各轴默认 MOTION_LOCKED,因此不设置其他内容时类似固定关节。用 MOTION_FREE 或 MOTION_LIMITED 开放需要的轴,为限位轴设置对应范围,再将刚度设为正数,即可成为拉向平衡位置的弹簧。
添加关节
下面将动态门通过铰链约束到静态门框。关节实体位于铰链线上,旋转使局部 X 向上,0 到 110 度限位让门只能单向打开:
- 引擎
- 编辑器
- React
- Web Components
const hinge = new pc.Entity('hinge');
hinge.setPosition(1, 1, 0);
hinge.setEulerAngles(0, 0, 90); // the local X axis now points up
hinge.addComponent('joint', {
type: pc.JOINTTYPE_HINGE,
entityA: door,
entityB: doorFrame,
enableLimits: true,
limits: new pc.Vec2(0, 110)
});
app.root.addChild(hinge);
pc.Application 自动注册关节系统。使用 pc.AppBase 时,在 AppOptions.componentSystems 中除碰撞和刚体系统外,还需加入 pc.JointComponentSystem。
编辑器 2.32 起,可通过 Add Component → Physics → Joint 直接添加并在检查器中配置,字段详情见Joint 组件。
也可在铰链实体脚本中通过代码添加。照常在视口中设置实体位置和旋转,再将两刚体赋给脚本属性:
import { Script, Vec2, JOINTTYPE_HINGE } from 'playcanvas';
export class DoorHinge extends Script {
static scriptName = 'doorHinge';
/**
* The door, a dynamic rigid body.
*
* @attribute
* @type {Entity}
*/
door;
/**
* The frame the door hangs from, a static rigid body.
*
* @attribute
* @type {Entity}
*/
frame;
initialize() {
this.entity.addComponent('joint', {
type: JOINTTYPE_HINGE,
entityA: this.door,
entityB: this.frame,
enableLimits: true,
limits: new Vec2(0, 110)
});
}
}
目前没有 <Joint> 组件。物理加载后,在铰链 <Entity> 内的 effect 中添加引擎组件,通过 ref 获取两刚体:
import { useEffect, useRef } from 'react';
import { JOINTTYPE_HINGE, Vec2 } from 'playcanvas';
import { Entity } from '@playcanvas/react';
import { useParent, usePhysics } from '@playcanvas/react/hooks';
function Hinge({ door, frame }) {
const entity = useParent();
const { isPhysicsLoaded } = usePhysics();
useEffect(() => {
if (!isPhysicsLoaded || !door.current || !frame.current) return;
entity.addComponent('joint', {
type: JOINTTYPE_HINGE,
entityA: door.current,
entityB: frame.current,
enableLimits: true,
limits: new Vec2(0, 110)
});
return () => entity.removeComponent('joint');
}, [entity, isPhysicsLoaded, door, frame]);
return null;
}
function Door() {
const door = useRef();
const frame = useRef();
return (
<>
<Entity ref={frame} name="frame" position={[1, 1, 0]}>{/* collision and static rigid body */}</Entity>
<Entity ref={door} name="door" position={[1.9, 1, 0]}>{/* collision and dynamic rigid body */}</Entity>
<Entity name="hinge" position={[1, 1, 0]} rotation={[0, 0, 90]}>
<Hinge door={door} frame={frame} />
</Entity>
</>
);
}
<pc-entity name="hinge" position="1 1 0" rotation="0 0 90">
<pc-joint type="hinge" entity-a="#door" entity-b="#door-frame"
enable-limits limits="0 110"></pc-joint>
</pc-entity>
所有特性及各类型实时示例见 <pc-joint>。
马达、限位与断裂
限位需要同时启用 enableLimits 并设置限位值。只设置 limits、但 enableLimits 为 false 的铰链仍可自由旋转。
铰链和滑块支持马达。maxMotorForce 大于零时启用,以 motorSpeed 驱动。铰链单位为度/秒,滑块为单位/秒。启用限位时,马达到达限位会停止推进。
// A windmill: spin the rotor about the hinge axis at 90 degrees per second
hinge.joint.motorSpeed = 90;
hinge.joint.maxMotorForce = 100;
所有关节都可断裂。breakImpulse 指定失效冲量,默认 Infinity 表示不断裂。断裂时触发 break,isBroken 变为 true,两刚体独立运动。调用 refreshFrames() 可按当前变换重新连接。
const weld = jointEntity.joint;
weld.breakImpulse = 60;
weld.on('break', () => {
console.log('The weld snapped');
});
如果 ammo.js 构建未暴露约束状态,则不能检测 6dof 断裂,也不会触发该类型的 break。其他关节不受影响。
另请参阅
- JointComponent:全部关节属性 API。
<pc-joint>:Web Components 中的相同关节及各类型实时示例。- Ragdoll:通过球关节和铰链构建角色的引擎示例。
- 刚体:关节约束的对象。
- Joint 组件:对应编辑器检查器。