Gravidade invertida no Godot 4

Aprenda a implementar gravidade invertida godot em um CharacterBody2D com GDScript, virar o sprite, ajustar chao e teto e adicionar transicao suave.
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.
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.


