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

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:
get_tree().create_timer(2.0)cria umSceneTreeTimerque dispara o sinaltimeoutdaqui a 2 segundos.- O
awaitpausa só a funçãoabrir_porta, devolvendo o controle para a engine. - 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.
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.


