Empurrar Caixas e Blocos no Godot 4

Aprenda a empurrar caixa godot em jogos 2D com duas abordagens: física com RigidBody2D e empurrão em grade estilo sokoban com RayCast2D. Código real.
Empurrar caixas é uma daquelas mecânicas que parecem triviais até você sentar para implementar. Quando você vai fazer o player empurrar caixa godot percebe que existem várias decisões escondidas: a caixa desliza ou anda em passos fixos? Ela tem peso, inércia, atrito? Pode ser empurrada para qualquer lado ou só nas direções da grade? A resposta certa depende do tipo de jogo, e por isso este post mostra duas abordagens diferentes, com código real de Godot 4, para você escolher a que encaixa no seu projeto.
Empurrar Caixas e Blocos no Godot 4
As duas abordagens resolvem o mesmo problema visual, mas vêm de mundos opostos. A primeira usa o motor de física: a caixa é um corpo rígido que reage a impulsos, escorrega, bate em paredes e empilha com outras caixas. A segunda ignora a física e move a caixa em incrementos exatos de tile, garantindo que ela sempre pare alinhada à grade, como em um quebra-cabeça sokoban. Vamos ver cada uma e depois comparar onde cada uma brilha.
Abordagem 1: empurrar caixa godot com física e RigidBody2D
Aqui a caixa é um RigidBody2D e o player é um CharacterBody2D. O CharacterBody2D não empurra corpos rígidos sozinho. Quando ele usa move_and_slide, ele desliza ao redor dos obstáculos, mas não transfere força para eles. Precisamos detectar a colisão manualmente e aplicar um impulso na caixa na direção do movimento.
A montagem da cena fica assim. A caixa tem um RigidBody2D com um CollisionShape2D e nada de especial além de uma massa configurada. O player é um CharacterBody2D com seu shape e o script de movimento. O ponto central está em iterar as colisões geradas pelo move_and_slide no mesmo frame.
extends CharacterBody2D
const SPEED := 200.0
const PUSH_FORCE := 80.0
func _physics_process(delta: float) -> void:
var direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = direction * SPEED
move_and_slide()
_push_rigid_bodies()
func _push_rigid_bodies() -> void:
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
var collider := collision.get_collider()
if collider is RigidBody2D:
var push_dir := -collision.get_normal()
collider.apply_central_impulse(push_dir * PUSH_FORCE)
O que está acontecendo aqui. Depois de move_and_slide, o CharacterBody2D registra cada colisão do frame. get_slide_collision_count diz quantas foram e get_slide_collision(i) devolve os dados de cada uma. Se o collider for um RigidBody2D, calculamos a direção do empurrão. A normal da colisão aponta da caixa de volta para o player, então invertemos com o sinal de menos para empurrar a caixa para longe. apply_central_impulse aplica a força no centro de massa, o que evita que a caixa gire de forma estranha ao ser empurrada por um canto.
O PUSH_FORCE é o parâmetro que você vai ajustar mais. Valor baixo deixa a caixa pesada e difícil de mover. Valor alto faz a caixa sair voando ao menor toque. Comece em torno de 80 e suba ou desça testando no jogo.
A massa da caixa também importa. Como o impulso é aplicado direto, uma caixa muito leve dispara longe e uma muito pesada nem se mexe. Para um empurrão que parece natural, mantenha a massa da caixa entre 1 e 4. Se quiser um bloco que se sinta como pedra, aumente a massa em vez de reduzir a força, assim ele resiste tanto ao player quanto a outras caixas que o empurram.
Um detalhe que evita bugs: aplicar impulso todo frame enquanto o player encosta acumula velocidade. Para um deslizar mais controlado, aumente o linear_damp da caixa no inspetor. Isso adiciona um amortecimento que freia a caixa quando o player para de empurrar, sem precisar de código extra. Se você quer entender melhor como esses corpos reagem entre si, o post sobre física de jogos no Godot cobre os tipos de corpo e quando usar cada um.
Essa abordagem dá um resultado orgânico. A caixa tem inércia, escorrega depois do empurrão, pode ser tocada de qualquer ângulo e interage com a gravidade se o jogo for top-down ou plataforma. O preço é a imprevisibilidade. A caixa pode parar a meio caminho de um tile, deslizar para um buraco ou empilhar de forma que você não planejou. Para ação e exploração isso é ótimo. Para um quebra-cabeça que exige posições exatas, é uma fonte de dor de cabeça.
Abordagem 2: empurrão em grade estilo sokoban
Quando o jogo é um quebra-cabeça onde cada bloco precisa parar exatamente sobre um tile, a física atrapalha mais do que ajuda. A solução é abandonar o RigidBody2D e mover a caixa manualmente em passos do tamanho do tile. A caixa vira um StaticBody2D ou um simples Node2D com colisor, e quem decide se ela pode se mover é o player, usando um RayCast2D apontado na direção do movimento.
A lógica é a clássica do sokoban. Quando o player tenta andar em uma direção, ele primeiro verifica se há uma caixa na célula à frente. Se houver, verifica se a célula logo atrás da caixa está livre. Se estiver, a caixa anda um tile e o player avança junto. Se a célula atrás estiver bloqueada por uma parede ou outra caixa, ninguém se move.
Use dois raycasts no player, um curto para detectar a caixa e a lógica de checar o espaço livre. Aqui está uma versão enxuta com o player também em grade.
extends CharacterBody2D
const TILE := 32
@onready var box_ray: RayCast2D = $BoxRay
func try_move(dir: Vector2i) -> void:
var offset := Vector2(dir) * TILE
box_ray.target_position = offset
box_ray.force_raycast_update()
if not box_ray.is_colliding():
global_position += offset
return
var box := box_ray.get_collider()
if not box.is_in_group("caixas"):
return
if _space_is_free(box.global_position, dir):
box.global_position += offset
global_position += offset
func _space_is_free(box_pos: Vector2, dir: Vector2i) -> bool:
var target := box_pos + Vector2(dir) * TILE
var space := get_world_2d().direct_space_state
var params := PhysicsPointQueryParameters2D.new()
params.position = target
params.collide_with_bodies = true
var hits := space.intersect_point(params)
return hits.is_empty()
O fluxo é direto. try_move recebe uma direção em inteiros, tipo Vector2i(1, 0) para a direita. O box_ray é apontado para o tile vizinho e forçamos a atualização com force_raycast_update, porque queremos o resultado no mesmo frame, não no próximo. Se o raycast não bate em nada, o caminho está livre e o player simplesmente anda um tile.
Se bater em algo, conferimos se é uma caixa pelo grupo caixas. Em seguida, _space_is_free faz uma consulta de ponto na posição logo atrás da caixa. Se não houver nada lá, a caixa e o player avançam juntos um tile. Tudo em incrementos de TILE, então as posições ficam sempre cravadas na grade.
Se preferir checar o espaço livre com outro raycast em vez de uma consulta de ponto, dá certo também. Um segundo RayCast2D partindo da posição da caixa na direção do empurrão resolve. A consulta de ponto costuma ser mais limpa porque você não precisa posicionar e atualizar um nó de raycast a cada tentativa. Para quem está começando com sensores de distância, o post sobre RayCast2D no Godot explica os parâmetros e os erros comuns de configuração.
Para o jogo ficar bom, você não vai querer que a caixa teleporte para o novo tile. Em vez de setar global_position direto, anime essa transição com um Tween. Mantenha a lógica de grade decidindo o destino e use o tween só para o movimento visual entre o tile antigo e o novo. Assim você tem a precisão da grade com a suavidade da animação.
Comparando as duas abordagens
As duas técnicas servem a propósitos diferentes e vale ter clareza sobre o trade-off antes de codar.
A física com RigidBody2D é orgânica e barata de configurar. A caixa reage de forma crível, pode ser empurrada de qualquer ângulo, escorrega, empilha e interage com rampas e gravidade. Você ganha tudo isso quase de graça. O custo é a imprevisibilidade. Posições finais não são garantidas, a caixa pode parar entre tiles e situações de empilhamento podem gerar resultados que você não testou. É a escolha para jogos de ação, plataforma e exploração, onde a sensação importa mais que a precisão milimétrica.
O empurrão em grade é o oposto. Cada caixa para exatamente onde você manda, o estado do nível é totalmente previsível e fácil de validar, e checar se o quebra-cabeça foi resolvido vira uma simples comparação de posições. O custo é que você escreve mais lógica manual e perde a naturalidade da física. A caixa só anda nas direções da grade, em passos fixos, sem inércia. É a escolha óbvia para sokoban, puzzles de blocos e qualquer jogo onde a posição exata define a vitória.
Uma decisão de bastidores que afeta as duas abordagens é o tipo de nó que detecta a caixa. Se você está em dúvida entre usar uma área sensora ou um corpo físico para essa detecção, o post Area2D vs CollisionBody no Godot ajuda a decidir entre um sensor que só avisa e um corpo que de fato bloqueia o movimento.
Qual escolher
Se o seu jogo precisa que a caixa pare em posições exatas para resolver um desafio, vá de grade. Se a caixa é parte do cenário e você quer que ela se mexa de forma viva quando o player encosta, vá de física. Não existe abordagem certa em abstrato, existe a que combina com o seu design.
E nada impede de misturar as duas no mesmo projeto. Caixas de quebra-cabeça em salas específicas usando grade, e barris soltos pelo mundo usando física. O importante é entender o que cada técnica garante e o que ela abre mão, para não brigar com o motor depois. Comece simples, ajuste o PUSH_FORCE ou o tamanho do TILE, teste muito no jogo e deixe a sensação guiar o ajuste fino.

