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.

Ferramenta:Godot

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.

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

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.