# Física do Player

Você pode aplicar forças físicas ao avatar do jogador a partir do código da sua cena. Isso permite criar mecânicas de jogabilidade como plataformas de lançamento, zonas de vento, efeitos de knockback, explosões e muito mais.

Existem dois tipos de força que você pode aplicar:

* **Impulse**: Um empurrão instantâneo de uso único. Use isto para efeitos súbitos e discretos, como lançar o jogador para o ar ou afastá-lo.
* **Continuous force**: Um empurrão sustentado aplicado a cada tick enquanto estiver ativo. Use isto para efeitos contínuos, como zonas de vento, correntes de água ou campos gravitacionais.

Ambos são aplicados por meio do `Physics` helper, importado de `@dcl/sdk/ecs`.

{% hint style="warning" %}
**📔 Nota**: Essas forças afetam apenas o avatar do jogador local. Outros jogadores veem as alterações nas posições dos outros jogadores, mas as forças em si não são sincronizadas com outros jogadores no multiplayer. A física de cada jogador é executada localmente em sua própria instância.
{% endhint %}

## Aplicar um impulso

Use `Physics.applyImpulseToPlayer()` para dar ao jogador um empurrão de uso único em uma direção específica.

* `vector`: Direção e força combinadas — o comprimento do vector codifica a magnitude do impulso.

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

// Lançar o jogador diretamente para cima
Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0))
```

Você também pode passar uma direção e uma magnitude separadamente. Nesse caso, o vector de direção é normalizado automaticamente:

* `direction`: Direção do empurrão — normalizada automaticamente antes da escala.
* `magnitude`: Força do impulso.

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

// Lançar o jogador para cima com uma magnitude de 50
Physics.applyImpulseToPlayer(Vector3.create(0, 1, 0), 50)
```

Se você chamar `applyImpulseToPlayer()` várias vezes no mesmo frame, os impulsos são acumulados — eles são somados e aplicados como um único impulso combinado.

### Disparar um impulso na entrada do jogador

Um padrão comum é disparar um impulso quando o jogador entra em uma área. Use uma trigger area para detectar quando o jogador entra:

```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 um impulso de knockback

Use `Physics.applyKnockbackToPlayer()` para afastar o jogador de um ponto com um único impulso. Isso é ideal para explosões, efeitos de impacto e outros disparos direcionais de uso único. A direção é calculada automaticamente a partir do ponto de origem até a posição atual do jogador.

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

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

// Afastar o jogador do centro da explosão
Physics.applyKnockbackToPlayer(explosionPosition, 40)
```

Você pode limitar a área de efeito com um `radius`, e controlar como a magnitude diminui com a distância usando a opção `KnockbackFalloff` :

* `fromPosition`: Origem em world-space do knockback (centro da explosão, posição do inimigo, etc.).
* `magnitude`: Força base do impulso.
* `radius`: Distância máxima de efeito (padrão: `Infinity`).
* `falloff`: Como a magnitude diminui com a distância (padrão: `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: sem efeito além de 10 metros
	KnockbackFalloff.LINEAR      // a magnitude cai até 0 na borda do radius
)
```

O `KnockbackFalloff` enum controla como a força diminui com a distância:

* `KnockbackFalloff.CONSTANT` — mesma magnitude em qualquer distância dentro do radius (padrão)
* `KnockbackFalloff.LINEAR` — diminuição linear suave até 0 na borda do radius
* `KnockbackFalloff.INVERSE_SQUARE` — queda acentuada e fisicamente realista

Se o jogador estiver exatamente na posição de origem, ele será empurrado diretamente para cima. Uma magnitude negativa puxa o jogador em direção ao ponto em vez de afastá-lo.

Os mesmos `KnockbackFalloff` values estão disponíveis para `applyRepulsionForceToPlayer()`.

## Aplicar uma continuous force

Use `Physics.applyForceToPlayer()` para aplicar uma força sustentada ao jogador. Ao contrário de um impulso, essa força é aplicada a cada tick enquanto permanecer ativa.

* `source`: Uma entity que identifica esta source de força — use-a para atualizar ou remover a força depois.
* `vector`: Direção e força combinadas — o comprimento do vector codifica a magnitude da força.

A posição da entity source não é relevante — ela é usada apenas como identificador.

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

const windZoneEntity = engine.addEntity()

// Empurrar o jogador continuamente para a direita
Physics.applyForceToPlayer(windZoneEntity, Vector3.create(10, 0, 0))
```

O vector de força está sempre em **world space**. Se você precisar de uma direção relativa a uma entity rotacionada, use `Transform.localToWorldDirection()` para convertê-la primeiro — veja [Converter uma direção local para world space](#convert-a-local-direction-to-world-space).

Assim como em `applyImpulseToPlayer()`, você também pode passar um `direction` e `magnitude` separadamente — veja [Aplicar um impulso](#apply-an-impulse):

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

Se você chamar `applyForceToPlayer()` novamente com a mesma source entity, ela substitui a força anterior dessa source. Se múltiplas sources de força forem acumuladas, seus vectors serão somados a cada tick.

### Remover uma continuous force

Para parar de aplicar uma força, chame `Physics.removeForceFromPlayer()` com uma referência à source entity que foi usada para criar a força. Se a entity não estiver aplicando uma força no momento, esta chamada é ignorada com segurança.

* `source`: A entity usada quando a força foi aplicada.

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

### Aplicar uma força por um período limitado

Use `Physics.applyForceToPlayerForDuration()` para aplicar uma força por uma quantidade específica de tempo. A duração é em segundos. A força é removida automaticamente quando o tempo termina. Chamar isso novamente com a mesma source entity reinicia o cronômetro.

* `source`: Uma entity que identifica esta source de força.
* `duration`: Por quanto tempo a força dura, em segundos.
* `vector`: Direção e força combinadas — o comprimento do vector codifica a magnitude da força.

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

const gustEntity = engine.addEntity()

// Aplicar uma força forte para cima por 1,5 segundos
Physics.applyForceToPlayerForDuration(gustEntity, 1.5, Vector3.create(0, 50, 0))
```

O `direction` + `magnitude` overload também está disponível — veja [Aplicar um impulso](#apply-an-impulse):

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

### Exemplo de zona de vento

Este exemplo cria um túnel de vento que empurra o jogador enquanto ele estiver dentro dele e para quando ele sai:

```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))  // empurra lateralmente ao longo do eixo X
})

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

## Aplicar uma força de repulsão

Use `Physics.applyRepulsionForceToPlayer()` para empurrar continuamente o jogador para longe de um ponto fixo no espaço. Isso é adequado para efeitos como um campo de repulsão magnética, uma barreira de força ou um objeto flutuante que mantém os jogadores à distância. Ao contrário de `applyKnockbackToPlayer()`, essa força é recalculada a cada tick conforme o jogador se move. A magnitude enfraquece com a distância com base no radius e no falloff — veja [Aplicar um impulso de knockback](#apply-a-knockback-impulse) para as opções `KnockbackFalloff` . Uma magnitude negativa inverte o efeito, puxando continuamente o jogador em direção ao ponto, como um vórtice ou um poço gravitacional.

{% hint style="warning" %}
**📔 Nota**: A origem da repulsão é o `fromPosition` vector que você passa — **não** a posição da `source` entity. A `source` entity é usada apenas como identificador para que você possa atualizar ou remover a força depois. Sua posição, rotação e escala são completamente ignoradas.
{% endhint %}

* `source`: Uma entity usada como identificador desta força — sua posição não é usada.
* `fromPosition`: O ponto em world-space de onde o jogador é afastado.
* `magnitude`: Força base. Valores negativos atraem em vez de repelir.
* `radius`: Distância máxima de efeito (padrão: `Infinity`).
* `falloff`: Como a magnitude diminui com a distância (padrão: `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) })

// Passe explicitamente a posição da entity como a origem da repulsão
Physics.applyRepulsionForceToPlayer(
	repulsionSource,
	Transform.get(repulsionSource).position,  // origem da repulsão — passada explicitamente, não lida automaticamente
	50,                                         // magnitude
	10,                                         // radius de efeito em metros
)

// Remover a força após meio segundo
timers.setTimeout(() => {
    Physics.removeForceFromPlayer(repulsionSource)
}, 500)
```

## Converter uma direção local para world space

Use `Transform.localToWorldDirection()` para transformar um vector de direção do espaço local de uma entity para world space, levando em conta toda a hierarquia de parentesco. Isso é útil ao aplicar forças relativas a uma entity rotacionada — por exemplo, afastar o jogador de uma face específica de um obstáculo giratório.

* `entity`: A source entity cujo espaço local define a direção.
* `localDirection`: Vector de direção nas coordenadas locais da entity.

Retorna o vector de direção nas coordenadas world.

Isso aplica **apenas a rotação** — não leva em conta translação ou escala — tornando-o diretamente adequado para vectors de direção passados para funções de força e impulso.

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

// Empurrar o jogador na direção para a qual o eixo local +Z da entity aponta em world space
const worldDir = Transform.localToWorldDirection(myEntity, Vector3.create(0, 0, 1))
Physics.applyImpulseToPlayer(worldDir, 20)
```
