【Godot 4】DamageVignette (被弾枠) コンポーネントの作り方

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

Godotで「ダメージを受けたときに画面の端が赤く光る」演出を入れようとすると、ありがちな実装はこんな感じですよね。

  • プレイヤーシーンのスクリプトに直接「ダメージ演出」のコードを書き足す
  • UIシーン(CanvasLayer)のスクリプトに、プレイヤーからシグナルを飛ばして制御を書く
  • さらにダメージ演出のアニメーションを AnimationPlayer にゴリゴリ記述

これでも動きますが、だんだんこうなります。

  • プレイヤーのスクリプトが「入力・移動・攻撃・UI制御…」と肥大化してカオス
  • 別のシーン(敵用デバッグシーンなど)で同じダメージ演出を使いたいのに、コピペ地獄
  • 「赤いフラッシュの強さや時間」を変えたいだけなのに、複数のシーンを探し回る羽目に

そこで、「ダメージ演出」だけを 1 つのコンポーネントとして切り出して、どの HUD / UI シーンにもポン付けできるようにしておくと、とても楽になります。継承や深いノード階層に縛られず、「合成(Composition)」で UI を組み立てる感じですね。

今回は、ダメージを受けた瞬間に、画面の端を赤く点滅させる TextureRect 制御コンポーネントDamageVignette を作っていきましょう。

【Godot 4】被弾時の赤いフラッシュをコンポーネント化!「DamageVignette」コンポーネント

このコンポーネントは、ざっくり言うと:

  • 任意の TextureRect を指定して
  • trigger() を呼ぶだけで
  • フェードイン → フェードアウトの赤いダメージビネット演出を再生

という役割だけに徹した、シンプルな UI コンポーネントです。プレイヤー・敵・トラップなど、誰がダメージを受けても、UI 側では同じコンポーネントを使い回せます。


フルコード:DamageVignette.gd


extends Node
class_name DamageVignette
## ダメージを受けた瞬間に画面端を赤く点滅させるコンポーネント。
## 任意の TextureRect を制御し、フェードイン・アウトのアニメーションを行う。

@export var target_texture_rect: TextureRect:
	set(value):
		target_texture_rect = value
		# 自動初期化(エディタ上でプレビューしやすくするため)
		if is_instance_valid(target_texture_rect):
			_init_texture_rect()

## 最大まで点灯したときの不透明度(0.0〜1.0)
@export_range(0.0, 1.0, 0.01)
var max_alpha: float = 0.7

## 点灯するまでの時間(秒)
@export_range(0.01, 5.0, 0.01)
var fade_in_time: float = 0.1

## 消えるまでの時間(秒)
@export_range(0.01, 5.0, 0.01)
var fade_out_time: float = 0.35

## ダメージ演出のカーブ(0.0〜1.0 を時間に対する強さにマッピング)
## 例: 緩やかに立ち上がって、最後にスッと消える…など
@export var intensity_curve: Curve

## 連打をどう扱うか:
## true なら、再生中に trigger() されたらタイマーをリセットして再スタート
## false なら、再生中のときは新しい trigger() を無視
@export var restart_on_trigger: bool = true

## フラッシュ中かどうか
var _is_playing := false
## 0.0〜1.0 の正規化時間(0 が開始、1 が終了)
var _t := 0.0

## 内部用の Tween(Godot 4 の Tween はシーンツリーにぶら下がる必要がある)
var _tween: Tween

func _ready() -> void:
	# target_texture_rect が未指定なら、同じ親の中からそれっぽいものを自動検出
	if target_texture_rect == null:
		_autodetect_texture_rect()
	# カーブが未設定なら、線形の簡易カーブを自動生成
	if intensity_curve == null:
		intensity_curve = Curve.new()
		intensity_curve.add_point(Vector2(0.0, 0.0))
		intensity_curve.add_point(Vector2(0.3, 1.0))
		intensity_curve.add_point(Vector2(1.0, 0.0))

	if is_instance_valid(target_texture_rect):
		_init_texture_rect()


func _init_texture_rect() -> void:
	# TextureRect の初期設定:
	# - 画面全体を覆うように拡大(必要に応じて UI 側で調整してください)
	# - 初期アルファは 0(非表示)
	# - マウス入力などをブロックしないようにする
	target_texture_rect.modulate.a = 0.0
	target_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
	# ビネット用テクスチャを貼るなら、ここでデフォルトを設定しても良い
	# (本記事では「画像は自前で用意する」前提にしておきます)


func _autodetect_texture_rect() -> void:
	# よくある構成:
	# CanvasLayer
	#  └─ Control (HUD)
	#      └─ TextureRect (DamageVignette用)
	#
	# このコンポーネントと同じ親にある TextureRect を1つだけ見つけたら採用する。
	if get_parent() == null:
		return

	var candidates: Array[TextureRect] = []
	for child in get_parent().get_children():
		if child is TextureRect:
			candidates.append(child)

	if candidates.size() == 1:
		target_texture_rect = candidates[0]
	elif candidates.size() > 1:
		# 2つ以上ある場合は、名前で "Vignette" を含むものを優先
		for c in candidates:
			if "vignette" in c.name.to_lower():
				target_texture_rect = c
				return


## 外部から呼び出すメインAPI。
## ダメージを受けた瞬間に呼び出してください。
func trigger() -> void:
	if not is_instance_valid(target_texture_rect):
		push_warning("[DamageVignette] target_texture_rect が設定されていません。")
		return

	if _is_playing and not restart_on_trigger:
		# 再生中は無視するモード
		return

	# 既存の Tween があれば止めて破棄
	if _tween:
		_tween.kill()
		_tween = null

	_is_playing = true
	_t = 0.0

	# アニメーション全体の長さ(秒)
	var total_time := fade_in_time + fade_out_time

	# Tween を作成して、_t を 0 → 1 に動かす
	_tween = create_tween()
	_tween.tween_property(self, "_t", 1.0, total_time).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN_OUT)
	_tween.finished.connect(_on_tween_finished)

	# 即座に 1 フレーム分更新して、ラグを感じさせないようにする
	_update_vignette(0.0)


func _process(delta: float) -> void:
	if _is_playing:
		_update_vignette(delta)


func _update_vignette(delta: float) -> void:
	if not is_instance_valid(target_texture_rect):
		return

	# _t は Tween によって 0.0〜1.0 の範囲で変化する
	var curve_t := clamp(_t, 0.0, 1.0)
	var intensity := intensity_curve.sample(curve_t)  # 0.0〜1.0

	# カーブの値に応じてアルファを変化させる
	var new_alpha := max_alpha * intensity
	var color := target_texture_rect.modulate
	color.a = new_alpha
	target_texture_rect.modulate = color


func _on_tween_finished() -> void:
	_is_playing = false
	_tween = null
	# 念のため完全に消灯しておく
	if is_instance_valid(target_texture_rect):
		var color := target_texture_rect.modulate
		color.a = 0.0
		target_texture_rect.modulate = color


## 外部から「強制的に消す」ためのAPI
func clear() -> void:
	if _tween:
		_tween.kill()
		_tween = null
	_is_playing = false
	_t = 0.0

	if is_instance_valid(target_texture_rect):
		var color := target_texture_rect.modulate
		color.a = 0.0
		target_texture_rect.modulate = color

使い方の手順

ここでは、典型的な「プレイヤーがダメージを受けたら画面端が赤く光る」HUD 構成を例にします。

手順①:HUD シーンに TextureRect と DamageVignette を配置する

まずは UI / HUD 用のシーンを用意します。例として:

HUD (CanvasLayer)
 └── Root (Control)
      ├── HealthBar (TextureProgressBar)
      ├── Crosshair  (TextureRect)
      ├── DamageVignetteTexture (TextureRect)  <-- 赤いビネット画像を貼る
      └── DamageVignette (Node)                <-- コンポーネントをアタッチ
  • DamageVignetteTexture に、画面端が暗く(赤く)なった PNG などを設定します。
  • アンカーをフルスクリーン にして、画面全体を覆うようにしておきましょう。
    (インスペクタの「アンカーをフル矩形に」ボタンを押すと楽です)
  • DamageVignette ノードを追加し、スクリプトに上記の DamageVignette.gd をアタッチします。

エディタ上で DamageVignette を選択し、インスペクタで以下を設定します。

  • target_texture_rectDamageVignetteTexture をドラッグ&ドロップ
  • max_alpha:0.6〜0.8 くらいがおすすめ
  • fade_in_time:0.1 秒前後
  • fade_out_time:0.3〜0.5 秒くらい

target_texture_rect を指定し忘れても、同じ親に TextureRect が 1 つだけなら自動で拾ってくれますが、明示的に設定しておく方が安心です。

手順②:プレイヤーから DamageVignette を呼び出す

次に、プレイヤーがダメージを受けたときに trigger() を呼ぶようにします。プレイヤー側は「ダメージ演出」の中身を知らなくて OK です。

例:プレイヤーシーン構成

Player (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── PlayerDamageHandler (Node)   <-- ダメージ処理用コンポーネント(例)

HUD シーンは別に読み込んでいる想定です。例えば、メインシーンはこんな感じ:

Main (Node)
 ├── Player (CharacterBody2D)
 └── HUD (CanvasLayer)
      └── Root (Control)
           ├── HealthBar (TextureProgressBar)
           ├── Crosshair (TextureRect)
           ├── DamageVignetteTexture (TextureRect)
           └── DamageVignette (Node)

プレイヤー側から HUD の DamageVignette を参照する方法はいくつかありますが、シンプルな例としては「オートロード(シングルトン)の GameUI 経由」が扱いやすいです。

GameUI.gd(オートロード)例:


extends Node

## HUD シーンをツリーにインスタンスしたあとでセットする想定
var damage_vignette: DamageVignette

func register_hud(hud_root: Node) -> void:
	# どこかにある DamageVignette を探して登録
	damage_vignette = hud_root.get_node_or_null("DamageVignette")
	if damage_vignette == null:
		push_warning("[GameUI] DamageVignette が HUD に見つかりませんでした。")

func play_damage_vignette() -> void:
	if damage_vignette:
		damage_vignette.trigger()

HUD のスクリプト側で登録:


extends CanvasLayer

func _ready() -> void:
	# この CanvasLayer 直下の Root などを GameUI に登録
	GameUI.register_hud(self)

プレイヤー側でダメージ時に呼ぶ:


extends CharacterBody2D

var hp := 100

func apply_damage(amount: int) -> void:
	hp -= amount
	if hp < 0:
		hp = 0

	# HPバー更新など…

	# ダメージ演出を再生
	GameUI.play_damage_vignette()

これで、プレイヤーがダメージを受けるたびに、HUD の DamageVignette コンポーネントが赤いフラッシュを再生してくれます。

手順③:敵用デバッグシーンや別キャラにも簡単に流用

ダメージ演出は「プレイヤーに紐づいていない」ので、敵用のデバッグシーンを作るときも、単に GameUI.play_damage_vignette() を呼ぶだけで同じ演出が使えます。

例えば、敵シーン内で「テスト用にスペースキーでダメージを受ける」コードを書いても:


extends CharacterBody2D

func _process(delta: float) -> void:
	if Input.is_action_just_pressed("ui_accept"):
		# ダメージテスト
		GameUI.play_damage_vignette()

HUD 側のコンポーネントをまったく触らずに、どのシーンからでも同じ UI 演出を呼べます。

手順④:ビネット画像やカーブを変えて、好みの表現にチューニング

最後に、見た目の調整です。

  • ビネット画像(TextureRect の Texture) を差し替えることで、
    ・画面端だけ赤く
    ・画面全体を薄く赤く
    ・血しぶき風のテクスチャ
    など、いろいろな表現ができます。
  • intensity_curve を編集すると、
    ・最初は弱く、途中で一気に強く
    ・序盤だけ強くて、すぐにスッと引く
    といった時間変化を細かくコントロールできます。

カーブはインスペクタから直接いじれるので、ゲームを再生しながら「いい感じの立ち上がり/引き具合」を探してみましょう。


メリットと応用

DamageVignette をコンポーネントとして分離しておくと、こんなメリットがあります。

  • UI ロジックがプレイヤーから独立する
    プレイヤーのスクリプトは「HPを減らす」「死んだらシグナルを出す」などゲームロジックだけに集中させて、
    「どう見せるか」は UI コンポーネントに任せられます。
  • シーン構造がスッキリする
    HUD シーンの中で、「HPバー」「クロスヘア」「ダメージビネット」などをそれぞれ独立したコンポーネントとして管理できるので、
    深い階層や巨大な 1 ファイル UI スクリプトから解放されます。
  • 別プロジェクトへのコピペがしやすい
    DamageVignette.gd とビネット用 TextureRect をセットで持っていくだけで、別ゲームでも即再利用できます。
    継承ベースでガッチリ組んだ UI よりも、合成ベースのコンポーネントの方が「持ち運びやすい」のが大きな利点ですね。

応用としては:

  • HP が低いときに、trigger() を連打して「鼓動するような赤フラッシュ」にする
  • 毒ダメージ用に「緑のビネット」、スタミナ切れ用に「青のビネット」など、色違いコンポーネントを複数置く
  • 3D ゲームでも、画面全体を覆うビネットとして同じ仕組みを流用

といった使い方もできます。

改造案:HP割合に応じて自動でビネットの強さを変える

例えば「HP が減るほどビネットが濃くなる」ようにしたい場合、こんなメソッドを追加するのもアリです。


## HP 割合(0.0〜1.0)に応じて max_alpha を変化させる例。
## プレイヤーの HP 更新時に呼び出してください。
func set_intensity_by_health_ratio(health_ratio: float) -> void:
	# health_ratio が低いほど強く光らせたいので、1 - ratio を使う
	var danger := clamp(1.0 - health_ratio, 0.0, 1.0)
	# 0.2〜0.8 の範囲で線形に変化させる
	max_alpha = lerp(0.2, 0.8, danger)

あとはプレイヤー側で:


func _update_health_ui() -> void:
	var ratio := float(hp) / float(max_hp)
	health_bar.value = ratio * 100.0
	if GameUI.damage_vignette:
		GameUI.damage_vignette.set_intensity_by_health_ratio(ratio)

のように呼び出せば、HP が減るほど「被弾枠」が濃くなり、常にピンチ感を演出できます。

こんな感じで、小さな UI 演出も 1 つのコンポーネントとして切り出しておくと、後からの拡張や調整がとても楽になるので、ぜひ「継承より合成」で組んでいきましょう。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!