Voltar para o Blog
Quest Log

Shader de Flash de Dano (Hit Flash) no Godot 4

Sprite de um inimigo pixel art piscando em branco no momento exato em que recebe um golpe.

Aprenda a criar um efeito de hit flash godot com shader canvas_item: personagem pisca branco ao tomar dano. Código real de shader e GDScript com Tween.

Quando o jogador acerta um inimigo e nada muda na tela, o golpe parece não ter peso. O hit flash resolve isso de um jeito barato e direto: no instante do dano, o sprite pisca branco por uma fração de segundo. É um dos efeitos de game feel com melhor retorno por linha de código. Neste tutorial você vai montar um efeito de hit flash godot completo, com um shader canvas_item real e o GDScript que dispara o flash quando o personagem toma dano.

A ideia é simples: o shader mistura a cor original da textura com uma cor de flash usando um parâmetro que vai de 0 a 1. Quando esse parâmetro está em 0, o sprite aparece normal. Quando sobe para 1, ele fica totalmente branco. Um Tween anima esse valor para dar a piscada.

Shader de Flash de Dano (Hit Flash) no Godot 4

Por que usar shader em vez de trocar a modulação

Dá para fazer algo parecido só mexendo no modulate do sprite, mas isso multiplica a cor e não pinta o sprite de branco de verdade. Um sprite escuro com modulate branco continua escuro. Para forçar branco puro independente da cor original, você precisa de um mix no fragment shader. Além disso, o shader preserva o alpha da textura, então áreas transparentes continuam transparentes em vez de virarem um bloco branco quadrado.

Outra vantagem: o shader é um único material que você reaproveita em qualquer Sprite2D ou AnimatedSprite2D. Se você ainda não mexeu com shaders no Godot, vale uma passada rápida no básico em shaders no Godot para iniciantes antes de seguir.

O shader de hit flash

Crie um novo shader (New Shader) ou um arquivo .gdshader. O tipo é canvas_item, porque estamos trabalhando com sprites 2D. Cole o código abaixo:

shader_type canvas_item;

// Quanto do flash aplicar: 0.0 = sprite normal, 1.0 = totalmente colorido
uniform float flash_modifier : hint_range(0.0, 1.0) = 0.0;

// Cor do flash. Branco por padrao, mas pode ser vermelho, ciano, etc.
uniform vec4 flash_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);

void fragment() {
    // Le a cor original do pixel da textura
    COLOR = texture(TEXTURE, UV);

    // Mistura a cor original com a cor de flash conforme o modifier
    COLOR.rgb = mix(COLOR.rgb, flash_color.rgb, flash_modifier);

    // Preserva o alpha original. Multiplicar pelo alpha da textura
    // garante que pixels transparentes nao virem branco solido.
    COLOR.a = COLOR.a;
}

O ponto crucial está na última parte. Ao basear o resultado no alpha que veio de texture(TEXTURE, UV), as bordas transparentes do sprite continuam transparentes. Se você esquecesse disso e forçasse COLOR.a = 1.0, o flash apareceria como um retângulo branco em volta do personagem, o que fica horrível.

Se quiser ser ainda mais defensivo com a transparência, pode multiplicar o componente do flash pelo alpha. Uma variação segura do fragment:

void fragment() {
    vec4 tex = texture(TEXTURE, UV);
    vec3 flashed = mix(tex.rgb, flash_color.rgb, flash_modifier);
    // O flash so afeta a area visivel do sprite
    COLOR = vec4(flashed, tex.a);
}

As duas versões produzem o mesmo resultado visual. Use a que ficar mais legível para você.

Aplicando o material no sprite

Selecione o Sprite2D ou AnimatedSprite2D do seu personagem ou inimigo. No Inspector, vá em CanvasItem > Material, crie um New ShaderMaterial e arraste o shader que você acabou de criar para o campo Shader. Pronto, o material está conectado.

Você pode testar na hora: mexa no flash_modifier direto no Inspector e veja o sprite ficar branco. Deixe ele de volta em 0 antes de rodar o jogo, porque esse é o estado de repouso.

Um detalhe importante sobre AnimatedSprite2D: o material fica no nó inteiro, então funciona em todos os frames da animação sem precisar configurar quadro a quadro.

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

Disparando o flash com GDScript

Agora a parte que dá vida ao efeito. Queremos que, ao tomar dano, o flash_modifier salte para 1.0 e volte para 0.0 suavemente. Um Tween faz isso em poucas linhas e ainda gerencia o tempo sozinho.

Aqui está um script de exemplo para um inimigo ou personagem. A função flash() cria um Tween que sobe e desce o valor:

extends CharacterBody2D

@export var flash_duration: float = 0.15

@onready var sprite: Sprite2D = $Sprite2D

var health: int = 30

func flash() -> void:
    # Garante que o material existe e e do tipo certo
    var mat := sprite.material as ShaderMaterial
    if mat == null:
        return

    # Coloca branco instantaneo e depois volta a zero
    mat.set_shader_parameter("flash_modifier", 1.0)

    var tween := create_tween()
    tween.tween_property(
        mat,
        "shader_parameter/flash_modifier",
        0.0,
        flash_duration
    )

Repare na string "shader_parameter/flash_modifier". No Godot 4, é assim que o Tween acessa um uniform do shader como se fosse uma propriedade animável. Ele anima o valor de 1.0 (que setamos manualmente acima) de volta para 0.0 ao longo de flash_duration segundos.

Se você prefere uma piscada com subida e descida em vez de um corte seco para o branco, encadeie dois passos no Tween:

func flash() -> void:
    var mat := sprite.material as ShaderMaterial
    if mat == null:
        return

    var tween := create_tween()
    # Sobe rapido para branco
    tween.tween_property(
        mat,
        "shader_parameter/flash_modifier",
        1.0,
        0.05
    )
    # Desce de volta para o normal
    tween.tween_property(
        mat,
        "shader_parameter/flash_modifier",
        0.0,
        0.1
    )

Como o Tween roda os passos em sequência por padrão, o sprite primeiro pisca para branco em 0.05s e depois desbota em 0.1s. O efeito fica um pouco mais orgânico assim.

Integrando com a função de tomar dano

O flash sozinho não serve de nada. Ele precisa rodar dentro da lógica que processa o dano. Conecte a chamada flash() na sua função de receber dano:

func take_damage(amount: int) -> void:
    health -= amount
    flash()

    if health <= 0:
        die()

func die() -> void:
    queue_free()

Quando algo chamar take_damage(5) nesse nó, o personagem perde vida, pisca branco e, se a vida acabar, é removido. Se você já tem um sistema de vida montado, é só plugar o flash() ali dentro. Caso ainda não tenha, dá para construir um do zero seguindo o passo a passo em sistema de vida e dano no Godot.

Usando Timer em vez de Tween

Tween é a forma mais limpa, mas se você quiser controle por frame ou já usa um Timer no fluxo de dano, dá para resolver com um node de Timer. A lógica é: liga o flash no máximo, espera, e desliga.

@onready var flash_timer: Timer = $FlashTimer

func flash() -> void:
    var mat := sprite.material as ShaderMaterial
    if mat == null:
        return
    mat.set_shader_parameter("flash_modifier", 1.0)
    flash_timer.start(flash_duration)

func _on_flash_timer_timeout() -> void:
    var mat := sprite.material as ShaderMaterial
    if mat:
        mat.set_shader_parameter("flash_modifier", 0.0)

Aqui o FlashTimer precisa ser um nó Timer filho, com o sinal timeout conectado a _on_flash_timer_timeout. Funciona, mas o Tween costuma ser mais direto porque dispensa nó extra e dá a transição suave de graça.

Cuidado com materiais compartilhados

Existe uma armadilha clássica. Se vários inimigos usam o mesmo recurso ShaderMaterial salvo em disco, eles compartilham os uniforms. Quando um pisca, todos piscam juntos. Para inimigos individuais, você quer que cada um tenha sua própria instância do material.

A solução é duplicar o material em tempo de execução no _ready:

func _ready() -> void:
    if sprite.material:
        # Cada inimigo recebe uma copia independente do material
        sprite.material = sprite.material.duplicate()

Com o duplicate(), cada nó passa a ter seu próprio flash_modifier, e os flashes ficam independentes. Faça isso sempre que tiver várias instâncias do mesmo inimigo em cena.

Ajustando o feel do efeito

Alguns valores que vale experimentar para deixar o efeito do jeito do seu jogo:

A duração entre 0.1 e 0.2 segundos cobre a maioria dos casos. Mais curto que isso quase não dá para ver. Mais longo começa a parecer que o personagem está pegando fogo em vez de tomando um golpe.

A cor do flash não precisa ser branca. Vermelho passa sensação de dano e sangue. Ciano ou branco azulado dá um tom mais sci-fi. Como flash_color é um uniform, dá para trocar por inimigo sem tocar no shader.

O flash combina muito bem com outros efeitos de impacto: um leve knockback, um pequeno screen shake e alguns frames de invencibilidade. Esse conjunto transforma um golpe sem graça em algo que o jogador sente. Se quiser empilhar o knockback e a invencibilidade por cima, veja o passo a passo em knockback e invencibilidade no Godot.

Resumo do que você montou

Você tem agora um efeito de hit flash funcional e reutilizável: um shader canvas_item que mistura a textura com uma cor de flash via flash_modifier, preservando o alpha original; um material aplicado no Sprite2D ou AnimatedSprite2D; e o GDScript que dispara a piscada com Tween dentro da função de tomar dano. O efeito é leve, roda em qualquer hardware e dá um peso imediato aos combates.

A partir daqui, plugue a chamada flash() em todos os nós que levam dano no seu projeto e ajuste a cor e a duração até o golpe parecer certo. Game feel é assim: pequenos detalhes que, somados, fazem o jogo responder.