Esteiras transportadoras (conveyor) no Godot 4

Aprenda a criar uma esteira godot 2d que empurra personagens e objetos, com duas abordagens praticas, velocidade exportada, textura animada e reversao.
Esteiras transportadoras aparecem em quase todo jogo de plataforma ou puzzle industrial. O chao se move debaixo dos pes do jogador, caixas deslizam sozinhas, e de repente o nivel ganha movimento sem voce precisar animar nada manualmente. Montar uma esteira godot 2d que se comporta direito parece simples, mas tem detalhes que travam muita gente: o personagem nao gruda, objetos empurrados saem voando, ou a velocidade nao bate com a animacao da textura. Aqui voce vai ver duas formas de fazer isso no Godot 4, com o codigo que realmente funciona na engine atual, e vai entender quando escolher cada uma.
Esteiras transportadoras (conveyor) no Godot 4
A ideia central de uma esteira eh aplicar um deslocamento horizontal constante em qualquer corpo que esteja apoiado nela. O no da esteira fica parado no lugar, mas a superficie "arrasta" o que estiver em cima. Existem dois caminhos limpos para isso no Godot 4, e cada um resolve um tipo de situacao melhor que o outro.
Abordagem 1: constant_linear_velocity no StaticBody2D
O Godot 4 tem uma propriedade pouco conhecida no StaticBody2D chamada constant_linear_velocity. Ela faz exatamente o que o nome diz: o corpo nao se move, mas o motor de fisica trata a superficie como se ela estivesse deslizando naquela velocidade. Qualquer RigidBody2D que encostar em cima recebe esse arrasto de graca, sem uma linha de codigo de fisica manual.
Essa eh a forma mais barata e mais correta fisicamente para empurrar caixas, barris e qualquer coisa baseada em RigidBody2D.
extends StaticBody2D
# Direcao da esteira: 1 = direita, -1 = esquerda
@export var direcao: int = 1
@export var velocidade: float = 200.0
@export var reversivel: bool = true
func _ready() -> void:
_aplicar_velocidade()
func _aplicar_velocidade() -> void:
# A esteira nao se move, mas arrasta o que esta em cima
constant_linear_velocity = Vector2(direcao * velocidade, 0.0)
func reverter() -> void:
if not reversivel:
return
direcao *= -1
_aplicar_velocidade()
Repare que constant_linear_velocity eh uma propriedade nativa do StaticBody2D. Voce nao precisa de _physics_process nem de deteccao de colisao. O motor cuida do resto. Para reverter a esteira em runtime, basta chamar reverter() a partir de um botao, alavanca ou sinal de timer.
O ponto fraco dessa abordagem aparece quando o seu personagem eh um CharacterBody2D, que eh o caso da maioria dos jogos de plataforma. O CharacterBody2D ignora constant_linear_velocity porque ele nao reage a fisica automatica: ele so se move quando voce manda. Para o jogador, voce precisa da abordagem 2.
Abordagem 2: Area2D mais deslocamento no CharacterBody2D
Aqui a esteira detecta quem esta em cima usando uma Area2D e adiciona um deslocamento na velocity do personagem a cada frame. Essa eh a tecnica que funciona bem com CharacterBody2D, que eh o no controlado por codigo via move_and_slide().
O fluxo eh: a esteira mantem uma lista dos corpos em cima dela, calcula o vetor de empurrao, e o personagem consulta esse valor no seu proprio _physics_process.
Primeiro, o script da esteira:
extends Area2D
class_name Esteira
@export var direcao: int = 1
@export var velocidade: float = 180.0
@export var reversivel: bool = true
func get_empurrao() -> Vector2:
# Deslocamento horizontal aplicado a quem esta em cima
return Vector2(direcao * velocidade, 0.0)
func reverter() -> void:
if reversivel:
direcao *= -1
Agora, no CharacterBody2D do jogador, voce detecta se ele esta apoiado em uma esteira e soma o empurrao. A forma mais robusta eh perguntar ao proprio personagem com que ele esta colidindo no chao, usando get_last_slide_collision() depois do move_and_slide():
extends CharacterBody2D
@export var velocidade_andar: float = 300.0
@export var forca_pulo: float = 600.0
@export var gravidade: float = 1400.0
func _physics_process(delta: float) -> void:
# Gravidade
if not is_on_floor():
velocity.y += gravidade * delta
# Input horizontal do jogador
var entrada := Input.get_axis("ui_left", "ui_right")
velocity.x = entrada * velocidade_andar
# Pulo
if is_on_floor() and Input.is_action_just_pressed("ui_accept"):
velocity.y = -forca_pulo
# Empurrao da esteira, somado a velocidade do frame
var empurrao := _empurrao_da_esteira()
velocity += empurrao
move_and_slide()
func _empurrao_da_esteira() -> Vector2:
if not is_on_floor():
return Vector2.ZERO
# Procura uma Esteira entre as colisoes do chao
for i in get_slide_collision_count():
var colisao := get_slide_collision(i)
var corpo := colisao.get_collider()
if corpo is Esteira:
return (corpo as Esteira).get_empurrao()
# Se a Esteira for uma Area2D separada do chao, checa por grupo
if corpo and corpo.has_method("get_empurrao"):
return corpo.get_empurrao()
return Vector2.ZERO
Detalhe importante: o empurrao eh somado depois do input horizontal e antes do move_and_slide(). Assim, se o jogador andar contra a esteira, as forcas se cancelam parcialmente, que eh o comportamento esperado. Se ele soltar os controles, a esteira o leva sozinha.
Se a sua esteira eh uma Area2D que cobre a regiao acima do chao em vez de ser o proprio piso, troque a deteccao por sinais body_entered e body_exited, mantendo uma lista de corpos dentro da area e aplicando o empurrao quando o personagem estiver nessa lista. As duas variacoes valem; a escolha depende de como voce montou as colisoes do nivel.
Animando a textura da esteira
Uma esteira parada visualmente fica estranha. O olho espera ver a superficie rolando. Tem duas formas simples de animar isso.
A primeira eh rolar a regiao de um Sprite2D com region_enabled ligado, deslocando region_rect a cada frame. A textura precisa ser configurada com repeticao (CanvasItem com filtro e a textura importada com Repeat):
extends Sprite2D
@export var velocidade_scroll: float = 180.0
@export var direcao: int = 1
func _process(delta: float) -> void:
# Rola a regiao na direcao da esteira
region_rect.position.x += direcao * velocidade_scroll * delta
A segunda forma eh usar um AnimationPlayer com uma animacao em loop que percorre os frames de um sprite sheet da esteira. Essa opcao eh melhor quando a arte ja vem com quadros desenhados, porque garante um visual consistente. Voce so precisa inverter a animacao quando a esteira reverter, ajustando speed_scale do AnimationPlayer para um valor negativo:
extends AnimationPlayer
@export var direcao: int = 1
func atualizar_direcao(nova_direcao: int) -> void:
direcao = nova_direcao
# speed_scale negativo roda a animacao ao contrario
speed_scale = float(direcao)
Para amarrar tudo, o ideal eh que a velocidade visual bata com a velocidade fisica. Se a esteira empurra a 180 pixels por segundo, a textura deve rolar mais ou menos na mesma cadencia. Nao precisa ser exato ao pixel, mas se o visual andar para um lado e o corpo for empurrado para o outro, fica obvio que algo esta errado.
Esteira reversivel com um sinal
Tornar a esteira reversivel eh so trocar o sinal de direcao. O cuidado eh propagar essa mudanca para os tres lugares que dependem dela: a fisica, a animacao da textura e qualquer indicador visual de direcao. Centralizar isso em um unico metodo evita estados dessincronizados.
extends Node2D
@export var reversivel: bool = true
@onready var corpo: StaticBody2D = $StaticBody2D
@onready var anim: AnimationPlayer = $AnimationPlayer
var direcao: int = 1
func reverter() -> void:
if not reversivel:
return
direcao *= -1
# Atualiza fisica
corpo.constant_linear_velocity = Vector2(direcao * 200.0, 0.0)
# Atualiza animacao
anim.speed_scale = float(direcao)
Conecte esse reverter() a uma alavanca, a um botao no chao ou a um Timer que alterna sozinho, e a esteira ganha vida no nivel.
Qual abordagem escolher
A escolha eh direta quando voce sabe o que vai andar em cima. Se forem objetos de fisica, como caixas e barris baseados em RigidBody2D, use constant_linear_velocity. Ela eh nativa, barata e fisicamente correta, sem codigo por frame.
Se o que anda em cima eh o jogador, que quase sempre eh um CharacterBody2D, use a abordagem da Area2D somando o empurrao na velocity. O CharacterBody2D nao reage a constant_linear_velocity, entao nao tem jeito: o empurrao precisa ser explicito no codigo.
Na pratica, muitos jogos usam as duas ao mesmo tempo na mesma esteira. O StaticBody2D com constant_linear_velocity resolve as caixas, e o codigo no CharacterBody2D resolve o jogador. Os dois convivem bem porque atuam em sistemas diferentes do motor.
O ponto de atencao da abordagem 2 eh a deteccao de contato: voce depende de is_on_floor() e das colisoes do frame, entao em rampas ou bordas a leitura pode falhar por um instante. Vale testar nos cantos da esteira. Ja a abordagem 1 quase nao tem armadilhas, mas voce abre mao de controle fino sobre como o empurrao interage com o input.
Se voce quiser ir alem, esteiras combinam bem com plataformas moveis, e entender melhor o motor por tras disso ajuda a evitar surpresas. Vale a leitura sobre fisica de jogos no Godot para firmar a base de como corpos e colisoes conversam entre si.
Com esses blocos, voce tem esteiras que empurram caixas e jogadores, animam a textura junto, e invertem com um toque. Sao pecas pequenas, mas que mudam completamente a sensacao de um nivel parado para um nivel vivo.


