Voltar para o Blog
Quest Log

Empurrar Caixas e Blocos no Godot 4

Personagem 2D empurrando uma caixa de madeira sobre o chão de pedra em um nível de plataforma no Godot.

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.

Ferramenta:Godot

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.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

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.