player-physics

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Player Physics in Decentraland

Decentraland 中的玩家物理

Apply forces to the player's avatar using the
Physics
API from
@dcl/sdk/ecs
. All physics operations affect the local player only.
使用
@dcl/sdk/ecs
中的
Physics
API 向玩家化身施加力。所有物理操作仅影响本地玩家。

Why this skill exists — the Transform mistake

为什么需要此 skill——Transform 误用

The player's
Transform
(on
engine.PlayerEntity
) is engine-controlled and read-only from scene code. Writing to it via
Transform.getMutable
,
Transform.createOrReplace
, or direct
.position
/
.rotation
mutation silently does nothing — the code compiles, the system ticks, no error is thrown, and the avatar never moves.
If your goal is to lift, float, push, knock back, or apply any sustained force to the player, use this skill's
Physics
API.
For instant teleports / smooth slides to a specific position, use
movePlayerTo
from
~system/RestrictedActions
(skill:
player-avatar
).
typescript
// 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)
玩家的
Transform
(位于
engine.PlayerEntity
上)由引擎控制,从场景代码中只能读取。通过
Transform.getMutable
、
Transform.createOrReplace
或直接修改
.position
/
.rotation
来写入它,会静默失效——代码可以编译,系统仍会执行 tick,不会抛出错误,而化身永远不会移动。
如果你的目标是抬起、漂浮、推动、击退玩家,或对玩家施加任何持续力,请使用此 skill 的
Physics
API。
若要瞬移或平滑滑行至特定位置,请使用
~system/RestrictedActions
中的
movePlayerTo
(skill:
player-avatar
)。
typescript
// 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)

Impulse (One-Shot Push)

冲量(一次性推动)

Apply a single instantaneous force with
Physics.applyImpulseToPlayer(direction, magnitude?)
. Direction is auto-normalized when magnitude is passed separately. Multiple calls within the same frame are accumulated. Use for launch pads, jumps, and explosions.
使用
Physics.applyImpulseToPlayer(direction, magnitude?)
施加一次瞬时力。当 magnitude 单独传入时,direction 会被自动归一化。同一帧内的多次调用会累积。适用于发射台、跳跃和爆炸。

Knockback (Push Away from a Point)

击退(从一点推开)

Push the player away from a source position with
Physics.applyKnockbackToPlayer(sourcePos, magnitude, radius?, falloff?)
. Direction is computed automatically from source to player.
使用
Physics.applyKnockbackToPlayer(sourcePos, magnitude, radius?, falloff?)
将玩家从源位置推开。方向会根据从源位置指向玩家的方向自动计算。

KnockbackFalloff Options

KnockbackFalloff 选项

FalloffBehavior
KnockbackFalloff.CONSTANT
Same magnitude at any distance within radius (default)
KnockbackFalloff.LINEAR
Smooth linear decrease to 0 at the radius edge
KnockbackFalloff.INVERSE_SQUARE
Sharp, physically-realistic drop-off
If the player is exactly at the source, they are pushed straight up. A negative magnitude pulls the player toward the point. Same falloff values apply to
applyRepulsionForceToPlayer()
. Prefer
KnockbackFalloff.LINEAR
for most area effects — it feels natural and predictable.
Falloff行为
KnockbackFalloff.CONSTANT
半径范围内的任何距离都会产生相同的 magnitude(默认)
KnockbackFalloff.LINEAR
平滑地线性衰减至半径边缘处的 0
KnockbackFalloff.INVERSE_SQUARE
急剧且符合物理规律的衰减
如果玩家正好位于源点,则会被垂直向上推开。负 magnitude 会将玩家拉向该点。相同的 falloff 值也适用于
applyRepulsionForceToPlayer()
。对于大多数区域效果,优先使用
KnockbackFalloff.LINEAR
,因为它更自然且可预测。

Continuous Force

持续力

Apply a persistent directional force identified by an entity (the force "owner"). Multiple forces from different entities stack.
  • Apply:
    Physics.applyForceToPlayer(entity, direction, magnitude?)
  • Remove:
    Physics.removeForceFromPlayer(entity)
Use with trigger zones for wind tunnels, conveyor belts, and gravity fields.
施加由 entity 标识的持续方向力(即力的“所有者”)。来自不同 entity 的多个力会叠加。
  • 施加:
    Physics.applyForceToPlayer(entity, direction, magnitude?)
  • 移除:
    Physics.removeForceFromPlayer(entity)
可与触发区域配合用于风洞、传送带和重力场。

Timed Force

定时力

Apply a force for a fixed duration:
Physics.applyForceToPlayerForDuration(entity, seconds, direction, magnitude?)
. Expires automatically.
施加固定时长的力:
Physics.applyForceToPlayerForDuration(entity, seconds, direction, magnitude?)
。该力会自动失效。

Repulsion Force

排斥力

Continuous push away from a fixed point with distance-based falloff, recalculated each frame:
Physics.applyRepulsionForceToPlayer(entity, position, magnitude, radius)
. The position must be passed explicitly — not read from the entity's Transform automatically. Remove with
Physics.removeForceFromPlayer(entity)
.
持续从固定点推开玩家,并按距离衰减,每帧重新计算:
Physics.applyRepulsionForceToPlayer(entity, position, magnitude, radius)
。必须显式传入 position——不会自动从 entity 的
Transform
读取。使用
Physics.removeForceFromPlayer(entity)
移除。

Coordinate Conversion (Local to World Direction)

坐标转换(从局部方向到世界方向)

Convert local direction to world space with
Transform.localToWorldDirection(entity, localDir)
. Use when pushing relative to an entity's orientation (e.g. "forward from this cannon").
使用
Transform.localToWorldDirection(entity, localDir)
将局部方向转换为世界空间。当需要相对于 entity 的朝向施力(例如“从这台炮口向前推”)时使用。

Quick Reference

快速参考

MethodTypeDescription
Physics.applyImpulseToPlayer(dir, mag?)
One-shotInstant directional push
Physics.applyKnockbackToPlayer(pos, mag, radius?, falloff?)
One-shotPush away from point
Physics.applyForceToPlayer(entity, dir, mag?)
ContinuousPersistent push while active
Physics.removeForceFromPlayer(entity)
ControlStop a continuous force
Physics.applyForceToPlayerForDuration(entity, secs, dir, mag?)
TimedForce that expires automatically
Physics.applyRepulsionForceToPlayer(entity, pos, mag, radius)
ContinuousDistance-based push from point
Transform.localToWorldDirection(entity, dir)
UtilityConvert local direction to world space
方法类型描述
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)
工具将局部方向转换为世界空间

Trigger-zone collider layers — which player fires the trigger

触发区域碰撞层——由哪些玩家触发

Physics.*
always affects the local player. When you drive forces from a
TriggerArea
, the collider mask decides which avatars fire the callback (verified in test scene
5,5-collider-layers
):
MaskFires for
ColliderLayer.CL_MAIN_PLAYER
the local player only
ColliderLayer.CL_PLAYER
remote avatars only (NOT the local player)
CL_PLAYER | CL_MAIN_PLAYER
both local and remote
ColliderLayer.CL_PHYSICS
never fires for any avatar (targets scene mesh/walls, not characters)
Prefer
CL_MAIN_PLAYER
for player-physics trigger zones.
Because the callback then only fires for the local player, you do not need a remote-vs-local guard, and the force is applied to the one avatar it can affect. A launch pad using
CL_PLAYER
alone will NOT fire for the local player (only for remote avatars), so it never launches the person standing on it — a common bug.
If you do use
CL_PLAYER | CL_MAIN_PLAYER
, guard with
result.trigger?.entity === engine.PlayerEntity
—
result.trigger?.entity
is the entity that entered. Note:
result.triggeredEntity
is the trigger area's OWN entity, not the entrant, so comparing it to
PlayerEntity
never distinguishes who entered.
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
是进入触发区域的 entity,而
result.triggeredEntity
是触发区域自身的 entity,因此将其与
PlayerEntity
比较永远无法区分是谁进入。

Forces while gliding

滑翔期间的力

While the player is gliding (glider open), forces behave differently:
  • Continuous forces (
    applyForceToPlayer
    ,
    applyForceToPlayerForDuration
    ,
    applyRepulsionForceToPlayer
    ) are 1.5× stronger — the open glider catches the airflow, so wind zones/currents feel more responsive.
  • The upward component of a continuous force can lift a gliding player.
    glidingFallingSpeed
    (in
    AvatarLocomotionSettings
    ) only caps descent speed; it does not cancel upward motion, so an angled or vertical current pushes the player along the full force direction. Enables thermal updrafts / wind corridors.
  • One-shot impulses (
    applyImpulseToPlayer
    ,
    applyKnockbackToPlayer
    ) are NOT affected by gliding — identical whether the glider is open or closed.
For full code examples (launch pad, wind tunnel, repulsion field, coordinate conversion), see
{baseDir}/references/physics-patterns.md
.
玩家滑翔时(滑翔伞打开),力的行为有所不同:
  • 持续力(
    applyForceToPlayer
    、
    applyForceToPlayerForDuration
    、
    applyRepulsionForceToPlayer
    )的强度会提高 1.5 倍——打开的滑翔伞会捕捉气流,因此风区和洋流会带来更明显的响应。
  • 持续力的向上分量可以抬起正在滑翔的玩家。
    glidingFallingSpeed
    (位于
    AvatarLocomotionSettings
    中)仅限制下降速度,不会抵消向上的运动,因此倾斜或垂直气流会沿完整的受力方向推动玩家。这可用于实现热上升气流和风道。
  • 一次性冲量(
    applyImpulseToPlayer
    、
    applyKnockbackToPlayer
    )不受滑翔影响——无论滑翔伞打开还是关闭,效果都完全相同。
有关完整代码示例(发射台、风洞、排斥力场、坐标转换),请参阅
{baseDir}/references/physics-patterns.md
。