Unityを触り始めたころは、つい「とりあえず全部Updateに書いておけば動くしOK!」となりがちですよね。
プレイヤー操作、カメラ制御、UIの点滅演出、ダメージ処理……これらを1つの巨大スクリプトに押し込んでしまうと、数週間後には「どこを直せばいいのか分からない」「別シーンに持っていくのがつらい」という状態になりやすいです。
特に「被ダメージ演出(画面端を赤く光らせる)」のようなUI演出は、プレイヤー制御や敵AIとはまったく別の関心事なのに、つい同じクラスに書かれがちです。
そこでこの記事では、「ダメージを受けた瞬間に画面の端を赤く点滅させる」だけに責務を絞った
「DamageVignette」コンポーネントを作って、コンポーネント指向でスッキリ分離していく方法を解説します。
【Unity】ダメージ演出をUIに丸投げ!「DamageVignette」コンポーネント
このコンポーネントは、以下のようなシンプルな役割だけを持たせます。
- ダメージを受けたときに「今だけ赤く光らせてほしい」と呼び出される
- UIのTextureRect(Unity UI ToolkitのVisualElement)を赤くフェードイン/アウトさせる
- プレイヤーのHP管理や敵の攻撃ロジックは一切知らない
こうしておくと、「ダメージ演出」を担当する小さな部品として、どのシーンにも簡単に再利用できるようになります。
フルコード:DamageVignette.cs
前提:
- Unity6(UI Toolkit)を使用
- UI Documentに、画面全体を覆う赤いTextureRect(VisualElement)を用意してある
- そのVisualElementのnameをインスペクターから指定して使う
using UnityEngine;
using UnityEngine.UIElements;
/// <summary>
/**
* ダメージを受けた瞬間に画面端を赤く点滅させるコンポーネント。
*
* - UI Toolkit の UIDocument から TextureRect(VisualElement) を取得
* - ダメージを受けたときに public メソッドを呼ぶだけで演出を再生
* - プレイヤーのHPや敵の攻撃ロジックとは完全に分離
*/
/// </summary>
[RequireComponent(typeof(UIDocument))]
public class DamageVignette : MonoBehaviour
{
// ====== インスペクタ設定項目 ======
[Header("UI設定")]
[Tooltip("ダメージ演出に使用するTextureRect(VisualElement)のname")]
[SerializeField] private string vignetteElementName = "DamageVignette";
[Header("フェード設定")]
[Tooltip("最大まで赤くなるまでの時間(秒)")]
[SerializeField] private float fadeInDuration = 0.1f;
[Tooltip("赤から完全に消えるまでの時間(秒)")]
[SerializeField] private float fadeOutDuration = 0.4f;
[Tooltip("最大時のアルファ値(0〜1)。1で完全な不透明")]
[SerializeField] private float maxAlpha = 0.6f;
[Header("ダメージ連打時の挙動")]
[Tooltip("ダメージを連続で受けたときにフェードを上書きするか")]
[SerializeField] private bool restartOnDamage = true;
// ====== 内部状態 ======
private UIDocument uiDocument;
private VisualElement vignetteElement;
// 現在のアルファ値
private float currentAlpha = 0f;
// 演出中かどうか
private bool isPlaying = false;
// 経過時間
private float elapsed = 0f;
private void Awake()
{
// 同じGameObjectに付いているUIDocumentを取得
uiDocument = GetComponent<UIDocument>();
if (uiDocument == null)
{
Debug.LogError("[DamageVignette] UIDocument が見つかりません。GameObject に UIDocument を追加してください。", this);
enabled = false;
return;
}
}
private void OnEnable()
{
// UI Document のルートから指定した name の要素を探す
var root = uiDocument.rootVisualElement;
vignetteElement = root.Q<VisualElement>(vignetteElementName);
if (vignetteElement == null)
{
Debug.LogError($"[DamageVignette] name='{vignetteElementName}' の VisualElement が見つかりません。UXML 上の name を確認してください。", this);
enabled = false;
return;
}
// 初期状態では非表示(アルファ0)にしておく
SetVignetteAlpha(0f);
isPlaying = false;
elapsed = 0f;
}
private void Update()
{
if (!isPlaying) return;
elapsed += Time.deltaTime;
// フェードインとフェードアウトを時間ベースで制御
float totalDuration = fadeInDuration + fadeOutDuration;
if (totalDuration <= 0f)
{
// 万が一0設定の場合は即座に終了
SetVignetteAlpha(0f);
isPlaying = false;
return;
}
if (elapsed <= fadeInDuration)
{
// フェードイン区間
float t = Mathf.Clamp01(elapsed / Mathf.Max(fadeInDuration, 0.0001f));
currentAlpha = Mathf.Lerp(0f, maxAlpha, t);
}
else if (elapsed <= totalDuration)
{
// フェードアウト区間
float t = Mathf.Clamp01((elapsed - fadeInDuration) / Mathf.Max(fadeOutDuration, 0.0001f));
currentAlpha = Mathf.Lerp(maxAlpha, 0f, t);
}
else
{
// 終了
currentAlpha = 0f;
isPlaying = false;
}
SetVignetteAlpha(currentAlpha);
}
/// <summary>
/// 外部から呼び出す「ダメージ演出開始」メソッド。
/// プレイヤーがダメージを受けたタイミングでこれを呼ぶだけ。
/// </summary>
public void Play()
{
if (!enabled || vignetteElement == null)
{
return;
}
if (!isPlaying)
{
// 初回再生
isPlaying = true;
elapsed = 0f;
}
else
{
// すでに再生中のときの挙動
if (restartOnDamage)
{
// 最初からやり直す
elapsed = 0f;
}
else
{
// そのまま。ここでは「今の状態からさらに強くなる」などの処理もあり得る
// 必要に応じて挙動を拡張しましょう
}
}
}
/// <summary>
/// 現在のアルファ値を強制的に0にし、演出をリセットする。
/// </summary>
public void ResetVignette()
{
isPlaying = false;
elapsed = 0f;
currentAlpha = 0f;
SetVignetteAlpha(0f);
}
/// <summary>
/// 実際に VisualElement のスタイルにアルファ値を適用する共通関数。
/// </summary>
/// <param name="alpha">0〜1</param>
private void SetVignetteAlpha(float alpha)
{
if (vignetteElement == null) return;
// 現在の背景色を取得(未設定の場合は赤を仮定)
Color baseColor = Color.red;
// style.backgroundColor が設定されていればそれを基準にする
// 注意: UI Toolkit の Color は UnityEngine.Color と互換
if (vignetteElement.resolvedStyle.backgroundColor.a > 0f)
{
baseColor = vignetteElement.resolvedStyle.backgroundColor;
}
baseColor.a = Mathf.Clamp01(alpha);
vignetteElement.style.backgroundColor = new StyleColor(baseColor);
}
}
使い方の手順
ここでは、プレイヤーが敵からダメージを受けたときに、画面端が赤く点滅する例で説明します。
-
① UI Toolkit で赤いTextureRectを用意する
- UXMLファイルを作成し、画面全体を覆うような
VisualElementもしくはTextureRectを配置します。 - style(USS)で以下のような感じに設定しておくと分かりやすいです:
- position: absolute
- left/top/right/bottom: 0
- background-color: red(アルファは1.0でOK。スクリプト側で上書き)
- この要素の name を
DamageVignetteなどにしておきます。
- UXMLファイルを作成し、画面全体を覆うような
-
② シーンに UIDocument を配置する
- 空の GameObject を作成し、
UIDocumentコンポーネントを追加します。 Source Assetに①で作成した UXML を指定します。
- 空の GameObject を作成し、
-
③ 同じGameObjectに「DamageVignette」コンポーネントを追加する
- ②の UIDocument を持っている GameObject に
DamageVignetteスクリプトを追加します。 - インスペクターで
- Vignette Element Name に、UXML側で付けた name(例:
DamageVignette)を入力 - フェード時間や最大アルファ値を好みに合わせて調整
- Vignette Element Name に、UXML側で付けた name(例:
- ②の UIDocument を持っている GameObject に
-
④ プレイヤーのダメージ処理から「Play()」を呼ぶ
プレイヤーのHP管理コンポーネントなどから、ダメージを受けたタイミングで
DamageVignette.Play()を呼び出します。using UnityEngine; /// <summary> /// 例:とても簡略化したプレイヤーHP管理。 /// ダメージを受けたときに DamageVignette に演出を依頼する。 /// </summary> public class PlayerHealth : MonoBehaviour { [SerializeField] private int maxHp = 100; [SerializeField] private int currentHp = 100; [Header("ダメージ演出")] [SerializeField] private DamageVignette damageVignette; public void TakeDamage(int damage) { currentHp = Mathf.Max(currentHp - damage, 0); // ダメージ演出を再生 if (damageVignette != null) { damageVignette.Play(); } if (currentHp <= 0) { // 死亡処理など Debug.Log("Player Dead"); } } }このように、PlayerHealth は「UIの詳細」を一切知らず、
「ダメージ演出をしてくれるコンポーネント」にただ依頼しているだけ、という形になります。
同じように、敵のHPスクリプトから呼べば「敵がダメージを受けたときにも画面を赤くする」ことも簡単ですし、
動く床のダメージゾーンなどから呼べば「トラップに触れたときも同じ演出」を使い回せます。
メリットと応用
1. プレハブ管理が楽になる
- 「ダメージ演出」のロジックが
DamageVignetteに閉じ込められているので、
UI用のプレハブ(Canvas / UIDocumentを乗せたGameObject)を1つ作っておけば、
どのシーンにもそのプレハブをポンと置くだけで同じダメージ演出を再利用できます。 - プレイヤーや敵のプレハブは、HPや移動、攻撃など「ゲームロジック」だけに集中させられます。
2. レベルデザイン時の調整がしやすい
- ステージによって「ダメージが重く感じられるように、赤の演出を強めたい」などの要望が出たとき、
シーンごとにDamageVignetteコンポーネントのmaxAlphaやfadeOutDurationを変えるだけで演出を調整できます。 - ゲームデザイナーがインスペクターから値をいじるだけで、
「このステージはシビアだから、赤の残像を長く残そう」といった調整が可能になります。
3. 責務が分かれているのでバグ調査がしやすい
- 「ダメージ演出が出ない」問題が起きたとき、
DamageVignetteだけを見ればよいので、原因切り分けがとても楽です。 - 逆に「HPが減らない」問題は HP 管理側のコンポーネントだけを見ればよく、
UI演出が絡んできて混乱することがありません。
改造案:ダメージ量に応じて赤さを変える
例えば「大ダメージのときはより赤く、長く残したい」という場合、
Play() をダメージ量付きの関数に拡張するのもアリですね。
/// <summary>
/// ダメージ量に応じて強さを変えるバージョンの再生関数例。
/// </summary>
/// <param name="damageRatio">0〜1想定。1で最大ダメージ。</param>
public void PlayByDamage(float damageRatio)
{
// 0〜1にクランプ
damageRatio = Mathf.Clamp01(damageRatio);
// ダメージが大きいほどアルファとフェードアウト時間を強くする
float baseMaxAlpha = maxAlpha;
float baseFadeOut = fadeOutDuration;
maxAlpha = Mathf.Lerp(baseMaxAlpha * 0.3f, baseMaxAlpha, damageRatio);
fadeOutDuration = Mathf.Lerp(baseFadeOut * 0.5f, baseFadeOut * 1.5f, damageRatio);
Play();
// 必要であれば、あとで元の値に戻す処理を入れてもよい
// (ここでは簡略化のため省略)
}
このように、1つのコンポーネントに1つの責務を意識しておくと、
後からいくらでも安全に改造・差し替えができるようになります。
ぜひ、自分のプロジェクトでも「ダメージ演出はDamageVignetteに任せる」スタイルを試してみてください。
