Voltar para o Blog
Quest Log

Sistema de Combos no Godot 4: Encadeando Ataques

Personagem 2D em pose de ataque com rastro de golpe sobre fundo de arena de luta estilizada

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.

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

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.