关于 player-physics
面向 Decentraland 场景开发者的玩家物理技能指南,说明如何通过 Physics API 对本地玩家施加瞬时或持续作用力,并涵盖触发区域与移动端注意事项。
主要功能
- 01施加瞬时冲量和击退
- 02按持续时间施加力
- 03创建带距离衰减的斥力
- 04移除指定实体拥有的作用力
- 05将局部方向转换为世界方向
- 06说明玩家触发器碰撞层行为
适用场景
- 01制作发射垫和爆炸击退
- 02实现风道、传送带或重力场
- 03创建围绕固定点的推斥区域
- 04调试玩家物理触发区域
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ decentraland/ sdk-skills --skill player-physics安装后在 Claude Code 中输入/player-physics确认已加载。
附带文件references/physics-patterns.md
详细介绍
Decentraland 中的玩家物理
使用 @dcl/sdk/ecs 中的 Physics API 对玩家 Avatar 施加力。所有物理操作仅影响本地玩家。
此技能存在的原因——Transform 错误
玩家的 Transform(位于 engine.PlayerEntity 上)由引擎控制,场景代码只能读取。通过 Transform.getMutable、Transform.createOrReplace 或直接修改 .position / .rotation 来写入它,不会产生任何效果,也不会报错——代码可以编译,系统会逐帧运行,但 Avatar 始终不会移动。
如果你想抬起、悬浮、推动、击退玩家,或对玩家施加任何持续的力,请使用此技能的 Physics API。 如果要瞬间传送到指定位置,或平滑滑动到指定位置,请使用 ~system/RestrictedActions 中的 movePlayerTo(技能:player-avatar)。
// WRONG — has no effect in-world
const t = Transform.getMutable(engine.PlayerEntity)
t.position.y += 0.1 // ignored every frame
// CORRECT — lift the player upward
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0)) // one-shot upward launch
// or, sustained lift / hover:
const lifter = engine.addEntity()
Physics.applyForceToPlayer(lifter, Vector3.create(0, 1, 0), 12) // continuous upward force
// stop with: Physics.removeForceFromPlayer(lifter)
冲量(一次性推动)
使用 Physics.applyImpulseToPlayer(direction, magnitude?) 施加一次瞬时力。单独传入 magnitude 时,direction 会自动归一化。同一帧内的多次调用会累积。适用于发射板、跳跃和爆炸。
击退(从某个点推开)
使用 Physics.applyKnockbackToPlayer(sourcePos, magnitude, radius?, falloff?) 将玩家从某个源位置推开。方向会根据源位置到玩家的方向自动计算。
KnockbackFalloff 选项
| 衰减方式 | 行为 |
|---|---|
KnockbackFalloff.CONSTANT | 半径内的任意距离都施加相同大小的力(默认) |
KnockbackFalloff.LINEAR | 平滑线性地减小,直到半径边缘处变为 0 |
KnockbackFalloff.INVERSE_SQUARE | 衰减急剧,符合真实物理规律 |
如果玩家正好位于源位置,会将其垂直向上推。负的 magnitude 会将玩家拉向该点。applyRepulsionForceToPlayer() 使用相同的衰减值。对于大多数范围效果,建议使用 KnockbackFalloff.LINEAR——效果自然且可预测。
持续力
通过一个实体(力的“所有者”)施加持续的定向力。来自不同实体的多个力会叠加。
- 施加:
Physics.applyForceToPlayer(entity, direction, magnitude?) - 移除:
Physics.removeForceFromPlayer(entity)
可与触发区域搭配使用,创建风道、传送带和重力场。
定时力
在固定时长内施加力:Physics.applyForceToPlayerForDuration(entity, seconds, direction, magnitude?)。到期后会自动结束。
排斥力
从固定点持续推开玩家,并根据距离衰减,每帧重新计算:Physics.applyRepulsionForceToPlayer(entity, position, magnitude, radius)。必须显式传入位置,不会自动从实体的 Transform 读取。使用 Physics.removeForceFromPlayer(entity) 移除。
坐标转换(局部方向转世界方向)
使用 Transform.localToWorldDirection(entity, localDir) 将局部方向转换为世界空间中的方向。需要根据实体朝向施加推动时使用(例如“从这门大炮朝前推”)。
快速参考
| 方法 | 类型 | 说明 |
|---|---|---|
Physics.applyImpulseToPlayer(dir, mag?) | 一次性 | 瞬时定向推动 |
Physics.applyKnockbackToPlayer(pos, mag, radius?, falloff?) | 一次性 | 将玩家从某个点推开 |
Physics.applyForceToPlayer(entity, dir, mag?) | 持续 | 激活期间持续推动 |
Physics.removeForceFromPlayer(entity) | 控制 | 停止持续力 |
Physics.applyForceToPlayerForDuration(entity, secs, dir, mag?) | 定时 | 到期后自动结束的力 |
Physics.applyRepulsionForceToPlayer(entity, pos, mag, radius) | 持续 | 根据距离从某个点推开 |
Transform.localToWorldDirection(entity, dir) | 工具 | 将局部方向转换为世界空间中的方向 |
触发区域碰撞层 — 哪个玩家会触发回调
Physics.* 始终影响本地玩家。通过 TriggerArea 施加力时,碰撞体遮罩决定哪些化身会触发回调(已在测试场景 5,5-collider-layers 中验证):
| 遮罩 | 触发对象 |
|---|---|
ColliderLayer.CL_MAIN_PLAYER | 仅本地玩家 |
ColliderLayer.CL_PLAYER | 仅远程化身(不包括本地玩家) |
CL_PLAYER | CL_MAIN_PLAYER | 本地和远程玩家 |
ColliderLayer.CL_PHYSICS | 不会为任何化身触发(目标是场景网格/墙壁,而非角色) |
玩家物理触发区域优先使用 CL_MAIN_PLAYER。 由于回调只会为本地玩家触发,因此无需判断远程或本地玩家,而且力只会施加给它能够影响的那个化身。仅使用 CL_PLAYER 的弹射垫不会为本地玩家触发(只会为远程化身触发),因此站在上面的人永远不会被弹射——这是一个常见错误。
如果使用 CL_PLAYER | CL_MAIN_PLAYER,请通过 result.trigger?.entity === engine.PlayerEntity 进行判断——result.trigger?.entity 是进入的实体。注意:result.triggeredEntity 是触发区域自身的实体,而非进入者,因此将它与 PlayerEntity 比较永远无法区分是谁进入了触发区域。
滑翔时的力
玩家滑翔(滑翔伞展开)时,力的表现有所不同:
- 持续力(
applyForceToPlayer、applyForceToPlayerForDuration、applyRepulsionForceToPlayer)会增强 1.5 倍——展开的滑翔伞会承接气流,因此风区/气流的反馈会更加明显。 - 持续力的向上分量可以托起滑翔中的玩家。
AvatarLocomotionSettings中的glidingFallingSpeed只会限制下降速度,不会抵消向上的运动,因此斜向或垂直气流会沿完整的力方向推动玩家。这可用于营造热气流上升气流/风道。 - 单次冲量(
applyImpulseToPlayer、applyKnockbackToPlayer)不受滑翔影响——无论滑翔伞展开还是收起,效果都相同。
移动端的行为与桌面端并不完全一致
移动应用 v1.15.0(2026 年 10 月)完成了首轮 Unity 行为对齐,涵盖胶囊体尺寸、台阶高度、跳跃轨迹与重力、奔跑速度和空中控制;同时修复了向下的玩家射线检测,使其现在能在倾斜几何体上正确报告 hitDistance。这只是首轮对齐,并非完全一致:以下问题仍未解决,目前没有预计时间(文档提交 5f212e3)。
- 斜坡、边缘、墙壁、着陆和移动平台的行为仍与 Unity 不同,滑翔行为也一样(修复正在审查中)。
- 厚度小于 1 cm 的场景碰撞体只能从上方发生碰撞。 在移动端,0.5 cm 厚的墙壁、薄薄的隐形地板板块或纸片般的斜坡都可以从侧面穿过。确保每个碰撞体至少有 1 cm 厚——这是成本低廉的保险措施,也是此处最值得优先执行的一条建议。
- 在移动端,隐形阻挡物和某些 SDK 碰撞层完全无法阻挡玩家。不要只用隐形碰撞体来围住游玩区域、设置死亡边界或强制“留在区域内”的规则;请使用
TriggerArea+movePlayerTo作为补充,或改用玩家看得见的几何体。 - 生成、着陆和跳跃动画以及移动混合效果均缺失——因此角色看起来更僵硬;不要把机制的触发时机绑定到移动端客户端根本不会播放的动画节拍上。
在桌面端调到“勉强”可用的台阶、路缘或边缘高度,在手机上可能仍无法攀爬——也可能变得不该能爬却能爬;冲量或击退后的着陆位置也可能不同。请为通行几何留出余量,所有强制移动机制都应提供宽容的着陆区域,并在发布精确平台跳跃内容前用设备验证。另外请注意,TweenSequence 在移动端不会播放动画(animations-tweens)——基于它构建的移动平台在手机上就是一座静止平台。移动端行为对齐列表:advanced-input → 移动端注意事项。
完整代码示例(发射台、风洞、斥力场、坐标转换)请参阅 {baseDir}/references/physics-patterns.md。