Script de Movimento 2D na Unity (C# com Input System)
Um script de movimento Unity 2D que funciona de verdade lê o input pelo Input System, aplica velocidade no Rigidbody2D com linearVelocity, acelera e freia em vez de ligar e desligar, e vira o sprite para o lado certo. Aqui você monta o PlayerMovement2D.cs completo, pronto para colar num projeto do Unity 6.3 LTS, e entende por que cada linha está ali.
No fim você vai ter um personagem que anda com peso, não atravessa parede, não treme com a câmera e não gruda no muro quando pula encostado nele. Esses três problemas aparecem em quase todo primeiro jogo de plataforma, e todos têm causa conhecida.
O que o script de movimento 2D precisa fazer
Antes de abrir o editor, vale listar o trabalho. Um movimento horizontal de plataforma faz quatro coisas:
- Ler a intenção do jogador. Um valor de -1 a 1 no eixo X, vindo de teclado, controle ou touch.
- Transformar intenção em velocidade. Velocidade alvo = input vezes velocidade máxima.
- Chegar na velocidade alvo com uma curva. Aceleração ao sair do lugar, desaceleração ao soltar o botão. É isso que separa "anda" de "controla bem".
- Mostrar para onde está indo. Virar o sprite, e de preferência o ponto de onde sai o tiro junto.
O pulo fica de fora de propósito. Ele tem ground check, altura variável e coyote time, e merece o próprio script: está no tutorial de pulo na Unity 2D com ground check. Separar os dois deixa cada arquivo pequeno e fácil de ajustar.
Preparar o personagem: Rigidbody2D e collider
Crie um GameObject chamado Player com um SpriteRenderer. Depois adicione os componentes nesta ordem e ajuste no Inspector:
Rigidbody2D
- Body Type:
Dynamic. - Gravity Scale:
3é um bom ponto de partida para plataforma (o padrão 1 deixa a queda lenta, com cara de lua). - Collision Detection:
Continuous. Evita que o personagem atravesse colliders finos em alta velocidade. - Interpolate:
Interpolate. Suaviza a posição desenhada entre os passos de física. Sem isso a câmera vai expor tremedeira. - Constraints: marque
Freeze Rotation Z. Sem isso o personagem tomba quando bate na quina de uma plataforma.
CapsuleCollider2D
- Direction:
Vertical. A cápsula tem a base arredondada, então o personagem não engancha em emendas de tiles como acontece com umBoxCollider2D.
Physics Material 2D
- Crie em
Assets > Create > 2D > Physics Material 2D, coloque Friction0e arraste para o campo Material do collider do player. Isso resolve o "grudar na parede", que aparece lá nos erros comuns.
O chão e as paredes podem ser um Tilemap com Tilemap Collider 2D ou sprites com BoxCollider2D. Ponha tudo que é cenário numa layer Ground; o script de pulo vai precisar dela.
Ler input com o Input System
Projetos novos do Unity 6 já vêm com o pacote Input System instalado e com um asset chamado InputSystem_Actions configurado como project-wide actions (em Edit > Project Settings > Input System Package). Esse asset traz um mapa Player com ações prontas como Move, Jump, Look e Interact, e as project-wide actions já ficam habilitadas por padrão. No Unity 6.3 LTS, a versão do pacote listada no manual é a 1.20.1.
Na prática isso significa que você não precisa criar asset nem gerar classe C#. Basta pedir a ação pelo nome:
InputAction moveAction = InputSystem.actions.FindAction("Move");
Vector2 move = moveAction.ReadValue<Vector2>();
A ação Move devolve um Vector2: WASD e setas no teclado, analógico esquerdo no controle. Para plataforma você usa só o x. Guarde a referência da ação no Awake em vez de chamar FindAction todo frame: a busca é por string e não tem por que repetir.
Se o seu projeto veio de uma versão antiga e ainda usa Input.GetAxis, confira em Edit > Project Settings > Player > Active Input Handling. Em Input System Package (New) o código antigo para de funcionar; em Both os dois convivem.
Script movimento unity 2d completo
Crie o arquivo PlayerMovement2D.cs (o nome da classe tem que ser igual ao do arquivo) e cole:
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMovement2D : MonoBehaviour
{
[Header("Velocidade")]
[SerializeField] private float maxSpeed = 7f;
[SerializeField] private float acceleration = 70f;
[SerializeField] private float deceleration = 90f;
[SerializeField] private float turnAcceleration = 120f;
[SerializeField, Range(0f, 1f)] private float airControl = 0.65f;
[Header("Referências")]
[SerializeField] private Rigidbody2D rb;
[SerializeField] private Transform visual;
[Header("Chão (para o controle no ar)")]
[SerializeField] private Transform groundCheck;
[SerializeField] private Vector2 groundCheckSize = new Vector2(0.5f, 0.1f);
[SerializeField] private LayerMask groundLayer;
private InputAction moveAction;
private float inputX;
private bool facingRight = true;
public bool FacingRight => facingRight;
private void Awake()
{
if (rb == null)
{
rb = GetComponent<Rigidbody2D>();
}
moveAction = InputSystem.actions.FindAction("Move");
}
private void Update()
{
inputX = moveAction.ReadValue<Vector2>().x;
if (inputX > 0.01f && !facingRight)
{
Flip();
}
else if (inputX < -0.01f && facingRight)
{
Flip();
}
}
private void FixedUpdate()
{
float targetSpeed = inputX * maxSpeed;
float currentSpeed = rb.linearVelocity.x;
float rate;
if (Mathf.Abs(targetSpeed) < 0.01f)
{
rate = deceleration;
}
else if (Mathf.Sign(targetSpeed) != Mathf.Sign(currentSpeed) && Mathf.Abs(currentSpeed) > 0.01f)
{
rate = turnAcceleration;
}
else
{
rate = acceleration;
}
if (!IsGrounded())
{
rate *= airControl;
}
float newSpeed = Mathf.MoveTowards(currentSpeed, targetSpeed, rate * Time.fixedDeltaTime);
rb.linearVelocity = new Vector2(newSpeed, rb.linearVelocity.y);
}
private bool IsGrounded()
{
if (groundCheck == null)
{
return true;
}
return Physics2D.OverlapBox(groundCheck.position, groundCheckSize, 0f, groundLayer) != null;
}
private void Flip()
{
facingRight = !facingRight;
Transform target = visual != null ? visual : transform;
Vector3 scale = target.localScale;
scale.x *= -1f;
target.localScale = scale;
}
private void OnDrawGizmosSelected()
{
if (groundCheck == null)
{
return;
}
Gizmos.color = Color.green;
Gizmos.DrawWireCube(groundCheck.position, groundCheckSize);
}
}
Configure no Inspector: arraste o próprio Rigidbody2D em Rb, crie um filho vazio GroundCheck encostado nos pés e arraste em Ground Check, e marque a layer Ground em Ground Layer. O campo Visual é opcional; ele aparece de novo na seção de virar o sprite.
Três decisões do script merecem explicação:
- Input no
Update, física noFixedUpdate. O input é amostrado a cada frame e a velocidade é aplicada no passo fixo da física. Ler input dentro doFixedUpdatefunciona para valores contínuos como oMove, mas perde toques rápidos de botão; manter o padrão desde já evita surpresa quando o pulo entrar. - Só o X é escrito.
new Vector2(newSpeed, rb.linearVelocity.y)preserva a velocidade vertical, que pertence à gravidade e ao pulo. No Unity 6 também existerb.linearVelocityX = newSpeed;, que faz a mesma coisa em uma linha. Time.fixedDeltaTimenoMoveTowards. A aceleração está em unidades por segundo ao quadrado. Multiplicar pelo passo fixo garante o mesmo comportamento com 50 ou 60 passos de física por segundo. É o mesmo princípio do delta time: o resultado depende do tempo que passou, não de quantas vezes o código rodou.
Aceleração e desaceleração
O coração do script é este trecho:
float newSpeed = Mathf.MoveTowards(currentSpeed, targetSpeed, rate * Time.fixedDeltaTime);
MoveTowards anda da velocidade atual até a velocidade alvo, no máximo rate * dt por passo. Com maxSpeed = 7 e acceleration = 70, o personagem leva 0,1 segundo para chegar na velocidade máxima. Com deceleration = 90, freia em menos de 0,08 segundo. Esses números dão um movimento responsivo, com um mínimo de inércia que o olho percebe.
Por que três taxas e não uma?
accelerationcontrola a arrancada. Valor alto (100+) deixa o controle seco, estilo Celeste. Valor baixo (20 a 30) dá sensação de personagem pesado, estilo plataforma com gelo.decelerationcontrola o freio ao soltar. Se for menor que a aceleração, o personagem "desliza" um pouco ao parar, o que costuma parecer descontrole.turnAccelerationentra quando o jogador inverte a direção com o personagem ainda andando. Sem essa taxa separada, virar parece lento, porque a velocidade precisa primeiro ir a zero e depois crescer do outro lado com a aceleração normal.
O airControl multiplica tudo quando o personagem está no ar. Com 0.65, mudar de direção no meio do pulo continua possível, mas custa mais. Coloque 1 para controle total no ar (comum em jogos de precisão) ou 0.3 para pulos mais comprometidos.
A forma de testar: deixe o jogo rodando e mexa nos valores pelo Inspector com o Play ligado. Quando achar o ponto, copie o componente (os três pontinhos, Copy Component), saia do Play e use Paste Component Values. Ajuste fino de sensação é o assunto do post de game feel e juice, que vale ler depois que o movimento estiver no lugar.
Um personagem que anda bem ainda está longe de um jogo. Na hora de juntar este script com pulo, animação, câmera, inimigos e uma fase que tenha começo e fim, é comum o projeto virar uma pilha de scripts que se atropelam e ninguém mais sabe quem mexe na velocidade de quem. Essa costura entre sistemas é onde mais gente trava aprendendo sozinha, e é o que um curso de Unity organizado em projeto completo resolve melhor do que tutoriais soltos.
Virar o sprite na direção: flipX, escala ou rotação
A busca "unity flip sprite" tem três respostas, e cada uma tem consequência diferente.
1. SpriteRenderer.flipX
spriteRenderer.flipX = inputX < 0f;
É o mais simples e só espelha o desenho. O transform não muda, então os filhos continuam no mesmo lugar: o FirePoint da arma continua do lado direito, o collider de ataque continua apontando para a direita, e partículas filhas não viram. O flipX também não vira hitbox. Se o seu BoxCollider2D tem offset (por exemplo, um personagem com uma espada que sai do corpo), o collider fica do lado errado quando ele olha para a esquerda. Serve bem para personagens simétricos sem filhos.
2. Escala X negativa
É o que o script acima usa no Flip(). Escala negativa espelha o objeto e todos os filhos, incluindo colliders 2D, pontos de spawn e partículas. A física 2D da Unity aceita escala negativa nos colliders. O cuidado: aplique no objeto Visual (um filho que segura sprite e pontos de ataque) quando o personagem tiver UI presa nele, como uma barra de vida, senão a barra também espelha e o texto fica de trás para frente.
3. Rotação de 180 graus no Y
transform.Rotate(0f, 180f, 0f);
Visualmente dá o mesmo resultado da escala negativa, com uma vantagem: transform.right passa a apontar para a esquerda. Isso deixa o código de tiro trivial, porque o projétil sai sempre em firePoint.right, sem if de direção. Para quem tem muito código que usa transform.right como "frente", é o jeito mais limpo.
Qual usar? Personagem simples sem filhos: flipX. Personagem com arma, ataque ou hitbox deslocada: escala negativa num filho Visual, ou rotação no Y se o seu código de tiro usa right. Escolha um e use no projeto inteiro, porque misturar os três num mesmo personagem gera o bug clássico do personagem que vira duas vezes e fica de costas.
Erros comuns: tremedeira, atravessar parede, grudar
Atravessar parede. Quase sempre é transform.position += ... ou transform.Translate(...) num objeto com Rigidbody2D. Isso teleporta o personagem para a nova posição a cada frame, ignorando colisões; a física só percebe a sobreposição depois e empurra de volta, quando consegue. Com velocidade alta, o collider pula a parede inteira entre um frame e outro. A correção é mover pelo Rigidbody2D (como no script) e manter Collision Detection em Continuous. Se mesmo assim atravessa, confira se a parede tem collider e se a layer dela colide com a do player em Edit > Project Settings > Physics 2D > Layer Collision Matrix.
Tremedeira com a câmera. O personagem se move em passos de física e a tela desenha em frames variáveis. Se a câmera segue a posição do transform no Update, ela pega o personagem em momentos diferentes do passo. Solução em duas partes: Interpolate no Rigidbody2D e câmera seguindo no LateUpdate, ou Cinemachine com o Brain no modo de atualização padrão.
Grudar na parede. O jogador pula encostado num muro, segura a direção do muro e o personagem fica parado no ar. É atrito: o collider empurra a parede e a fricção segura a queda. O Physics Material 2D com Friction 0 no collider do player resolve. Se você quer atrito no chão (para escorregar menos em rampas), aplique o material sem atrito só nas paredes.
velocity não existe ou dá aviso. Tutorial antigo. No Unity 6 é linearVelocity, e drag virou linearDamping.
Personagem lento demais no ar ou flutuando. Gravity Scale em 1. Suba para 3 ou 4 e ajuste a força do pulo depois.
Input não responde. A ação foi buscada por um nome que não existe no asset (o FindAction não diferencia maiúsculas, mas um erro de digitação devolve null) ou o Active Input Handling está em Input Manager (Old). Um Debug.Log(moveAction.ReadValue<Vector2>()) no Update mostra na hora se o problema é input ou física.
Próximo passo
Com o PlayerMovement2D no lugar, o próximo arquivo é o pulo. O tutorial de pulo na Unity 2D com ground check, altura variável e coyote usa o mesmo GroundCheck e a mesma layer Ground deste post, então os dois scripts encaixam sem mudar nada.
Antes de seguir, faça um exercício rápido: crie três presets de valores (pesado, padrão e seco) e troque entre eles no Play. Perceber como a aceleração muda a sensação do jogo é a base para todo ajuste de movimento que você vai fazer depois, em qualquer engine. Se quiser comparar com outra abordagem, o post sobre movimento de personagem de plataforma 2D faz a mesma lógica de aceleração e fricção no Godot, e ver as duas lado a lado ajuda a separar o que é conceito do que é API.
Perguntas frequentes
Devo mover o personagem 2D pelo transform ou pelo Rigidbody2D?
Se o personagem colide com o cenário, mova pelo Rigidbody2D (linearVelocity, AddForce ou MovePosition). Mexer direto em transform.position teleporta o objeto a cada frame e a física só descobre a parede depois que o collider já entrou nela. Transform puro serve para objetos sem colisão, como um efeito decorativo.
Por que Rigidbody2D.velocity dá erro ou aviso no Unity 6?
A Unity renomeou a propriedade para linearVelocity (e drag para linearDamping) no Unity 6. Tutoriais antigos usam rb.velocity; no Unity 6.3 LTS use rb.linearVelocity, ou linearVelocityX e linearVelocityY para mexer em um eixo só.
Como virar o sprite do personagem 2D na Unity?
Os três jeitos comuns são SpriteRenderer.flipX, escala X negativa no transform e rotação de 180 graus no eixo Y. O flipX só espelha o desenho; escala e rotação viram também os filhos, como o ponto de onde sai o tiro e as hitboxes.
Meu personagem 2D treme quando a câmera segue. Como resolvo?
Ligue Interpolate no Rigidbody2D e mova a câmera em LateUpdate (ou use Cinemachine). A física roda em passos fixos e a tela em frames variáveis; sem interpolação, o sprite salta entre posições de física e a câmera evidencia o salto.



