Dash na Unity 2D: Impulso, Cooldown e Invencibilidade
Um unity dash 2D bem feito é um estado curto e controlado: o personagem ignora a gravidade, segue numa velocidade fixa por uma fração de segundo, fica imune a dano enquanto isso e só pode repetir depois de um cooldown. Este tutorial monta o PlayerDash2D.cs com essas quatro partes, mais rastro visual com TrailRenderer, em C# do Unity 6.3 LTS com Rigidbody2D.linearVelocity e Input System.
No fim você também vai saber resolver os dois bugs que todo dash tem na primeira versão: o dash infinito, que deixa o jogador atravessar a fase voando, e o dash que atravessa parede.
Unity dash 2d: o que precisa
Antes do código, decida três coisas, porque cada uma muda o script:
- Direção. Na direção do input (oito direções, estilo Celeste e jogos top-down) ou na direção em que o personagem olha (só horizontal, estilo Hollow Knight). O script abaixo suporta as duas com um checkbox.
- Duração e distância. Distância = velocidade vezes duração. Com 22 unidades por segundo e 0,15 segundo, o dash cobre 3,3 unidades. Dash curto e rápido parece explosão; longo e lento parece deslizar.
- O que o dash ignora. Gravidade, sim, quase sempre. Input de movimento, sim. Dano de inimigo, depende do jogo: em jogos de ação o dash é a esquiva, então precisa de invencibilidade.
Pré-requisitos: um personagem com Rigidbody2D Dynamic e Collision Detection em Continuous, e um script de movimento horizontal. Se ainda não tem, o script de movimento 2D na Unity é a base que este dash espera.
Criar a ação Dash no Input System
O asset InputSystem_Actions dos projetos novos do Unity 6 não traz uma ação de dash. Crie a sua:
- Dê dois cliques em
InputSystem_Actionsna pasta Assets. - No mapa
Player, clique no+da coluna Actions e nomeieDash. Action Type:Button. - Adicione uma binding de teclado (por exemplo,
Left Shiftse o seu jogo não tem corrida, ouK) e uma de controle (Right ShoulderouButton West). - Salve o asset (
Save Assetno topo da janela, ou deixeAuto-Savemarcado).
No código, InputSystem.actions.FindAction("Dash") encontra a ação nova.
Dash com linearVelocity e gravidade zerada
O núcleo do dash são três linhas:
rb.gravityScale = 0f;
rb.linearVelocity = direction * dashSpeed;
// ... espera a duração ...
rb.gravityScale = originalGravity;
Zerar o gravityScale faz o dash ser uma linha reta. Sem isso, um dash horizontal no ar cai numa curva e perde a sensação de impulso. Definir linearVelocity (e não somar força) garante que todo dash tenha a mesma distância, não importa se o personagem estava parado, correndo ou caindo.
Tem um detalhe que quebra a maioria das primeiras versões: o script de movimento. Ele escreve a velocidade X a cada FixedUpdate, então no passo seguinte ao início do dash ele substitui a velocidade do dash pela de andar, e o dash dura um frame. A solução é desligar os scripts que mexem na velocidade enquanto o dash acontece. O script recebe uma lista deles no Inspector e liga e desliga com enabled.
O script PlayerDash2D completo
Crie PlayerDash2D.cs:
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerDash2D : MonoBehaviour
{
[Header("Dash")]
[SerializeField] private float dashSpeed = 22f;
[SerializeField] private float dashDuration = 0.15f;
[SerializeField] private float dashCooldown = 0.4f;
[SerializeField] private bool eightDirections = false;
[SerializeField] private int maxAirDashes = 1;
[SerializeField, Range(0f, 1f)] private float endSpeedMultiplier = 0.3f;
[Header("Invencibilidade")]
[SerializeField] private string dashLayerName = "PlayerDash";
[Header("Chão")]
[SerializeField] private Transform groundCheck;
[SerializeField] private Vector2 groundCheckSize = new Vector2(0.8f, 0.1f);
[SerializeField] private LayerMask groundLayer;
[Header("Referências")]
[SerializeField] private Rigidbody2D rb;
[SerializeField] private Transform visual;
[SerializeField] private TrailRenderer trail;
[SerializeField] private MonoBehaviour[] disableWhileDashing;
private InputAction dashAction;
private InputAction moveAction;
private float nextDashTime;
private int airDashesLeft;
private int originalLayer;
private int dashLayer;
private bool isDashing;
public bool IsDashing => isDashing;
private void Awake()
{
if (rb == null)
{
rb = GetComponent<Rigidbody2D>();
}
dashAction = InputSystem.actions.FindAction("Dash");
moveAction = InputSystem.actions.FindAction("Move");
originalLayer = gameObject.layer;
dashLayer = LayerMask.NameToLayer(dashLayerName);
if (dashAction == null)
{
Debug.LogError("Crie a ação 'Dash' no mapa Player do InputSystem_Actions.");
}
if (trail != null)
{
trail.emitting = false;
}
}
private void Update()
{
if (IsGrounded())
{
airDashesLeft = maxAirDashes;
}
if (dashAction != null && dashAction.WasPressedThisFrame() && CanDash())
{
StartCoroutine(DashRoutine(GetDashDirection()));
}
}
private bool CanDash()
{
if (isDashing || Time.time < nextDashTime)
{
return false;
}
return IsGrounded() || airDashesLeft > 0;
}
private Vector2 GetDashDirection()
{
Vector2 input = moveAction.ReadValue<Vector2>();
if (eightDirections && input.sqrMagnitude > 0.01f)
{
return input.normalized;
}
if (Mathf.Abs(input.x) > 0.01f)
{
return new Vector2(Mathf.Sign(input.x), 0f);
}
Transform facingSource = visual != null ? visual : transform;
float facing = facingSource.localScale.x >= 0f ? 1f : -1f;
return new Vector2(facing, 0f);
}
private IEnumerator DashRoutine(Vector2 direction)
{
isDashing = true;
nextDashTime = Time.time + dashDuration + dashCooldown;
if (!IsGrounded())
{
airDashesLeft--;
}
SetScriptsEnabled(false);
float originalGravity = rb.gravityScale;
rb.gravityScale = 0f;
if (dashLayer >= 0)
{
gameObject.layer = dashLayer;
}
if (trail != null)
{
trail.emitting = true;
}
float endTime = Time.time + dashDuration;
while (Time.time < endTime)
{
rb.linearVelocity = direction * dashSpeed;
yield return new WaitForFixedUpdate();
}
rb.linearVelocity = direction * dashSpeed * endSpeedMultiplier;
rb.gravityScale = originalGravity;
gameObject.layer = originalLayer;
if (trail != null)
{
trail.emitting = false;
}
SetScriptsEnabled(true);
isDashing = false;
}
private void SetScriptsEnabled(bool value)
{
foreach (MonoBehaviour script in disableWhileDashing)
{
if (script != null)
{
script.enabled = value;
}
}
}
private bool IsGrounded()
{
if (groundCheck == null)
{
return false;
}
return Physics2D.OverlapBox(groundCheck.position, groundCheckSize, 0f, groundLayer) != null;
}
}
No Inspector: arraste o Rigidbody2D, o objeto visual (o mesmo que vira de lado no movimento), o TrailRenderer e, em Disable While Dashing, os seus scripts de movimento e pulo.
Duração com coroutine
A DashRoutine é uma coroutine porque o dash é uma sequência no tempo: começa, dura, termina. O laço while reaplica a velocidade a cada WaitForFixedUpdate, ou seja, a cada passo de física. Por que reaplicar em vez de definir uma vez? Se o personagem raspar numa quina, a física reduz a velocidade e o resto do dash sai mais curto. Reaplicando, o dash mantém a velocidade até o fim ou até bater de frente numa parede.
O endSpeedMultiplier decide como o dash termina. Com 0, o personagem para seco, o que é bom para dashes de precisão. Com 0,3, sobra um pouco de impulso, que o script de movimento recém-ligado desacelera, e a saída do dash fica mais natural.
Cooldown
nextDashTime = Time.time + dashDuration + dashCooldown marca o primeiro instante em que outro dash é aceito. Somar a duração garante que o cooldown começa a contar no fim do dash, não no começo.
O cooldown sozinho não impede o dash infinito no ar: com 0,4 segundo de espera, o jogador dá dash, espera, dá outro e atravessa um abismo inteiro. Por isso existe airDashesLeft. Ele recarrega só quando o personagem toca o chão, e cada dash no ar gasta um. Com maxAirDashes = 1, é o comportamento clássico de "um dash por pulo".
Dash, pulo e movimento já são três scripts disputando a mesma velocidade, e ainda faltam ataque, dano, knockback e animação de cada estado. Quando cada habilidade nova quebra uma antiga, o projeto pede uma estrutura (estados, prioridades, quem manda na velocidade) que tutorial isolado não monta. Organizar essas peças num jogo que chega ao fim é o que o curso de Unity faz em projeto, e é a etapa em que mais gente desiste sozinha.
Invencibilidade durante o dash
A invencibilidade mais limpa na física 2D da Unity é trocar a layer do personagem durante o dash:
Edit > Project Settings > Tags and Layers: crie as layersPlayer,PlayerDash,EnemyeEnemyAttack(se ainda não existem). O personagem fica emPlayer.Edit > Project Settings > Physics 2D > Layer Collision Matrix: desmarque o cruzamento dePlayerDashcomEnemye comEnemyAttack. MantenhaPlayerDashcolidindo comGround, senão o dash atravessa o cenário.- O script troca
gameObject.layerparaPlayerDashno início e volta no fim.
A matriz de colisão vale também para triggers. Se o dano do inimigo vem de um OnTriggerEnter2D, ele simplesmente não dispara enquanto o player está na layer de dash.
A alternativa é Physics2D.IgnoreLayerCollision(playerLayer, enemyLayer, true) no começo e false no fim. Funciona, mas a regra vale para todos os objetos daquelas layers: num jogo multiplayer local, o dash de um jogador deixaria os outros invencíveis. A troca de layer afeta só quem está dando dash.
Atenção a filhos: gameObject.layer muda só o objeto do script. Se a hurtbox do personagem é um filho com collider próprio, troque a layer dele também.
Efeito visual com TrailRenderer
O TrailRenderer desenha um rastro atrás de um objeto em movimento, e a propriedade emitting liga e desliga a geração de novos pontos sem apagar o rastro que já existe. É o que o script usa.
Configuração para um dash de pixel art:
- Adicione
Trail Renderernum filho do personagem, na altura do centro do corpo. - Time:
0.15. É quanto tempo cada ponto do rastro dura. - Width: uma curva que começa na largura do corpo (por exemplo 0,8) e termina em 0.
- Color: gradiente da cor principal do personagem com alpha 1 para alpha 0.
- Material:
Sprites-Defaultserve para começar. - No Sorting Layer, coloque o rastro atrás do sprite do personagem.
Um rastro curto e um leve tremor de câmera no início do dash fazem mais pela sensação do que aumentar a velocidade. O post de game feel e juice tem outras técnicas que combinam com o dash: hit stop, partícula de poeira na saída, squash no sprite.
Erros comuns: dash infinito e atravessar parede
Dash infinito. Falta cooldown, ou o cooldown existe mas não há limite de dashes no ar. Use os dois, como no script.
Dash dura um frame. O script de movimento sobrescreve a velocidade. Coloque-o em Disable While Dashing.
Dash atravessa parede. Três causas possíveis:
- O dash move o transform (
transform.position += ...), teleportando o personagem. UselinearVelocity. Collision Detectionestá emDiscrete. Com 22 unidades por segundo e passo de física de 0,02 segundo, o personagem anda 0,44 unidade por passo, mais que a espessura de uma parede fina. UseContinuous.- A layer
PlayerDashficou sem colisão comGroundna matriz.
Dash para cima voa alto demais. Num dash de oito direções, a velocidade final para cima vira um super pulo. Reduza o endSpeedMultiplier ou zere a componente Y da velocidade ao terminar.
Personagem continua invencível depois do dash. A coroutine foi interrompida (o objeto foi desativado, ou StopAllCoroutines foi chamado) antes de restaurar a layer. Se o seu jogo desativa o player ao morrer, restaure a layer e a gravidade também no OnDisable.
Dash na direção errada parado. O script lê o lado do personagem pela escala do objeto visual. Se você vira o sprite com flipX, troque essa leitura por spriteRenderer.flipX.
Próximo passo
Com o dash funcionando, ajuste os números em pares: velocidade e duração definem a distância, cooldown e dashes no ar definem quanto o jogador pode abusar. Monte uma sala com um vão que só passa com dash e um inimigo que só se evita com a invencibilidade, e teste se os dois exigem a mecânica sem frustrar.
Para comparar com outra engine, o tutorial de dash no Godot 4 faz o mesmo sistema com Timers e mostra como transformar o dash num estado de uma state machine, o caminho natural quando o personagem acumula habilidades.
Perguntas frequentes
Como fazer um dash simples na Unity 2D?
Zere o gravityScale do Rigidbody2D, defina linearVelocity como direção vezes velocidade do dash, espere a duração numa coroutine e restaure a gravidade. Durante o dash, impeça que o script de movimento sobrescreva a velocidade.
Como deixar o personagem invencível durante o dash?
Troque temporariamente a layer do personagem para uma layer de dash que não colide com inimigos na Layer Collision Matrix, ou use Physics2D.IgnoreLayerCollision entre a layer do player e a dos inimigos. No fim do dash, volte a layer original.
Como colocar cooldown no dash na Unity?
Guarde o momento em que o dash pode ser usado de novo, com Time.time mais o tempo de cooldown, e só aceite o input quando Time.time passar desse valor. Para dash no ar, conte quantos dashes ainda restam e recarregue ao tocar o chão.
Por que meu dash atravessa paredes?
Normalmente porque o dash move o transform direto, teleportando o personagem. Mova pelo Rigidbody2D com linearVelocity e deixe Collision Detection em Continuous. Também confira se a layer de dash não ficou sem colisão com o cenário.



