Voltar para o Blog
Quest Log

Gravidade invertida no Godot 4

Personagem 2D andando no teto apos inverter a gravidade em um cenario de plataforma

Aprenda a implementar gravidade invertida godot em um CharacterBody2D com GDScript, virar o sprite, ajustar chao e teto e adicionar transicao suave.

Ferramenta:Godot

Mecanicas de gravidade invertida sao um classico dos jogos de plataforma. O personagem aperta uma tecla, a gravidade troca de sentido e de repente o que era chao vira teto. Jogos como VVVVVV construiram toda a identidade em cima disso. A boa noticia e que implementar gravidade invertida godot e bem mais simples do que parece: na pratica voce so precisa controlar o sinal de uma variavel e ajustar alguns detalhes de colisao e visual. Neste tutorial vamos montar essa mecanica do zero em um CharacterBody2D, usando GDScript de Godot 4 de verdade, sem gambiarra.

A ideia central e que gravidade nada mais e do que uma aceleracao aplicada na velocidade vertical do corpo a cada frame. Se a aceleracao empurra para baixo, voce cai. Se voce inverte o sinal dela, o corpo passa a ser puxado para cima. O resto do trabalho e cosmetico e de colisao: virar o sprite para o jogador entender o que aconteceu e trocar a leitura de chao por teto.

Gravidade invertida no Godot 4

Antes de escrever codigo, vale entender como o Godot 4 trata gravidade num corpo cinematico. Diferente de um RigidBody2D, o CharacterBody2D nao recebe gravidade automaticamente. Voce e quem aplica, somando aceleracao em velocity.y a cada frame de fisica. Isso e otimo para a nossa mecanica, porque significa que temos controle total sobre o sentido da forca.

Montando o CharacterBody2D com gravidade invertida godot

Vamos comecar com a estrutura basica. A peca chave e a variavel gravity_dir, um inteiro que vale 1 quando a gravidade aponta para baixo e -1 quando aponta para cima. Toda a aceleracao vertical e multiplicada por ela.

extends CharacterBody2D

@export var speed: float = 220.0
@export var jump_force: float = 420.0
@export var gravity: float = 1400.0

# 1 = gravidade normal (para baixo), -1 = gravidade invertida (para cima)
var gravity_dir: int = 1

@onready var sprite: Sprite2D = $Sprite2D


func _physics_process(delta: float) -> void:
    apply_gravity(delta)
    handle_jump()
    handle_move()
    move_and_slide()


func apply_gravity(delta: float) -> void:
    velocity.y += gravity * gravity_dir * delta

Repare na linha velocity.y += gravity * gravity_dir * delta. Quando gravity_dir e 1, a velocidade vertical aumenta no sentido positivo, ou seja, para baixo (no Godot o eixo Y cresce para baixo). Quando viramos para -1, a mesma conta passa a subtrair, puxando o corpo para cima. Nenhuma logica extra de fisica e necessaria, so o sinal muda.

Detectando chao e teto conforme o sentido

Aqui mora a primeira sutileza. Com gravidade normal, o personagem pousa no chao e usamos is_on_floor(). Mas quando a gravidade aponta para cima, o personagem encosta no teto, e a funcao que faz sentido e is_on_ceiling(). Se voce nao tratar isso, o pulo simplesmente nao funciona com a gravidade invertida.

A forma limpa de resolver e criar um helper que devolve se o personagem esta apoiado, levando em conta o sentido atual:

func is_grounded() -> bool:
    if gravity_dir == 1:
        return is_on_floor()
    return is_on_ceiling()


func handle_jump() -> void:
    if Input.is_action_just_pressed("jump") and is_grounded():
        # o pulo sempre empurra contra a gravidade atual
        velocity.y = -jump_force * gravity_dir

O detalhe importante esta em -jump_force * gravity_dir. Com gravidade normal (gravity_dir = 1), isso vira -jump_force, ou seja, impulso para cima. Com gravidade invertida (gravity_dir = -1), vira +jump_force, impulso para baixo, que e exatamente o que voce quer quando o personagem esta grudado no teto e precisa pular na direcao oposta.

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

Configurando o up_direction do corpo

O CharacterBody2D tem uma propriedade chamada up_direction. Ela diz ao motor qual e o vetor que aponta para cima, e e justamente isso que move_and_slide() usa para decidir o que conta como chao e o que conta como teto, alem de influenciar o comportamento de subir rampas e deslizar.

Por padrao up_direction vale Vector2.UP, que aponta para cima da tela. Quando invertemos a gravidade, faz sentido inverter tambem essa referencia, para que o motor classifique a superficie corretamente. Vamos guardar essa troca dentro da funcao que faz a inversao:

func set_gravity_dir(new_dir: int) -> void:
    gravity_dir = new_dir
    # atualiza a referencia de "cima" do corpo
    up_direction = Vector2.UP * float(gravity_dir)

Com up_direction sincronizado, is_on_floor() e is_on_ceiling() passam a refletir a fisica que o jogador percebe na tela. Em jogos mais simples voce pode ate ignorar essa parte e confiar so no helper is_grounded(), mas ajustar up_direction deixa o comportamento de colisao mais consistente, especialmente com rampas e plataformas inclinadas.

Virando o sprite ao inverter

Agora a parte que comunica a mecanica ao jogador. Sem feedback visual, a gravidade invertida fica confusa: o personagem continua de cabeca para cima andando no teto, o que parece um bug. A solucao e espelhar o sprite no eixo vertical.

Existem dois caminhos. O primeiro e usar flip_v do Sprite2D, que e direto e nao mexe na escala do no. O segundo e inverter o scale.y do proprio sprite ou de um no pai, o que tambem vira colisores filhos caso voce dependa disso. Para a maioria dos casos, flip_v resolve:

func update_sprite_flip() -> void:
    sprite.flip_v = gravity_dir == -1

Se voce tiver animacoes ou nos filhos que precisam acompanhar a inversao, a abordagem de escala costuma ser mais robusta:

func update_sprite_scale() -> void:
    sprite.scale.y = -1.0 if gravity_dir == -1 else 1.0

Escolha uma das duas e mantenha a coerencia. Misturar flip_v com scale.y negativo no mesmo no acaba se cancelando e gera dor de cabeca depois.

A tecla que dispara a inversao

Com tudo no lugar, falta o gatilho. Cadastre uma acao de input no projeto, por exemplo flip_gravity mapeada para a tecla Shift ou para um botao do controle, e chame a troca quando ela for pressionada:

func handle_flip_input() -> void:
    if Input.is_action_just_pressed("flip_gravity"):
        set_gravity_dir(-gravity_dir)
        update_sprite_flip()

E lembre de chamar handle_flip_input() dentro do _physics_process, junto das outras chamadas:

func _physics_process(delta: float) -> void:
    handle_flip_input()
    apply_gravity(delta)
    handle_jump()
    handle_move()
    move_and_slide()

A funcao handle_move() cuida do deslocamento horizontal e independe da gravidade. Fica assim:

func handle_move() -> void:
    var dir: float = Input.get_axis("move_left", "move_right")
    if dir != 0.0:
        velocity.x = dir * speed
    else:
        velocity.x = move_toward(velocity.x, 0.0, speed)

Transicao suave opcional

Inverter a gravidade de forma instantanea funciona e tem um charme retro, mas as vezes voce quer uma sensacao mais fluida, com o personagem desacelerando e mudando de sentido aos poucos. Da para conseguir isso interpolando o efeito da gravidade em vez de trocar o sinal de uma vez.

Uma maneira simples e manter um valor de direcao em ponto flutuante e mover ele suavemente em direcao ao alvo:

var gravity_dir_smooth: float = 1.0
@export var flip_speed: float = 6.0

func apply_gravity(delta: float) -> void:
    gravity_dir_smooth = move_toward(
        gravity_dir_smooth, float(gravity_dir), flip_speed * delta
    )
    velocity.y += gravity * gravity_dir_smooth * delta

Com isso, ao apertar a tecla o gravity_dir muda na hora para -1, mas gravity_dir_smooth leva alguns frames para chegar la. Durante essa transicao a gravidade passa por zero, o personagem flutua por um instante e depois e puxado para o novo sentido. O ajuste de flip_speed controla quao rapida e a virada. Para o sprite, voce pode trocar o flip_v por uma rotacao animada com um Tween, girando o no em 180 graus no mesmo intervalo da transicao.

Vale notar que com transicao suave o helper is_grounded() pode ficar instavel no meio da virada, quando o personagem nao esta nem no chao nem no teto. Em geral isso nao e problema, ja que o jogador costuma estar no ar durante a inversao de qualquer jeito.

Juntando as pecas

O fluxo final fica claro: a cada frame de fisica voce checa o input de inversao, aplica a gravidade no sentido atual, processa pulo e movimento horizontal e chama move_and_slide() sem argumentos, do jeito que Godot 4 espera. A variavel gravity_dir e a unica fonte de verdade para o sentido, e tudo o mais (colisao, pulo, sprite, up_direction) deriva dela.

Esse padrao escala bem. Voce pode adicionar zonas que forcam um sentido especifico, plataformas que so existem em uma das gravidades ou inimigos que tambem respondem a inversao. Como toda a logica gira em torno de um inteiro e de algumas multiplicacoes, dar manutencao e expandir fica tranquilo.

Se voce ainda esta montando a base de controle do personagem, vale revisar o movimento de personagem em plataforma 2D antes de sofisticar a gravidade. E para entender melhor como o motor lida com forcas e colisoes por baixo dos panos, da uma olhada no guia de fisica de jogos no Godot, que ajuda a tomar decisoes melhores quando a mecanica comecar a crescer.

No fim, gravidade invertida e um daqueles recursos que parecem complexos mas se resumem a controlar um sinal com cuidado. Comece simples, com troca instantanea, garanta que pulo e colisao respondem ao sentido certo e so depois invista na transicao suave e nos efeitos visuais. Teste bastante encostando no teto e no chao, porque os bugs dessa mecanica quase sempre aparecem na deteccao de apoio.