Voltar para o Blog
Quest Log

Como Fazer um Endless Runner no Godot 4 (Corredor Infinito)

Personagem 2D correndo e pulando obstaculos num cenario com parallax, estilo corredor infinito

Como fazer um endless runner no Godot 4 com GDScript tipado: pulo do player, parallax, spawn de obstaculos com Timer, dificuldade crescente e game over.

Como Fazer um Endless Runner no Godot 4 (Corredor Infinito)

Fazer um endless runner no Godot e um dos melhores caminhos pra terminar seu primeiro jogo de verdade. O genero corredor infinito (pense em Flappy Bird, Subway Surfers ou aquele dinossauro do Chrome) tem uma mecanica central so: o mundo passa sem parar e o jogador apenas reage, pulando ou desviando. Escopo pequeno, divertido na hora, e ainda por cima e um jogo perfeito pra celular, porque o controle inteiro cabe num toque na tela.

Neste tutorial voce monta um corredor infinito completo com GDScript tipado de Godot 4: o pulo do player, o cenario rolando com parallax, o spawn de obstaculos com Timer e instantiate, a dificuldade que sobe sozinha e o game over com pontuacao por distancia. Tudo com codigo que roda de verdade, nada de pseudocodigo.

A ideia central do endless runner no Godot

Antes de abrir o editor, entenda o truque que sustenta o genero: o player nao anda pra frente. Ele fica parado no eixo X, ali na esquerda da tela, e quem se move e o mundo, da direita pra esquerda. A ilusao de corrida vem do chao rolando e dos obstaculos vindo na sua direcao.

Isso simplifica tudo. Voce nao precisa mover a camera, nem controlar ate onde o cenario vai, nem reposicionar nada que ficou pra tras. A camera fica fixa, o player so sobe e desce (pula), e os obstaculos nascem fora da tela na direita, deslizam pra esquerda e se autodestroem quando saem do outro lado.

A cena principal fica mais ou menos assim:

Main (Node2D)
├── Player (CharacterBody2D)
├── Chao (StaticBody2D)
├── ParallaxFundo (Parallax2D)
│   └── Sprite2D
├── SpawnTimer (Timer)
├── SpawnPoint (Marker2D)
└── CanvasLayer
    └── LabelPontos (Label)

Com essa estrutura na cabeca, cada peca vira um pedaco pequeno de codigo. Vamos por partes.

O pulo do player com CharacterBody2D

O player e um CharacterBody2D com um CollisionShape2D e um Sprite2D. A logica e a de qualquer plataforma: gravidade puxando pra baixo o tempo todo, e uma forca pra cima quando o jogador manda pular.

O detalhe que faz a diferenca pro mobile esta no Input. No menu Project > Project Settings > Input Map, crie uma acao chamada pular e adicione dois eventos a ela: a tecla espaco (pra testar no PC) e um evento de toque na tela (Touch). Assim o mesmo Input.is_action_just_pressed("pular") responde ao teclado e ao dedo, sem voce escrever nada especifico pra celular.

extends CharacterBody2D

@export var gravidade: float = 1400.0
@export var forca_pulo: float = -600.0

func _physics_process(delta: float) -> void:
    # Gravidade puxa pra baixo sempre que nao estamos no chao.
    if not is_on_floor():
        velocity.y += gravidade * delta

    # Pula so quando esta no chao, evita pulo duplo no ar.
    if Input.is_action_just_pressed("pular") and is_on_floor():
        velocity.y = forca_pulo

    # O player NAO anda no X: quem se move e o mundo.
    velocity.x = 0.0
    move_and_slide()

Repare em velocity.x = 0.0: e o coracao do genero. O player fica parado na horizontal, e a sensacao de corrida vem do cenario. O forca_pulo e negativo porque no Godot 2D o eixo Y cresce pra baixo, entao subir e ir pro negativo.

Chao e cenario com rolagem e parallax

Pra dar sensacao de velocidade voce precisa de movimento no fundo. O jeito mais barato e o parallax: camadas se deslocando em velocidades diferentes, com a mais distante quase parada e a mais proxima passando rapido. No Godot 4.3 e acima, o node Parallax2D faz isso com autoscroll embutido, sem nenhuma linha de codigo:

extends Parallax2D

@export var velocidade_fundo: float = -120.0

func _ready() -> void:
    # Fundo deslizando pra esquerda sozinho, sem depender da camera.
    autoscroll = Vector2(velocidade_fundo, 0.0)
    # Repeat Size com a largura da textura faz o fundo ser infinito.
    repeat_size = Vector2(1920.0, 0.0)

Se voce nunca configurou parallax, vale parar e montar o cenario em camadas direito, porque e o que separa um corredor chapado de um que parece ter profundidade. Eu cubro o passo a passo completo, incluindo a pegadinha do Repeat Size, em parallax background no Godot 2D. O chao em si pode ser um StaticBody2D com um CollisionShape2D largo o suficiente pra cobrir a tela; como o player nao anda, o chao nem precisa rolar de verdade, basta a arte do fundo dar a impressao de movimento.

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

Spawn de obstaculos com Timer e instantiate

Aqui mora a alma do jogo. Os obstaculos nascem fora da tela na direita, em intervalos, e deslizam pra esquerda ate sumir. Primeiro, a cena do obstaculo: um Area2D com CollisionShape2D e Sprite2D. Uso Area2D em vez de corpo fisico porque eu so quero detectar o toque com o player, nao empurrar nada.

O script do obstaculo cuida de mover e de se destruir ao sair da tela:

extends Area2D

var velocidade: float = 300.0

func _process(delta: float) -> void:
    position.x -= velocidade * delta

    # Saiu pela esquerda? Libera da memoria.
    if position.x < -100.0:
        queue_free()

Agora o spawner. Coloque um Timer na cena principal e um Marker2D (o SpawnPoint) posicionado fora da tela, na direita, na altura do chao. A cada timeout do Timer voce instancia um obstaculo na posicao do marker e passa a velocidade atual do mundo pra ele:

extends Node2D

@export var cena_obstaculo: PackedScene
@export var velocidade_mundo: float = 300.0

@onready var spawn_timer: Timer = $SpawnTimer
@onready var spawn_point: Marker2D = $SpawnPoint

func _ready() -> void:
    spawn_timer.timeout.connect(_on_spawn_timer_timeout)
    spawn_timer.start()

func _on_spawn_timer_timeout() -> void:
    var obstaculo: Area2D = cena_obstaculo.instantiate()
    obstaculo.position = spawn_point.position
    # Variar a altura um pouco deixa o padrao menos previsivel.
    obstaculo.position.y += randf_range(-20.0, 20.0)
    obstaculo.velocidade = velocidade_mundo
    add_child(obstaculo)

O cena_obstaculo e um @export var ... : PackedScene: arraste o arquivo .tscn do obstaculo pra esse campo no Inspector. O randf_range quebra a regularidade, e o instantiate() cria uma copia nova a cada timeout. Como o obstaculo se destroi sozinho com queue_free, a memoria nunca enche, mesmo o jogo rodando por horas.

Dificuldade crescente: o mundo acelera

Um corredor infinito que comeca dificil e fica igual e chato; o que prende e a velocidade subindo aos poucos. A receita e simples: aumente a velocidade_mundo com o tempo, com um teto pra nao virar injogavel, e diminua o intervalo do Timer pra obstaculos virem mais juntos.

@export var velocidade_max: float = 700.0
@export var aceleracao: float = 8.0

func _process(delta: float) -> void:
    # Sobe a velocidade ate o teto.
    velocidade_mundo = min(velocidade_mundo + aceleracao * delta, velocidade_max)

    # Aperta o intervalo de spawn conforme acelera, com piso de seguranca.
    var intervalo: float = max(0.6, 1.5 - velocidade_mundo / 1000.0)
    spawn_timer.wait_time = intervalo

O min garante que a velocidade nunca passe de velocidade_max, e o max garante que o Timer nunca dispare rapido demais a ponto de spawnar dois obstaculos colados sem espaco pra reagir. Esses dois numeros, velocidade_max e o piso de 0.6, sao os que voce mais vai ajustar testando o jogo. E ai que esta o game feel.

Colisao e game over com pontuacao por distancia

Falta fechar o ciclo: bateu, acabou. Como o obstaculo e um Area2D, basta conectar o sinal body_entered dele ao player (que e um CharacterBody2D, ou seja, um corpo). Eu prefiro centralizar isso no player, detectando quando ele toca qualquer obstaculo. Coloque os obstaculos num grupo chamado obstaculos (no Inspector, aba Node > Groups) e use uma Area2D filha do player como detector:

extends CharacterBody2D

signal morreu

func _ready() -> void:
    $AreaDetectora.area_entered.connect(_on_area_detectora_area_entered)

func _on_area_detectora_area_entered(area: Area2D) -> void:
    if area.is_in_group("obstaculos"):
        emit_signal("morreu")

A pontuacao num endless runner e sempre por distancia ou tempo, que dao no mesmo, ja que a velocidade e constante por instante. O jeito mais direto e somar a distancia percorrida pelo mundo a cada frame. Na cena principal:

@onready var label_pontos: Label = $CanvasLayer/LabelPontos

var distancia: float = 0.0
var jogo_ativo: bool = true

func _process(delta: float) -> void:
    if not jogo_ativo:
        return

    # Distancia = velocidade do mundo somada a cada frame.
    distancia += velocidade_mundo * delta
    label_pontos.text = "Pontos: %d" % int(distancia / 10.0)

func _on_player_morreu() -> void:
    jogo_ativo = false
    spawn_timer.stop()
    # Aqui voce mostra a tela de game over, toca um som, etc.
    get_tree().reload_current_scene()

Conecte o sinal morreu do player a _on_player_morreu. O int(distancia / 10.0) so deixa o numero da pontuacao mais agradavel de ler. O reload_current_scene() reinicia o jogo de forma crua; numa versao mais caprichada voce trocaria isso por uma tela de game over com o recorde salvo e um botao de tentar de novo.

Por que esse e um otimo primeiro jogo pra celular

Junte as pecas e voce tem um jogo completo: player que pula com gravidade, fundo com parallax, obstaculos nascendo e morrendo sozinhos, dificuldade que sobe e game over com placar. E tudo isso roda liso no celular, porque o controle inteiro e um toque na tela, exatamente o que a acao pular no Input Map ja resolve.

E por isso que o corredor infinito e o primeiro jogo que eu recomendo pra quem quer publicar algo no celular. O escopo e honesto, a mecanica e uma so, e mesmo assim voce pratica fisica de personagem, Timer, instantiate, grupos, sinais e colisao. Se voce ainda esta decidindo o caminho pra desenvolver pensando no aparelho, vale ler como criar um jogo pelo celular antes de seguir.

Proximos passos

Com a base de pe, da pra evoluir o jogo em camadas, sempre testando cada adicao:

  • Troque o reload cru por uma tela de game over de verdade, com recorde salvo num arquivo.
  • Adicione moedas pra coletar (outro Area2D, com body_entered somando pontos em vez de matar).
  • Coloque animacao no player (AnimatedSprite2D) pro pulo e pra corrida.
  • Se quiser tres pistas estilo Subway Surfers, adicione movimento lateral por toque, e o caminho ali e dominar controles touch e joystick virtual no Godot.

E se voce sente que ja entendeu o suficiente pra querer construir uma base solida de Godot em vez de catar tutorial solto, o jeito mais rapido de sair do zero pra um jogo publicado e seguir uma trilha pensada pra isso. Veja o melhor curso de Godot pra encurtar o caminho.

O segredo do endless runner e que ele e pequeno o bastante pra voce terminar e divertido o bastante pra voce querer melhorar. Faca a versao crua hoje, jogue ela amanha, e ajuste os numeros ate ficar gostoso. Esse vai e volta e o trabalho de quem faz jogo de verdade.

Perguntas frequentes

O que e um endless runner?

E o genero de corredor infinito, tipo Flappy Bird ou Subway Surfers. O mundo se move sozinho pra sempre e o jogador so reage: pula, desvia ou troca de pista. Nao tem fim de fase, voce joga ate colidir, e a pontuacao vem da distancia percorrida. E um dos generos mais simples de programar, por isso e otimo como primeiro jogo.

Por que mover o mundo em vez do player no endless runner?

Porque deixa tudo mais simples. Se o player ficasse andando pra frente sem parar, voce teria que mover a camera junto, controlar limites de cenario e reposicionar obstaculos longe. Mantendo o player parado no eixo X e fazendo chao e obstaculos virem da direita pra esquerda, a camera fica fixa e a logica vira so spawn e movimento horizontal.

Como faco o pulo funcionar no toque da tela pra mobile?

No Input Map do Godot, crie a acao pular e adicione tanto a tecla espaco quanto um evento de toque na tela (Touch). No codigo voce le Input.is_action_just_pressed("pular") sem se importar se veio do teclado ou do dedo. Assim o mesmo jogo roda no PC e no celular sem mudar uma linha de logica.

Como deixo o endless runner mais dificil com o tempo?

Aumente gradualmente a velocidade do mundo a cada segundo, com um teto pra nao virar impossivel, e reduza o intervalo do Timer que faz o spawn de obstaculos. Conforme a velocidade sobe, os obstaculos passam mais rapido e mais perto um do outro, criando a curva de dificuldade que segura o jogador.

Endless runner e um bom primeiro jogo pra fazer no Godot?

E um dos melhores. O escopo e pequeno, a mecanica central e uma so (pular ou desviar), e mesmo assim voce pratica fisica de personagem, Timer, instantiate, colisao, pontuacao e dificuldade progressiva. Tudo isso cabe em poucas cenas e roda liso no celular, o que faz dele um projeto que voce realmente termina.