Voltar para o Blog
Quest Log

Esteiras transportadoras (conveyor) no Godot 4

Personagem 2D em cima de uma esteira transportadora sendo empurrado lateralmente em um cenario industrial.

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.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

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.