敵を攻撃したとき、「スカッ」とした手応えだと寂しいですよね。

名作アクションゲームを観察すると、攻撃が当たった瞬間にキャラが一瞬**「真っ白に発光」**していることに気づくはずです。

しかし、Godotの標準機能である modulate(色調補正)では、画像を赤や青にすることはできても、「真っ白(シルエット)」にすることはできません。

そこで今回は、**「アタッチするだけで、専用シェーダーを自動生成して光らせる」**という、少しプロっぽいコンポーネントを作ります。

どんなことができるの?

この「HitFlashComponent」を敵やプレイヤーに追加するだけです。

  • 真っ白に光る: シェーダーを使って、画像のピクセルを一時的に純白に置き換えます。
  • 準備いらず: 面倒なシェーダーファイル(.gdshader)を作る必要はありません。スクリプトが自動で適用します。
  • ワンラインで実行: ダメージ処理のところに flash() と書くだけで動きます。

ステップ1:スクリプトの作成

hit_flash_component.gd という名前でスクリプトを作成し、以下のコードをコピペしてください。

このスクリプトは、実行時(ゲーム開始時)に親ノードに「白く光る能力(マテリアル)」を自動でプレゼントします。

class_name HitFlashComponent
extends Node

## 親ノード(Spriteなど)を一時的に単色(白)で光らせるコンポーネント
## シェーダーを動的に生成・適用するため、事前のマテリアル設定は不要です。

# 光らせる色(デフォルトは白)
@export var flash_color: Color = Color.WHITE

# 光っている時間(秒)
@export var flash_duration: float = 0.2

# 内部で使用するシェーダーコード(2D用)
# これをスクリプト内で定義することで、ファイル管理を楽にしています
const SHADER_CODE_2D = """
shader_type canvas_item;

uniform vec4 flash_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float flash_modifier : hint_range(0.0, 1.0) = 0.0;

void fragment() {
	vec4 color = texture(TEXTURE, UV);
	// 元の色とフラッシュ色を混ぜる
	color.rgb = mix(color.rgb, flash_color.rgb, flash_modifier);
	COLOR = color;
}
"""

var _parent_sprite: CanvasItem
var _material: ShaderMaterial
var _tween: Tween

func _ready() -> void:
	# 親がSpriteなどのCanvasItemかチェック
	var parent = get_parent()
	if parent is CanvasItem:
		_parent_sprite = parent
		_setup_material()

func _setup_material() -> void:
	# 新しいシェーダーマテリアルを作成
	_material = ShaderMaterial.new()
	var shader = Shader.new()
	shader.code = SHADER_CODE_2D
	_material.shader = shader
	
	# 親に適用(元のマテリアルがある場合は上書きになるため注意)
	# ※既存のマテリアルと共存させたい場合は、構成を工夫する必要があります
	_parent_sprite.material = _material

## 外部からこの関数を呼ぶと光ります
func flash() -> void:
	if not _material:
		return
		
	# 前の点滅が残っていたらキャンセル
	if _tween:
		_tween.kill()
	
	# Tweenを作成してアニメーション
	_tween = create_tween()
	
	# 1. 色をセット
	_material.set_shader_parameter("flash_color", flash_color)
	
	# 2. フラッシュ強度を 1.0 (真っ白) にする
	_material.set_shader_parameter("flash_modifier", 1.0)
	
	# 3. 指定時間かけて 0.0 (元の色) に戻す
	_tween.tween_method(
		func(value): _material.set_shader_parameter("flash_modifier", value),
		1.0, # 開始値
		0.0, # 終了値
		flash_duration
	).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CUBIC)

ステップ2:実際に使ってみよう

1. ノードを追加する

光らせたい敵キャラ(例えば Enemy シーン)の Sprite2D の下に、このコンポーネントを追加します。

もちろん、親ノード(CharacterBody2Dなど)の下に置いてもOKですが、「画像を持っているノード(Spriteなど)」の下に追加する方が、このスクリプトの仕様上(get_parent()を使っているため)確実です。

  • Enemy (CharacterBody2D)
    • Sprite2D ← ここに HitFlashComponent を追加
    • CollisionShape2D

2. ダメージ処理で呼び出す

敵キャラがダメージを受けるスクリプトの中に、1行追加します。

# Enemy.gd (敵のスクリプト例)

@onready var hit_flash = $Sprite2D/HitFlashComponent

func take_damage(amount: int):
    hp -= amount
    
    # ここでフラッシュを実行!
    hit_flash.flash()
    
    if hp <= 0:
        die()

これだけで、ダメージを受けるたびに敵が「ピカッ」と白く光り、徐々に元の色に戻るようになります。


応用:もっと気持ちよくする「ヒットストップ」

光るだけでも十分ですが、さらに**「ヒットストップ(一瞬時間が止まる演出)」**を組み合わせると、プロの味になります。これもコンポーネント内で処理できます。

HitFlashComponent に以下の機能を追加してみてください。

# ヒットストップの時間(秒)
@export var hit_stop_duration: float = 0.05

func flash() -> void:
    # ...(さっきのTween処理)...
    
    # ヒットストップ実行
    if hit_stop_duration > 0:
        Engine.time_scale = 0.05 # 全体の時間を遅くする
        # 指定時間待ってから戻す
        await get_tree().create_timer(hit_stop_duration, true, false, true).timeout
        Engine.time_scale = 1.0

Engine.time_scale をいじるとゲーム全体の時間が止まるため、プレイヤーの動きも止まって「重い一撃」感が出ます。

まとめ:演出の「外注化」

今回のコンポーネントのポイントは、**「敵のスクリプトは『光れ』と命令するだけで、どうやって光るかは知らなくていい」**という点です。

もし将来、「光るんじゃなくて、赤く点滅させたいな」と思ったとしても、敵のコードは修正せず、このコンポーネントの設定を変えるだけで済みます。これがコンポーネント指向の強みです!


(ブログ執筆者へのメモ)

  • 3Dの場合: 上記コードは CanvasItem(2D)用です。3D(MeshInstance3D)で行う場合は、shader_type spatial; に書き換え、ALBEDO = mix(ALBEDO, flash_color.rgb, flash_modifier); とすることで同様に実現できます。記事のボリューム次第で「3D版はコードのここを変えるだけ!」と補足しても良いでしょう。
  • 既存マテリアル問題: このスクリプトは material を上書きします。もし敵キャラがすでに特殊なシェーダーを使っている場合は、そのシェーダーの中に flash_modifier のロジックを組み込む必要があります。この記事では「一番簡単な導入法」として上書き方式を紹介しています。