Voltar para o Blog
Quest Log

Godot await e corrotinas no GDScript: como esperar no código sem travar o jogo

Editor da Godot 4 mostrando código GDScript com a palavra await em destaque

Aprenda a usar godot await e corrotinas no GDScript tipado para esperar tempo, sinais e animações sem congelar a game loop do seu jogo.

Cedo ou tarde todo mundo que programa na Godot precisa fazer o jogo "esperar" alguma coisa: esperar dois segundos antes de abrir uma porta, esperar uma animação terminar antes de dar o próximo passo de uma cutscene, ou dar um intervalo entre o spawn de cada inimigo. O jeito certo de fazer isso na Godot 4 é com godot await e corrotinas no GDScript, e neste tutorial você vai entender por que essa é a ferramenta correta e como usá-la sem travar o jogo.

Vou usar GDScript tipado em todos os exemplos, porque tipagem deixa o código mais legível e o autocomplete mais esperto. Se você ainda está começando com a linguagem, vale ler antes o guia de GDScript do zero para acompanhar com tranquilidade.

Por que não dá para usar um sleep comum

A primeira ideia de quem vem de outras linguagens é procurar um sleep() ou fazer um loop que fica contando o tempo. O problema é que a Godot roda tudo dentro de uma game loop: a cada frame ela processa input, física, animação, som e desenho da tela. Se o seu código bloqueia essa loop, o jogo inteiro congela.

Veja o que NÃO fazer:

func abrir_porta_errado() -> void:
    var tempo_restante: float = 2.0
    while tempo_restante > 0.0:
        # ISTO TRAVA O JOGO INTEIRO
        tempo_restante -= 0.016
    print("porta aberta")

Esse loop while gira o processador sem devolver o controle para a engine. Enquanto ele roda, nada se atualiza: a tela para, o input para, tudo congela. Mesmo um OS.delay_msec() teria o mesmo efeito, porque bloqueia a thread principal. A game loop simplesmente não pode ser bloqueada.

A solução é await. Em vez de segurar a thread, você diz à engine: "pausa esta função aqui e me acorde quando o tempo passar ou o sinal disparar". Nesse meio tempo o jogo continua rodando normalmente.

Esperando um tempo com await e create_timer

O jeito mais comum de esperar X segundos é criar um timer temporário e aguardar o sinal timeout dele:

func abrir_porta() -> void:
    print("porta vai abrir em 2 segundos")
    await get_tree().create_timer(2.0).timeout
    print("porta aberta")

O que acontece na linha do await:

  1. get_tree().create_timer(2.0) cria um SceneTreeTimer que dispara o sinal timeout daqui a 2 segundos.
  2. O await pausa a função abrir_porta, devolvendo o controle para a engine.
  3. Quando o timer dispara, a função retoma exatamente de onde parou e imprime a segunda linha.

Durante esses 2 segundos, o jogo inteiro segue rodando: personagem se move, física acontece, animações tocam. Só aquela função ficou em espera. Essa é a mágica do await.

Um uso prático clássico é a contagem regressiva de um "3, 2, 1, já":

func contagem_regressiva() -> void:
    for i: int in range(3, 0, -1):
        print(str(i))
        await get_tree().create_timer(1.0).timeout
    print("JA!")

Aqui cada volta do laço imprime o número e espera 1 segundo antes da próxima, sem travar nada.

Esperando um sinal disparar

O await não serve só para tempo. Você pode aguardar qualquer sinal (signal). Isso é poderoso porque casa perfeitamente com o sistema de eventos da Godot. Se você ainda não domina esse assunto, o tutorial de sinais no GDScript explica a base que faz o await de signals brilhar.

Um exemplo muito comum é esperar uma animação terminar antes de continuar:

func atacar() -> void:
    var anim: AnimationPlayer = $AnimationPlayer
    anim.play("golpe")
    await anim.animation_finished
    print("golpe terminou, pode atacar de novo")

O código pausa até o AnimationPlayer emitir animation_finished. Não importa se a animação dura meio segundo ou dois: a função só continua quando ela realmente acaba.

Também dá para esperar a interação do jogador, como o clique num botão:

func esperar_confirmacao() -> void:
    var botao: Button = $UI/BotaoConfirmar
    print("clique para continuar")
    await botao.pressed
    print("jogador confirmou")

Repare que você aguarda diretamente botao.pressed, o mesmo sinal que usaria num connect. O await é uma forma de consumir aquele sinal uma única vez, no ponto exato do código onde você precisa dele.

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

Corrotinas: sua função vira assíncrona

Quando uma função usa await, ela deixa de rodar do começo ao fim de uma vez só. Ela vira uma corrotina: uma função que pode pausar e retomar. E aqui está o detalhe que muita gente não percebe: quem chama uma corrotina também pode dar await nela para esperar a corrotina terminar.

Veja uma função tipada que faz um passo de cutscene e é aguardada por outra:

func mostrar_fala(texto: String, duracao: float) -> void:
    $UI/Legenda.text = texto
    $UI/Legenda.show()
    await get_tree().create_timer(duracao).timeout
    $UI/Legenda.hide()

func rodar_cutscene() -> void:
    await mostrar_fala("Faz tempo que ninguem passa por aqui...", 2.5)
    await mostrar_fala("O que voce quer?", 2.0)
    await mostrar_fala("Entao siga o corredor.", 2.0)
    print("cutscene concluida")

A função rodar_cutscene chama mostrar_fala três vezes, e cada await garante que uma fala só começa depois que a anterior termina. O resultado é uma sequência limpa, lida de cima para baixo, sem callbacks aninhados nem máquinas de estado gigantes. Isso é o que torna o await tão bom para cutscenes e roteiros.

Note que uma corrotina normalmente retorna -> void. Você também pode retornar um valor e capturá-lo com var resultado = await minha_funcao(), mas para a maioria dos casos de espera o void basta.

Exemplo prático: delay entre spawns de inimigo

Outro uso muito comum é espalhar o spawn de vários inimigos no tempo, em vez de criar todos de uma vez. Com corrotina isso fica direto:

@export var cena_inimigo: PackedScene
@export var quantidade: int = 5
@export var intervalo: float = 0.8

func spawnar_onda() -> void:
    for i: int in range(quantidade):
        var inimigo: Node2D = cena_inimigo.instantiate() as Node2D
        add_child(inimigo)
        inimigo.global_position = Vector2(randf_range(0.0, 800.0), -50.0)
        await get_tree().create_timer(intervalo).timeout
    print("onda completa")

Cada inimigo aparece, a função espera o intervalo e só então cria o próximo. A cena continua respondendo normalmente entre um spawn e outro.

Cuidados importantes com await

O await é seguro, mas tem armadilhas que aparecem quando o jogo fica mais complexo. Vale conhecer três.

1. O nó pode ser liberado durante a espera. Se algo chamar queue_free() no nó enquanto um await dele está pendente, o código depois do await pode tentar acessar um nó que já não existe, gerando erro. Proteja-se checando a validade:

func piscar_e_sumir() -> void:
    modulate = Color(1, 0, 0)
    await get_tree().create_timer(0.5).timeout
    if not is_instance_valid(self):
        return
    modulate = Color(1, 1, 1)

O is_instance_valid(self) garante que você só continua se o nó ainda estiver vivo. Esse cuidado é essencial em inimigos que podem morrer no meio de uma animação de dano, por exemplo.

2. Não abuse de await dentro do _process. As funções _process e _physics_process rodam toda frame. Colocar um await dentro delas empilha esperas e cria estado confuso, com várias corrotinas pendentes ao mesmo tempo. Para lógica que se repete a cada frame, use a própria frame; para intervalos recorrentes, prefira um nó Timer.

3. Timer temporário não é a mesma coisa que o nó Timer. O await create_timer() cria um timer descartável, ótimo para uma espera pontual dentro de uma sequência. Mas quando você precisa de algo reutilizável, com estado próprio, que pode ser reiniciado ou pausado, o nó Timer é a escolha certa.

Quando usar o nó Timer em vez de await

A regra prática é simples. Use await create_timer() para esperas pontuais dentro de uma corrotina, como uma cutscene ou um delay único. Use o nó Timer para comportamentos recorrentes ou que precisam de controle, como o cooldown de uma habilidade ou o intervalo de disparo de uma arma. O guia sobre Timer e cooldown na Godot mostra esse padrão em detalhe e explica como configurar o nó no editor.

Uma boa forma de decidir: se a espera acontece uma vez, dentro de uma sequência lida de cima para baixo, await é mais limpo. Se a espera é um sistema com estado, que reinicia e é consultado de vários lugares, o nó Timer paga melhor.

Fechando

O await resolve de forma elegante o problema de esperar no código sem congelar o jogo. Ele pausa apenas a sua função, deixando a game loop livre para continuar. Você pode esperar tempo com create_timer().timeout, esperar qualquer sinal disparar, e encadear corrotinas tipadas para montar sequências complexas de forma legível.

Comece pelos casos simples, uma espera de tempo ou de animação, e vá evoluindo para corrotinas encadeadas. Lembre de checar is_instance_valid quando o nó pode morrer no meio da espera, e reserve o nó Timer para o que é recorrente. Com esses padrões no bolso, boa parte da lógica temporal do seu jogo fica muito mais fácil de escrever e manter aqui no CursoGame.Dev.

Perguntas frequentes

O await trava o jogo enquanto espera?

Não. O await só pausa a função que o chamou; o resto do jogo continua rodando normalmente, incluindo _process, física e input. É por isso que ele substitui bem um sleep bloqueante.

Posso usar await dentro do _process ou _physics_process?

Tecnicamente sim, mas evite. Essas funções são chamadas toda frame e um await pendente ali cria confusão de estado e várias esperas empilhadas. Prefira funções próprias ou o nó Timer para lógica recorrente.

Qual a diferença entre await de timer e o nó Timer?

O await get_tree().create_timer() é ideal para uma espera pontual dentro de uma sequência. O nó Timer é melhor para algo recorrente ou reutilizável, como cooldown de habilidade, porque tem estado próprio e pode ser reiniciado no editor.

O que acontece se o nó for liberado durante o await?

Se você chamar queue_free enquanto um await está pendente, o código depois do await pode tentar acessar um nó que já não existe. Cheque is_instance_valid(self) antes de continuar ou envolva a lógica com esse cuidado.