Sistema de Combos no Godot 4: Encadeando Ataques

Sistema de combos no Godot 4 com GDScript tipado: encadeie 3 golpes, use janela de tempo com Timer, leia input e resete o combo na hora certa.
Sistema de Combos no Godot 4: Encadeando Ataques
Um sistema de combos é o que separa um jogo de luta ou ação que parece travado de um que parece vivo. Em vez de cada aperto de botão disparar sempre o mesmo golpe isolado, o jogador aprende que apertar no ritmo certo encadeia uma sequência: soco, soco, chute. Neste guia você vai montar um sistema de combos no Godot 4 com GDScript tipado, medindo uma janela de tempo com um Timer, lendo o input, avançando o contador de golpe e resetando quando o jogador deixa o tempo passar.
A ideia é simples de descrever e fácil de errar na prática. Você tem uma contagem de qual golpe está saindo agora. Quando o jogador aperta o botão de ataque de novo dentro de um intervalo curto, você avança para o próximo golpe da sequência. Se ninguém aperta nada nesse intervalo, o combo esfria e o contador volta ao começo. O resto do trabalho é casar isso com as animações para que cada golpe tenha peso.
Como um sistema de combos funciona por dentro
Antes do código, vale fixar o modelo mental. Pense em três peças que conversam entre si.
A primeira é o contador de golpe. É um número inteiro que diz em que ponto da sequência você está. Zero quer dizer parado. Um é o primeiro golpe, dois o segundo, três o terceiro. Quando ele chega ao fim da sequência, ele volta a zero.
A segunda peça é a janela de combo. Depois que um golpe sai, existe um período curto em que o jogo aceita o próximo botão para continuar. Essa janela é o coração da sensação de ritmo. Se ela for longa demais, o combo sai sozinho e perde a intenção. Se for curta demais, o jogador sente que o jogo não obedece. Um Timer resolve isso de forma limpa porque ele conta o tempo e avisa quando acaba.
A terceira peça é a animação. Cada golpe tem uma animação própria, e é ela que dá o timing real do combate. O contador escolhe qual golpe, mas a animação define quanto tempo o personagem fica comprometido naquele movimento e em que momento a hitbox fica ativa.
Com essas três peças no lugar, o loop fica assim: o jogador aperta ataque, você toca a animação do golpe atual, avança o contador e liga o Timer da janela. Se o jogador apertar de novo antes do Timer acabar, você emenda no próximo golpe. Se o Timer acabar primeiro, você zera o contador.
Montando a cena e o Input Map
Para acompanhar, use um CharacterBody2D com um AnimationPlayer (ou AnimatedSprite2D, se preferir animar por quadros) e um Timer filho chamado ComboTimer. Deixe o ComboTimer com one_shot ligado, porque ele deve contar uma vez e parar, não repetir.
No Input Map, crie uma ação chamada ataque e mapeie para a tecla e para o botão de controle que quiser. Ler a ação em vez da tecla direta é o que faz o combo funcionar igual no teclado e no controle sem mexer no código.
O contador de golpe e a janela de tempo
Vamos ao GDScript tipado. O script guarda o índice do combo, o tamanho da sequência e conversa com o Timer. Note que combo_index começa em zero e que a janela de combo é medida em segundos.
extends CharacterBody2D
@export var combo_janela: float = 0.45
@export var combo_maximo: int = 3
var combo_index: int = 0
var atacando: bool = false
@onready var anim: AnimationPlayer = $AnimationPlayer
@onready var combo_timer: Timer = $ComboTimer
func _ready() -> void:
combo_timer.one_shot = true
combo_timer.wait_time = combo_janela
combo_timer.timeout.connect(_on_combo_timer_timeout)
func _on_combo_timer_timeout() -> void:
resetar_combo()
func resetar_combo() -> void:
combo_index = 0
atacando = false
Aqui o combo_timer.timeout é um sinal do próprio nó Timer. Quando o tempo da janela acaba, o Godot chama _on_combo_timer_timeout, que zera tudo. Esse é o "resetar quando o tempo passa" pedido pelo modelo. Você não precisa somar delta na mão, o Timer faz essa conta.
Lendo o input e avançando o combo
Agora a leitura de input. Use _unhandled_input para reagir ao aperto do botão de ataque. Cada aperto válido chama uma função que decide se começa, continua ou ignora o comando.
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ataque"):
tentar_ataque()
func tentar_ataque() -> void:
if combo_index >= combo_maximo:
return
combo_index += 1
atacando = true
executar_golpe(combo_index)
combo_timer.start()
func executar_golpe(indice: int) -> void:
var nome_anim: String = "ataque_%d" % indice
anim.play(nome_anim)
Leia o fluxo com calma. Quando o jogador aperta ataque, tentar_ataque primeiro checa se o combo já chegou ao máximo. Se chegou, ignora o input, porque a sequência acabou. Se não, o contador sobe em um, o golpe correspondente é executado e o Timer da janela reinicia com start.
O nome da animação é montado a partir do índice, então ataque_1, ataque_2 e ataque_3 precisam existir no seu AnimationPlayer. Reiniciar o Timer a cada golpe é o detalhe que mantém a janela aberta enquanto o jogador acerta o ritmo. Cada golpe novo renova o prazo para o próximo.
Casando o combo com a animação
Até aqui o combo dispara animações em cima da anterior. Em um combate de verdade, você quer que cada golpe termine antes de o próximo começar, a não ser que exista uma janela de cancelamento. A forma mais direta no Godot 4 é usar o sinal animation_finished para liberar o estado de ataque quando o golpe atual chega ao fim naturalmente.
func _ready() -> void:
combo_timer.one_shot = true
combo_timer.wait_time = combo_janela
combo_timer.timeout.connect(_on_combo_timer_timeout)
anim.animation_finished.connect(_on_animation_finished)
func _on_animation_finished(nome_anim: String) -> void:
if nome_anim.begins_with("ataque_"):
atacando = false
Com isso, atacando volta a ser falso assim que a animação do golpe acaba. Você pode usar essa flag para bloquear movimento durante o ataque ou para impedir que o jogador ande no meio do golpe. O ponto importante é que a animação, e não um número fixo no código, decide o tempo de cada golpe.
Se você ainda vai montar a base do seu projeto de luta, vale ver antes como fazer um jogo de luta para entender onde o sistema de combos se encaixa no loop geral de combate.
Input buffer: aceitando o aperto um pouco antes
Aqui entra o conceito que dá polimento ao combate. O jogador raramente aperta no quadro exato em que o próximo golpe pode sair. Ele tende a apertar um pouco antes, ainda no fim da animação anterior. Sem tratamento, esse aperto se perde e o combo trava, o que passa a sensação de que o jogo ignora o comando.
Input buffer é guardar esse aperto por alguns instantes e usá-lo assim que o golpe atual libera o próximo. Em vez de descartar o input quando atacando é verdadeiro, você o memoriza e o executa na primeira oportunidade.
var buffer_ataque: bool = false
func tentar_ataque() -> void:
if atacando:
buffer_ataque = true
return
if combo_index >= combo_maximo:
return
combo_index += 1
atacando = true
executar_golpe(combo_index)
combo_timer.start()
func _on_animation_finished(nome_anim: String) -> void:
if not nome_anim.begins_with("ataque_"):
return
atacando = false
if buffer_ataque:
buffer_ataque = false
tentar_ataque()
Agora, quando o jogador aperta durante um golpe que ainda está rodando, o comando fica em buffer_ataque. Assim que a animação termina, o jogo verifica o buffer e, se houver um aperto guardado, chama tentar_ataque de novo. O combo emenda sozinho no ritmo do jogador, e o comando cedo demais deixa de ser um comando perdido.
Um cuidado: o buffer deve durar pouco. Guardar o input por tempo demais faz o combo sair muito depois do aperto, o que também parece errado. Como aqui o buffer só sobrevive de um golpe ao próximo, ele já fica curto por natureza.
Cancelamento: cortando um golpe para emendar outro
Cancelamento é o irmão avançado do buffer. Em jogos de luta, cancelar é interromper a animação de um golpe em um ponto permitido para emendar direto no próximo, sem esperar o movimento terminar por completo. É o que dá fluidez às sequências rápidas.
De forma acessível: você marca um trecho no meio ou no fim da animação como janela de cancelamento. Se o próximo input chega dentro dessa janela, você troca de animação na hora em vez de esperar animation_finished. Se chega antes, vira buffer. Se chega depois, o combo já resetou.
Uma maneira limpa de marcar essa janela no Godot 4 é usar um Call Method Track na AnimationPlayer, chamando um método que abre a janela de cancelamento em um quadro específico do golpe.
var pode_cancelar: bool = false
func abrir_cancelamento() -> void:
pode_cancelar = true
func fechar_cancelamento() -> void:
pode_cancelar = false
func tentar_ataque() -> void:
if atacando and not pode_cancelar:
buffer_ataque = true
return
if combo_index >= combo_maximo:
return
pode_cancelar = false
combo_index += 1
atacando = true
executar_golpe(combo_index)
combo_timer.start()
Chame abrir_cancelamento e fechar_cancelamento a partir da timeline da animação, nos quadros que você quiser. Enquanto pode_cancelar é verdadeiro, um novo aperto corta o golpe atual e emenda no próximo imediatamente. Fora dessa janela, o aperto vira buffer e espera a vez. Essa é a diferença entre um combate que responde na hora e um que sempre parece meio segundo atrasado.
Ligando o dano de cada golpe
O sistema de combos decide qual golpe sai, mas ele não aplica dano sozinho. O dano de cada golpe fica separado, ativado durante alguns quadros da animação. A forma padrão no Godot 4 é ligar e desligar uma hitbox nos quadros certos do golpe. Se você ainda não montou essa parte, vale entender hitbox e hurtbox com Area2D para saber onde a colisão do combo entrega o dano.
Para o retorno visual do acerto, um efeito rápido ajuda a leitura do combate. Você pode usar o shader de hit flash para o dano e fazer o alvo piscar de branco no quadro em que a hitbox conecta, reforçando que aquele golpe da sequência acertou.
Próximo passo
Você tem agora as peças essenciais de um sistema de combos no Godot 4: um contador de golpe tipado, um Timer que mede a janela e reseta a sequência, leitura de input por ação, buffer para aceitar o aperto adiantado e uma janela de cancelamento para emendar golpes com fluidez. O próximo passo é abrir o seu projeto e criar as três animações ataque_1, ataque_2 e ataque_3, ligar os sinais de timeout e animation_finished e ajustar o valor de combo_janela até o ritmo ficar bom no controle.
Depois que a sequência de três golpes estiver firme, experimente ramificar o combo. Em vez de sempre seguir uma linha reta, faça o terceiro golpe mudar dependendo de uma direção pressionada, e você já terá a base de uma árvore de combos como nos jogos de luta que você admira.
Perguntas frequentes
O que é uma janela de combo?
É o intervalo curto de tempo em que o jogo aceita o próximo botão de ataque para continuar a sequência. Se você apertar dentro dessa janela, o combo avança. Se o tempo passa, o combo reseta e o próximo golpe volta a ser o primeiro.
Preciso de um Timer para fazer combos no Godot 4?
Não é obrigatório, mas o nó Timer deixa o código mais limpo. Ele mede a janela de combo e dispara um sinal quando o tempo acaba, o que é o momento exato para resetar o contador de golpe sem precisar somar delta manualmente no _process.
O que é input buffer em um sistema de combos?
É guardar o botão que o jogador apertou um pouco antes da hora certa e usar esse input assim que a animação libera o próximo golpe. Isso faz o combate parecer responsivo mesmo quando o jogador aperta cedo demais.
Como cancelar um ataque para outro no Godot 4?
Cancelamento é interromper a animação atual em um ponto permitido e emendar direto no próximo golpe. Na prática você marca uma janela dentro da animação onde o novo input pode cortar o movimento e trocar para a próxima animação de ataque.
Onde ligo o dano de cada golpe do combo?
O dano fica separado do contador de combo. Cada golpe ativa a hitbox durante alguns quadros da animação. O combo só decide qual golpe toca; a hitbox com Area2D é quem aplica o dano no alvo atingido.
O combo funciona igual com teclado e controle?
Sim. Como a leitura usa uma ação do Input Map (por exemplo "ataque"), você mapeia essa ação para uma tecla e para um botão do controle. O código do combo não muda; ele só reage à ação, não ao dispositivo.


