CharacterBody2D: por que usar e como a física entra no movimento
Para personagens controlados pelo jogador em jogos 2D, o CharacterBody2D é o nó mais indicado porque ele já foi pensado para movimentação baseada em colisão e “deslizamento” em superfícies. A ideia central é simples: você mantém um vetor velocity (velocidade atual) e, a cada passo de física, atualiza esse vetor com aceleração, gravidade e pulo; em seguida chama move_and_slide() para aplicar o movimento respeitando colisões.
No Godot 4, CharacterBody2D já possui a propriedade velocity. Você não move o personagem alterando diretamente position; em vez disso, você calcula velocity e deixa o motor resolver o deslocamento e as colisões.
O papel de move_and_slide()
- Move o corpo de acordo com
velocitye o tempo de física. - Ao colidir, ajusta o movimento para “deslizar” ao longo das superfícies.
- Atualiza informações úteis como
is_on_floor(),is_on_wall()eis_on_ceiling(), que você usa para pulo, atrito, etc.
_process vs _physics_process: quando usar cada um
Na Godot, existem dois loops principais para atualizar lógica:
| Método | Ritmo | Use para | Evite para |
|---|---|---|---|
_process(delta) | Varia conforme FPS | Animações não físicas, UI, efeitos visuais, timers simples, ler input para interface | Movimento com colisão e física (pode ficar inconsistente) |
_physics_process(delta) | Fixo (ex.: 60x/s) | Movimento com colisão, gravidade, pulo, qualquer lógica dependente de física | Atualizações visuais que não precisam de passo fixo |
Regra prática: se você chama move_and_slide(), faça isso em _physics_process.
Passo a passo prático: movimento e física com CharacterBody2D
Pré-requisitos da cena (sem repetir conceitos básicos)
Você precisa de um nó CharacterBody2D com um CollisionShape2D configurado e um chão com colisão (por exemplo, um StaticBody2D com CollisionShape2D). O script abaixo deve estar no CharacterBody2D.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Etapa 1 — Movimento horizontal básico (sem suavização)
Objetivo: andar para esquerda/direita com velocidade constante. Isso ajuda a validar colisões e o fluxo velocity + move_and_slide().
extends CharacterBody2D
const SPEED := 220.0
func _physics_process(delta: float) -> void:
var dir := Input.get_axis("move_left", "move_right")
velocity.x = dir * SPEED
move_and_slide()Como testar: rode a cena e verifique se o personagem para ao soltar as teclas e não atravessa o chão/parede. Se atravessar, o problema geralmente está na colisão (shapes) ou no tipo de corpo do chão.
Etapa 2 — Suavização com aceleração e desaceleração
Agora vamos evitar mudanças instantâneas de velocidade. Em vez de setar velocity.x diretamente, vamos aproximar a velocidade atual de um alvo usando aceleração (quando há input) e desaceleração (quando não há input).
Uma forma comum é usar move_toward (função do Godot para aproximar um valor de outro por um passo máximo).
extends CharacterBody2D
@export var max_speed: float = 260.0
@export var acceleration: float = 1200.0
@export var deceleration: float = 1600.0
func _physics_process(delta: float) -> void:
var dir := Input.get_axis("move_left", "move_right")
var target_speed := dir * max_speed
if dir != 0.0:
velocity.x = move_toward(velocity.x, target_speed, acceleration * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, deceleration * delta)
move_and_slide()Valores iniciais sugeridos:
max_speed: 240 a 320acceleration: 900 a 2000deceleration: 1200 a 2600 (geralmente maior que a aceleração para “frear” mais rápido)
Como sentir a diferença: aumente acceleration para deixar a resposta mais “arcade” (arranca rápido). Aumente deceleration para parar mais seco ao soltar o botão.
Etapa 3 — Limites de velocidade e parâmetros ajustáveis em tempo real
Mesmo com alvo, é útil garantir limites (por exemplo, se você adicionar empurrões, vento, knockback). Vamos aplicar clamp em velocity.x para assegurar que não passe do máximo.
extends CharacterBody2D
@export var max_speed: float = 280.0
@export var acceleration: float = 1400.0
@export var deceleration: float = 1800.0
func _physics_process(delta: float) -> void:
var dir := Input.get_axis("move_left", "move_right")
var target_speed := dir * max_speed
if dir != 0.0:
velocity.x = move_toward(velocity.x, target_speed, acceleration * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, deceleration * delta)
velocity.x = clamp(velocity.x, -max_speed, max_speed)
move_and_slide()Ajuste em tempo real: como as variáveis são @export, você pode selecioná-las no Inspector e alterar valores enquanto o jogo está rodando para “sentir” o controle. Um fluxo prático:
- Comece com
max_speed = 280,acceleration = 1400,deceleration = 1800. - Se estiver “escorregando” demais ao parar, aumente
deceleration. - Se estiver “pesado” para arrancar, aumente
acceleration. - Se estiver rápido demais para o tamanho da fase, reduza
max_speed.
Gravidade e pulo com is_on_floor()
Para um platformer, você normalmente controla o eixo X (horizontal) e deixa o eixo Y (vertical) ser influenciado por gravidade e pulo. A gravidade é uma aceleração constante para baixo; o pulo é um impulso inicial para cima (velocidade negativa no eixo Y, pois em 2D o eixo Y cresce para baixo).
Implementando gravidade
A cada passo de física, se o personagem não estiver no chão, somamos gravidade em velocity.y. Quando está no chão, é comum manter velocity.y “colada” (por exemplo, 0), para evitar acumular valores residuais.
Implementando pulo com checagem de chão
O pulo deve acontecer apenas quando is_on_floor() for verdadeiro (ou seja, o personagem está apoiado). Isso impede pulo infinito no ar.
extends CharacterBody2D
@export var max_speed: float = 280.0
@export var acceleration: float = 1400.0
@export var deceleration: float = 1800.0
@export var gravity: float = 1400.0
@export var jump_velocity: float = -480.0
func _physics_process(delta: float) -> void:
# Horizontal
var dir := Input.get_axis("move_left", "move_right")
var target_speed := dir * max_speed
if dir != 0.0:
velocity.x = move_toward(velocity.x, target_speed, acceleration * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, deceleration * delta)
velocity.x = clamp(velocity.x, -max_speed, max_speed)
# Vertical (gravidade + pulo)
if not is_on_floor():
velocity.y += gravity * delta
else:
# Ajuda a estabilizar no chão (opcional)
if velocity.y > 0.0:
velocity.y = 0.0
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
move_and_slide()Valores iniciais sugeridos:
gravity: 1200 a 2200 (quanto maior, mais “pesado” e rápido cai)jump_velocity: -380 a -620 (mais negativo = pula mais alto)
Como testar e ajustar rapidamente:
- Se o pulo estiver baixo demais, torne
jump_velocitymais negativo (ex.: de-480para-540). - Se o personagem cair muito devagar, aumente
gravity. - Se o personagem “flutuar” no topo do pulo, aumente um pouco a gravidade ou reduza a força do pulo.
Exercício incremental (3 etapas) para fixar o controle do personagem
Exercício 1 — Movimento básico
- Implemente apenas a Etapa 1 (velocidade constante).
- Teste em uma fase com uma plataforma longa e uma parede.
- Checklist: encosta na parede e para? não atravessa o chão? a velocidade é a esperada?
Exercício 2 — Suavização
- Substitua o movimento básico pela Etapa 2 (aceleração/desaceleração).
- Durante o jogo rodando, ajuste
accelerationedecelerationno Inspector. - Meta: encontrar um par de valores em que o personagem responda rápido, mas sem “teleportar” para a velocidade máxima.
Exercício 3 — Limites + gravidade + pulo com parâmetros exportados
- Implemente a Etapa 3 completa (clamp + gravidade + pulo).
- Defina valores iniciais:
max_speed=280,acceleration=1400,deceleration=1800,gravity=1400,jump_velocity=-480. - Teste em tempo real: aumente
gravityem passos de 200 e observe a queda; depois ajustejump_velocityem passos de 40 até o pulo ficar confortável. - Checklist: o pulo só funciona no chão? ao cair, o personagem não atravessa o piso? ao soltar o direcional, ele desacelera de forma agradável?