Godotでアクションゲームを作っていると、「当たり判定は取れてるけど、ヒット感が薄い…」という悩みが出てきますよね。
一番手軽なやり方は、プレイヤーや敵のスクリプトに「ダメージ時は色を変える処理」を直書きすることですが、

  • プレイヤー、敵、壊れるオブジェクトなど、全部に同じような処理を書きがち
  • 「白フラッシュの時間」「強さ」を変えたい時に、複数スクリプトを修正する羽目になる
  • シェーダーを使った表現を試したいのに、各キャラごとにマテリアル設定をいじるのが面倒

といった「継承&コピペ地獄」に陥りやすいです。

そこで今回は、「ヒットしたら一瞬だけ真っ白になる」表現を、どのキャラにもポン付けできる
コンポーネント型として切り出した FlashWhite(ヒット閃光)コンポーネントを作ってみましょう。

攻撃ヒット時に flash() を呼ぶだけで、シェーダーでスプライトを白くフラッシュしてくれるようになります。
もちろん、プレイヤー・敵・動く床・ギミックなど、どんなスプライト系ノードにも再利用可能です。


【Godot 4】当たり判定に「バチッ」とした手応えを!「FlashWhite」コンポーネント

このコンポーネントは、

  • 対象スプライトに専用シェーダーマテリアルを自動でセット
  • 攻撃ヒット時などに flash() を呼ぶだけで、指定時間だけ真っ白フラッシュ
  • フラッシュ時間・強さ・対象ノードを @export で柔軟に設定

という構成になっています。
「ダメージ処理」は各キャラのスクリプトに任せて、「見た目の演出」はこのコンポーネントに丸投げ、という分離ができるのがポイントですね。


フルコード(GDScript + シェーダー)

以下を res://components/flash_white.gd のように保存して使ってください。

extends Node
class_name FlashWhite
## FlashWhite(ヒット閃光)コンポーネント
##
## 攻撃ヒット時などに flash() を呼ぶと、
## 対象スプライトが一瞬だけ真っ白にフラッシュします。
##
## 「見た目の演出」をコンポーネントに閉じ込めることで、
## プレイヤー・敵・ギミックなど、どこからでも簡単に再利用できます。

@export_group("ターゲット設定")
## フラッシュ対象のノード。
## 通常は Sprite2D / AnimatedSprite2D / MeshInstance2D などを指定します。
## 未指定の場合、親ノードから自動で Sprite2D を探します。
@export var target_node: NodePath

@export_group("フラッシュ設定")
## フラッシュしている時間(秒)
@export_range(0.01, 1.0, 0.01)
var flash_duration: float = 0.1

## フラッシュの強さ(0.0〜1.0)。
## 1.0 で完全に真っ白、0.5 なら少しだけ白くなります。
@export_range(0.0, 1.0, 0.05)
var flash_intensity: float = 1.0

## すでにフラッシュ中に flash() を呼んだ時の挙動。
## true ならフラッシュ時間を延長、false なら無視。
@export var extend_if_flashing: bool = true

## デバッグ用に、シーン再生直後に一度だけフラッシュさせるか
@export var test_flash_on_ready: bool = false

# 内部用: 実際にマテリアルを持つ CanvasItem (Sprite2D 等)
var _canvas_item: CanvasItem
# 内部用: 生成した ShaderMaterial
var _shader_material: ShaderMaterial
# 内部用: フラッシュ残り時間
var _flash_timer: float = 0.0

# シェーダーコード(2D用)。Sprite2D, AnimatedSprite2D などで使用可能。
const FLASH_SHADER_CODE := """
shader_type canvas_item;

uniform float flash_strength : hint_range(0.0, 1.0) = 0.0;

void fragment() {
    vec4 tex_color = texture(TEXTURE, UV);
    // 通常の色と「真っ白」の線形補間
    vec3 flashed = mix(tex_color.rgb, vec3(1.0, 1.0, 1.0), flash_strength);
    COLOR = vec4(flashed, tex_color.a);
}
"""

func _ready() -> void:
    _resolve_target()
    _ensure_shader_material()

    if test_flash_on_ready:
        flash()

func _process(delta: float) -> void:
    if _flash_timer > 0.0:
        _flash_timer -= delta
        if _flash_timer <= 0.0:
            _set_flash_strength(0.0)

## パブリックAPI: 外部から呼ぶ用
## ダメージを受けた時などに呼び出してください。
func flash() -> void:
    if not _canvas_item:
        push_warning("FlashWhite: 有効な target_node が見つからないため、flash() を無視しました。")
        return

    if _flash_timer > 0.0 and not extend_if_flashing:
        # すでにフラッシュ中で、延長しない設定なら何もしない
        return

    _flash_timer = flash_duration
    _set_flash_strength(flash_intensity)

# --- 内部処理ここから ---

## ターゲットノード(CanvasItem)を解決する
func _resolve_target() -> void:
    var node: Node = null

    if target_node != NodePath():
        node = get_node_or_null(target_node)
        if node == null:
            push_warning("FlashWhite: target_node '%s' が見つかりません。親から Sprite2D を自動検索します。" % target_node)

    if node == null:
        # 親ノードから Sprite2D / AnimatedSprite2D などを探す
        var parent := get_parent()
        if parent is CanvasItem:
            node = parent
        else:
            # 親の子孫から探す(最初に見つかった CanvasItem を採用)
            for child in parent.get_children():
                if child is CanvasItem:
                    node = child
                    break

    if node and node is CanvasItem:
        _canvas_item = node
    else:
        push_warning("FlashWhite: CanvasItem ターゲットが見つかりませんでした。flash() は無視されます。")

## ターゲットにシェーダーマテリアルをセットする
func _ensure_shader_material() -> void:
    if not _canvas_item:
        return

    # すでに ShaderMaterial が設定されている場合は、そのまま拡張して使う
    var current_material := _canvas_item.material

    if current_material is ShaderMaterial:
        _shader_material = current_material
        # 既存のシェーダーを上書きしたくない場合は、
        # ここで「ラップ用シェーダー」を作るなどの工夫も可能です。
        # 今回はシンプルに「ヒットフラッシュ専用」として扱います。
        _shader_material.shader = Shader.new()
        _shader_material.shader.code = FLASH_SHADER_CODE
    else:
        # 新しく ShaderMaterial を作成
        _shader_material = ShaderMaterial.new()
        _shader_material.shader = Shader.new()
        _shader_material.shader.code = FLASH_SHADER_CODE
        _canvas_item.material = _shader_material

    # 初期状態ではフラッシュなし
    _set_flash_strength(0.0)

## シェーダーの uniform を更新する
func _set_flash_strength(value: float) -> void:
    if _shader_material:
        _shader_material.set_shader_parameter("flash_strength", clamp(value, 0.0, 1.0))

使い方の手順

ここでは代表的な3パターンを例にします。

  • ① プレイヤーキャラにヒット閃光をつける
  • ② 敵キャラにヒット閃光をつける
  • ③ 壊れる箱(ギミック)にヒット閃光をつける

手順①:コンポーネントをシーンに追加する

プレイヤーシーンの例:

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── FlashWhite (Node)   ← このスクリプトをアタッチ
  1. 上記のように、Sprite2D と同じ階層Node を1つ追加します。
  2. その Node に、先ほどの FlashWhite.gd をアタッチします。
  3. target_node を空のままにしておくと、親ノード(Player)から最初に見つかった CanvasItem を自動で使います。
    明示的に指定したい場合は、target_node = "../Sprite2D" のようにパスを設定しましょう。

敵シーンの例:

Enemy (CharacterBody2D)
 ├── AnimatedSprite2D
 ├── CollisionShape2D
 └── FlashWhite (Node)

壊れる箱シーンの例:

BreakableCrate (StaticBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── FlashWhite (Node)

どのシーンでも「Sprite系ノードの近くにコンポーネントをぶら下げる」だけでOKです。
この「ノード階層を深くせず、横にコンポーネントを並べる」スタイルが、合成(Composition)志向のポイントですね。

手順②:ダメージ処理から flash() を呼ぶ

プレイヤーのスクリプト側では、ダメージを受けたタイミングで flash() を呼びます。

# Player.gd (例)
extends CharacterBody2D

@onready var flash_white: FlashWhite = $FlashWhite

var hp: int = 10

func apply_damage(amount: int) -> void:
    hp -= amount
    if flash_white:
        flash_white.flash()
    if hp <= 0:
        die()

func die() -> void:
    queue_free()

敵キャラでも同じように:

# Enemy.gd (例)
extends CharacterBody2D

@onready var flash_white: FlashWhite = $FlashWhite

func hit_by_player(damage: int) -> void:
    # HP処理など
    if flash_white:
        flash_white.flash()

壊れる箱でも同様です:

# BreakableCrate.gd (例)
extends StaticBody2D

@onready var flash_white: FlashWhite = $FlashWhite

func apply_hit() -> void:
    if flash_white:
        flash_white.flash()
    # ちょっとだけ待ってから壊す、などの演出もOK
    queue_free()

手順③:パラメータを調整する

FlashWhite ノードを選択すると、インスペクタに以下のパラメータが出てきます。

  • flash_duration … 白く光っている時間(秒)
    → 素早いアクションなら 0.05〜0.1 秒くらいがオススメです。
  • flash_intensity … 白さの強さ(0〜1)
    → 1.0 だと完全に真っ白、0.6 くらいにすると「ちょっとだけ白くなる」柔らかい表現になります。
  • extend_if_flashing … 連続ヒット時にフラッシュ時間を延長するか
    → コンボゲームなど、ヒットが連続する場合は true にすると「光りっぱなし」になりやすいです。
    → false にすると「1発ごとにチカッと光る」印象になります。
  • test_flash_on_ready … 再生開始時に一度だけテストフラッシュ
    → シェーダーがちゃんと効いているか確認したい時に使いましょう。

手順④:複数のキャラにコピペで適用

一度 FlashWhite コンポーネントを作ってしまえば、

  • 敵Aシーンに FlashWhite を追加して、flash_duration = 0.08
  • 敵Bシーンにも追加して、flash_intensity = 0.5(控えめな演出)
  • ボスシーンには、flash_duration = 0.15(長めに光らせる)

というように、見た目だけをパラメータで調整できます。
ダメージのロジックは各スクリプトに任せつつ、視覚効果はコンポーネントに集約できるのが嬉しいところですね。


メリットと応用

この FlashWhite コンポーネントを導入すると、次のようなメリットがあります。

  • シーン構造がスッキリ
    各キャラのスクリプトに「色変更ロジック」を書かなくてよくなり、
    ノード構造も「キャラ本体 + 見た目 + コンポーネント」というシンプルな形に保てます。
  • 使い回しが超ラク
    プレイヤー、敵、ギミックなど、「当たり判定があるもの全部」に同じ演出を一瞬で適用できます。
    継承ツリーをいじらなくても、Node を1個足してスクリプトをアタッチするだけです。
  • 演出の差し替えが容易
    「白フラッシュじゃなくて、赤フラッシュにしたい」「アウトラインを光らせたい」などの要望が出た時も、
    コンポーネント側のシェーダーを差し替えるだけで、全キャラに一括適用できます。
  • レベルデザインがやりやすい
    レベルデザイナーが「この敵だけ派手に光らせたい」と思ったら、
    エディタから flash_intensity をいじるだけで完結します。スクリプト改修は不要です。

コンポーネント指向にしておくことで、「演出を変えたい」「別のゲームでも使いたい」といった時に、
FlashWhite.gd だけをコピペして持っていけば済むのがとても大きいです。

改造案:ダメージ量に応じてフラッシュ強度を変える

ちょっとした応用として、「ダメージが大きいほど強く光る」APIを追加してみましょう。

## 追加API: ダメージ量に応じてフラッシュ強度を変える
func flash_with_damage(damage: float, max_damage: float = 100.0) -> void:
    if max_damage <= 0.0:
        flash()
        return

    # ダメージ量を 0.0〜1.0 に正規化して、intensity に反映
    var ratio := clamp(damage / max_damage, 0.0, 1.0)
    var original_intensity := flash_intensity
    flash_intensity = lerp(0.3, 1.0, ratio)
    flash()
    # 呼び出し後は元の設定に戻す
    flash_intensity = original_intensity

敵側では、

func hit_by_player(damage: int) -> void:
    if flash_white:
        flash_white.flash_with_damage(damage, 50.0)

のように使えば、大ダメージの時だけ強く光る表現が簡単に作れます。

このように、「見た目の演出」をどんどんコンポーネント化していくと、
継承に縛られない、柔軟な Godot プロジェクトが組めるようになります。
ぜひ自分のプロジェクト用に、FlashWhite をベースにしたエフェクトコンポーネントを増やしていきましょう。