# Física del jugador

Puedes aplicar fuerzas físicas al avatar del jugador desde el código de tu scene. Esto te permite crear mecánicas de juego como plataformas de lanzamiento, zonas de viento, efectos de retroceso, explosiones y más.

Hay dos tipos de fuerza que puedes aplicar:

* **Impulso**: Un empujón instantáneo de un solo uso. Úsalo para efectos súbitos y discretos, como lanzar al jugador por los aires o hacer que retroceda.
* **Fuerza continua**: Un empujón sostenido que se aplica en cada tick mientras esté activa. Úsalo para efectos continuos como zonas de viento, corrientes de agua o campos gravitatorios.

Ambos se aplican a través del `Physics` helper, importado desde `@dcl/sdk/ecs`.

{% hint style="warning" %}
**📔 Nota**: Estas fuerzas solo afectan al avatar del jugador local. Los demás jugadores ven los cambios en la posición de los otros jugadores, pero las fuerzas en sí no se sincronizan con otros jugadores en multiplayer. La física de cada jugador se ejecuta localmente en su propia instancia.
{% endhint %}

## Aplicar un impulso

Usa `Physics.applyImpulseToPlayer()` para dar al jugador un empujón de un solo uso en una dirección determinada.

* `vector`: Dirección y fuerza combinadas: la longitud del vector codifica la magnitud del impulso.

```ts
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

// Lanzar al jugador directamente hacia arriba
Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0))
```

También puedes pasar una dirección y una magnitud por separado. En este caso, el vector de dirección se normaliza automáticamente:

* `direction`: Dirección del empuje: se normaliza automáticamente antes de escalarla.
* `magnitude`: Fuerza del impulso.

```ts
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

// Lanzar al jugador hacia arriba con una magnitud de 50
Physics.applyImpulseToPlayer(Vector3.create(0, 1, 0), 50)
```

Si llamas a `applyImpulseToPlayer()` varias veces dentro del mismo frame, los impulsos se acumulan: se suman y se aplican como un único impulso combinado.

### Disparar un impulso al entrar el jugador

Un patrón común es disparar un impulso cuando el jugador entra en un área. Usa un trigger area para detectar cuándo entra el jugador:

```ts
import { Physics, TriggerArea, triggerAreaEventsSystem, ColliderLayer, MeshRenderer, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const launchPad = engine.addEntity()
Transform.create(launchPad, { position: Vector3.create(8, 0, 8) })
MeshRenderer.setBox(launchPad)
TriggerArea.setBox(launchPad, ColliderLayer.CL_PLAYER)

triggerAreaEventsSystem.onTriggerEnter(launchPad, (result) => {
	if (result.trigger?.entity !== engine.PlayerEntity) return
	Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0))
})
```

## Aplicar un impulso de retroceso

Usa `Physics.applyKnockbackToPlayer()` para empujar al jugador lejos de un punto con un único impulso. Esto es ideal para explosiones, efectos de impacto y otros estallidos direccionales de un solo uso. La dirección se calcula automáticamente desde el punto de origen hasta la posición actual del jugador.

```ts
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const explosionPosition = Vector3.create(8, 1, 8)

// Hacer retroceder al jugador desde el centro de la explosión
Physics.applyKnockbackToPlayer(explosionPosition, 40)
```

Puedes limitar el área de efecto con un `radius`, y controlar cómo disminuye la magnitud con la distancia usando la opción `KnockbackFalloff` :

* `fromPosition`: Origen en world-space del retroceso (centro de la explosión, posición del enemigo, etc.).
* `magnitude`: Fuerza base del impulso.
* `radius`: Distancia máxima de efecto (valor predeterminado: `Infinity`).
* `falloff`: Cómo disminuye la magnitud con la distancia (valor predeterminado: `CONSTANT`).

```ts
import { Physics, KnockbackFalloff } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

Physics.applyKnockbackToPlayer(
	Vector3.create(8, 1, 8),
	40,                          // magnitude
	10,                          // radius: no effect beyond 10 meters
	KnockbackFalloff.LINEAR      // magnitude fades to 0 at the radius edge
)
```

El `KnockbackFalloff` enum controla cómo disminuye la fuerza con la distancia:

* `KnockbackFalloff.CONSTANT` — misma magnitud a cualquier distancia dentro del radius (predeterminado)
* `KnockbackFalloff.LINEAR` — disminución lineal suave hasta 0 en el borde del radius
* `KnockbackFalloff.INVERSE_SQUARE` — caída brusca y físicamente realista

Si el jugador está exactamente en la posición de origen, se le empuja directamente hacia arriba. Una magnitud negativa atrae al jugador hacia el punto en lugar de empujarlo lejos.

Los mismos `KnockbackFalloff` values están disponibles para `applyRepulsionForceToPlayer()`.

## Aplicar una fuerza continua

Usa `Physics.applyForceToPlayer()` para aplicar una fuerza sostenida al jugador. A diferencia de un impulso, esta fuerza se aplica en cada tick mientras permanezca activa.

* `source`: Una entidad que identifica el origen de esta fuerza; úsala para actualizarla o eliminarla más tarde.
* `vector`: Dirección y fuerza combinadas: la longitud del vector codifica la magnitud de la fuerza.

La posición de la entidad de origen no es relevante: solo se usa como identificador.

```ts
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const windZoneEntity = engine.addEntity()

// Empujar al jugador hacia la derecha de forma continua
Physics.applyForceToPlayer(windZoneEntity, Vector3.create(10, 0, 0))
```

El vector de fuerza siempre está en **world space**. Si necesitas una dirección relativa a una entidad rotada, usa `Transform.localToWorldDirection()` para convertirla primero; consulta [Convert a local direction to world space](#convert-a-local-direction-to-world-space).

Al igual que con `applyImpulseToPlayer()`, también puedes pasar un `direction` y `magnitude` por separado; consulta [Aplicar un impulso](#apply-an-impulse):

```ts
Physics.applyForceToPlayer(windZoneEntity, Vector3.create(0, 1, 0), 50)
```

Si llamas a `applyForceToPlayer()` de nuevo con la misma entidad de origen, reemplaza la fuerza anterior de esa fuente. Si se acumulan múltiples fuentes de fuerza, sus vectores se suman en cada tick.

### Eliminar una fuerza continua

Para dejar de aplicar una fuerza, llama a `Physics.removeForceFromPlayer()` con una referencia a la entidad de origen que se usó para crear la fuerza. Si la entidad no está aplicando actualmente una fuerza, esta llamada se ignora de forma segura.

* `source`: La entidad usada cuando se aplicó la fuerza.

```ts
Physics.removeForceFromPlayer(windZoneEntity)
```

### Aplicar una fuerza durante una duración limitada

Usa `Physics.applyForceToPlayerForDuration()` para aplicar una fuerza durante una cantidad específica de tiempo. La duración se indica en segundos. La fuerza se elimina automáticamente cuando el tiempo termina. Llamarla de nuevo con la misma entidad de origen reinicia el temporizador.

* `source`: Una entidad que identifica este origen de fuerza.
* `duration`: Cuánto dura la fuerza, en segundos.
* `vector`: Dirección y fuerza combinadas: la longitud del vector codifica la magnitud de la fuerza.

```ts
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const gustEntity = engine.addEntity()

// Aplicar una fuerza ascendente intensa durante 1.5 segundos
Physics.applyForceToPlayerForDuration(gustEntity, 1.5, Vector3.create(0, 50, 0))
```

El `direction` + `magnitude` también está disponible: consulta [Aplicar un impulso](#apply-an-impulse):

```ts
Physics.applyForceToPlayerForDuration(gustEntity, 1.5, Vector3.create(0, 1, 0), 50)
```

### Ejemplo de zona de viento

Este ejemplo crea un túnel de viento que empuja al jugador mientras está dentro y se detiene cuando sale:

```ts
import { Physics, TriggerArea, triggerAreaEventsSystem, ColliderLayer, MeshRenderer, MeshCollider, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const windTunnel = engine.addEntity()
Transform.create(windTunnel, {
	position: Vector3.create(8, 1, 8),
	scale: Vector3.create(4, 3, 4),
})
MeshRenderer.setBox(windTunnel)
TriggerArea.setBox(windTunnel, ColliderLayer.CL_PLAYER)

triggerAreaEventsSystem.onTriggerEnter(windTunnel, (result) => {
	if (result.trigger?.entity !== engine.PlayerEntity) return
	Physics.applyForceToPlayer(windTunnel, Vector3.create(15, 0, 0))  // empuja lateralmente a lo largo del eje X
})

triggerAreaEventsSystem.onTriggerExit(windTunnel, (result) => {
	if (result.trigger?.entity !== engine.PlayerEntity) return
	Physics.removeForceFromPlayer(windTunnel)
})
```

## Aplicar una fuerza de repulsión

Usa `Physics.applyRepulsionForceToPlayer()` para empujar continuamente al jugador lejos de un punto fijo en el espacio. Esto es adecuado para efectos como un campo de repulsión magnética, una barrera de fuerza o un objeto flotante que mantiene a los jugadores a distancia. A diferencia de `applyKnockbackToPlayer()`, esta fuerza se recalcula en cada tick a medida que el jugador se mueve. La magnitud se debilita con la distancia según el radius y el falloff; consulta [Aplicar un impulso de retroceso](#apply-a-knockback-impulse) para las `KnockbackFalloff` options. Una magnitud negativa invierte el efecto, atrayendo continuamente al jugador hacia el punto como un vórtice o un pozo gravitatorio.

{% hint style="warning" %}
**📔 Nota**: El origen de la repulsión es el `fromPosition` vector que pasas; **no** la posición de la `source` entity. La `source` entity solo se usa como identificador para que puedas actualizar o eliminar la fuerza más tarde. Su posición, rotación y escala se ignoran por completo.
{% endhint %}

* `source`: Una entidad usada como identificador de esta fuerza; su posición no se usa.
* `fromPosition`: El punto en world-space del que se aleja al jugador.
* `magnitude`: Fuerza base de la fuerza. Los valores negativos atraen en lugar de repeler.
* `radius`: Distancia máxima de efecto (valor predeterminado: `Infinity`).
* `falloff`: Cómo disminuye la magnitud con la distancia (valor predeterminado: `CONSTANT`).

```ts
import { Physics, timers } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const repulsionSource = engine.addEntity()
Transform.create(repulsionSource, { position: Vector3.create(8, 1, 8) })

// Pasar explícitamente la posición de la entidad como origen de la repulsión
Physics.applyRepulsionForceToPlayer(
	repulsionSource,
	Transform.get(repulsionSource).position,  // origen de la repulsión: se pasa explícitamente, no se lee automáticamente
	50,                                         // magnitude
	10,                                         // radius de efecto en metros
)

// Eliminar la fuerza después de medio segundo
timers.setTimeout(() => {
    Physics.removeForceFromPlayer(repulsionSource)
}, 500)
```

## Convert a local direction to world space

Usa `Transform.localToWorldDirection()` para transformar un vector de dirección desde el espacio local de una entidad a world space, teniendo en cuenta toda la jerarquía de padres. Esto es útil al aplicar fuerzas relativas a una entidad rotada; por ejemplo, empujar al jugador lejos de una cara específica de un obstáculo giratorio.

* `entity`: La entidad de origen cuyo espacio local define la dirección.
* `localDirection`: Vector de dirección en las coordenadas locales de la entidad.

Devuelve el vector de dirección en coordenadas world.

Esto aplica **solo la rotación** — no tiene en cuenta la traslación ni la escala —, lo que lo hace directamente adecuado para vectores de dirección pasados a funciones de fuerza e impulso.

```ts
import { Physics, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

// Empujar al jugador en la dirección hacia la que apunta el eje local +Z de la entidad en world space
const worldDir = Transform.localToWorldDirection(myEntity, Vector3.create(0, 0, 1))
Physics.applyImpulseToPlayer(worldDir, 20)
```
