バーチャル3Dクリエイター神部まゆみです(*^_^*)
この記事はUnityでダメージ表示演出を実装したので、それについての記事です。
前回の記事はこちら。
Starter Assetsを改変して作っているため、↓こちらの記事から見ると順番に進めていけます。
やりたいこと:敵味方問わずダメージを受けたらダメージ量を表示、量によって色を変えたり点滅させたりしたい
敵の攻撃はまだ作っていないのでこちらの攻撃でダメージを与えた時にしか表示できないけど。
ダメージ100までは白、それを超えたら赤とか設定できたら良いですね。
できれば敵を点滅させたりしたいかなぁ。
ChatGPTパイセンに聞いてみる
とりあえず彼に聞いてみます。




次はダメージを表示したいんですけど、UIとかでやるんでしょうか?低ダメージなら白、大ダメージなら赤みたいにできるのでしょうか?あとダメージを受けたら点滅とかもやりたいんですけど。敵味方どちらでも使える汎用性があるスクリプトがあると嬉しいです


いいテーマですね。
ここから一気に「ゲームっぽさ」が跳ね上がるところです。
結論から言うと👇
- ダメージ数値表示 → UI(World Space)
- 色分け → ダメージ量で色を切り替え
- 点滅 → Renderer or UI を一瞬フラッシュ
- 汎用化 → 「ダメージを受けた側」にまとめる
この設計にすると、敵・味方どちらでもそのまま使えます。
全体構成(まずはイメージ)
GunShoot
└ ダメージ値を渡す
↓
IDamageable.TakeDamage(int damage)
↓
DamageReceiver(敵でも味方でもOK)
├ HP減少
├ ダメージUI表示
└ 点滅エフェクト
「撃った側」は一切UIを知らない
→ これが汎用設計のコツです。
① ダメージ表示の基本(World Space UI)
やり方
- Canvas(World Space)
- TextMeshPro(おすすめ)
- ダメージを受けた位置に生成
DamagePopup.cs(汎用)
using UnityEngine;
using TMPro;
public class DamagePopup : MonoBehaviour
{
public TextMeshProUGUI text;
public float moveUpSpeed = 1f;
public float lifeTime = 1f;
void Update()
{
transform.position += Vector3.up * moveUpSpeed * Time.deltaTime;
}
public void Setup(int damage)
{
text.text = damage.ToString();
// ダメージ量で色分け
if (damage < 10)
text.color = Color.white;
else if (damage < 30)
text.color = Color.yellow;
else
text.color = Color.red;
Destroy(gameObject, lifeTime);
}
}
DamagePopup Prefab のポイント
- Canvas
- Render Mode:World Space
- TextMeshPro
- 「ゲーム開始時に再生」は関係なし
- Scale は 0.01〜0.02 くらいがちょうどいい
② ダメージを受ける側(汎用)
DamageReceiver.cs
using UnityEngine;
public class DamageReceiver : MonoBehaviour, IDamageable
{
public int maxHP = 100;
public int currentHP;
[Header("Damage UI")]
public GameObject damagePopupPrefab;
public Transform popupSpawnPoint;
[Header("Flash")]
public Renderer[] renderers;
public Color flashColor = Color.red;
public float flashDuration = 0.1f;
private Color[] originalColors;
void Awake()
{
currentHP = maxHP;
// 元の色を保存
originalColors = new Color[renderers.Length];
for (int i = 0; i < renderers.Length; i++)
{
originalColors[i] = renderers[i].material.color;
}
}
public void TakeDamage(int damage)
{
currentHP -= damage;
ShowDamagePopup(damage);
StartCoroutine(Flash());
if (currentHP <= 0)
{
Die();
}
}
void ShowDamagePopup(int damage)
{
if (damagePopupPrefab == null) return;
Vector3 pos = popupSpawnPoint != null
? popupSpawnPoint.position
: transform.position + Vector3.up;
GameObject popupObj = Instantiate(damagePopupPrefab, pos, Quaternion.identity);
popupObj.GetComponent<DamagePopup>().Setup(damage);
}
System.Collections.IEnumerator Flash()
{
foreach (var r in renderers)
r.material.color = flashColor;
yield return new WaitForSeconds(flashDuration);
for (int i = 0; i < renderers.Length; i++)
renderers[i].material.color = originalColors[i];
}
void Die()
{
Destroy(gameObject);
}
}
これが「汎用」な理由
- GunShoot は ダメージを渡すだけ
- 敵・味方の区別なし
- 見た目演出は 受けた側が責任を持つ
- UI / フラッシュ / 死亡処理を一元管理
👉 あとから差し替え・拡張が超ラク
③ よくある拡張(すぐできる)
🔴 クリティカルヒット
if (isCritical)
{
text.color = Color.magenta;
text.fontSize *= 1.3f;
}
✨ 点滅をShaderでやる(上級)
- Emission を一瞬強くする
- URPならFlash用Shader
🧠 味方だけUI非表示
if (isPlayer)
return;
まとめ
- ダメージ表示 → World Space UI
- 色分け → ダメージ量で制御
- 点滅 → DamageReceiver側で
- 汎用化の鍵 → 撃たれた側が全部やる
結構簡単にできそう?
とりあえずやってみます。
実際に実装してみる
ちゃんと実装できるのかやってみます。
前回作ったEnemyDamageReceiverコンポーネントは削除しておく
これが残ったままだと今回追加するDamageReceiver.csと重複してしまい正常に動かなかった。
詳しくは↓こちらの記事を参照。
インスペクターでオフにしてもダメなので削除しましょう。
TextMeshProをインポートしておく
これをインポートしておかないと使えません。
ウィンドウ ⇒ TextmeshPro のところ、とりあえずこの二つをインポートしておく。


ここでやらなくてもUIでTextMeshProを追加した時にインポートするかメニューが出るけど、まあ先にやっておく。
DamagePopup.csをコピペ
これをコピペしておく。


using UnityEngine;
using TMPro;
public class DamagePopup : MonoBehaviour
{
public TextMeshProUGUI text;
public float moveUpSpeed = 1f;
public float lifeTime = 1f;
void Update()
{
transform.position += Vector3.up * moveUpSpeed * Time.deltaTime;
}
public void Setup(int damage)
{
text.text = damage.ToString();
if (damage < 10)
text.color = Color.white;
else if (damage < 30)
text.color = Color.yellow;
else
text.color = Color.red;
Destroy(gameObject, lifeTime);
}
}
キャンバスとTextMeshProを追加、キャンバスにDamagePopUp.csをアタッチしてプレハブに、キャンバスはヒエラルキーから削除
ダメージを表示するキャンバスを作っていく。
①キャンバスとTextMeshProを追加
②キャンバスのレンダーモードをワールド空間に、位置はXYZすべて0、スケール0.01に。TextMeshProはCenter(真ん中)に表示
③キャンバスにDamagePopup.csをアタッチ、TextMeshProの参照を指定。LifeTime(表示する時間)は0.5くらいが良いかな?
④このキャンバスをヒエラルキーからプロジェクトにD&Dしてプレハブ化、ヒエラルキーのキャンバスは削除する
でOK。
④はとりあえずまだやらなくても動くけど、変なところに数値が表示されてしまうので動作確認したら削除したほうが良いかも。
ダメージが通ったらキャンバスのプレハブを呼ぶので、ヒエラルキーになくても勝手にその都度召喚されます。
設定はこんな感じ。




DamageReceiver.csをコピペしてEnemyにアタッチ
DamageReceiver.csをコピペします。


using UnityEngine;
using System.Collections;
public class DamageReceiver : MonoBehaviour, IDamageable
{
public int maxHP = 100;
public int currentHP;
[Header("Damage UI")]
public GameObject damagePopupPrefab;
public Transform popupSpawnPoint;
[Header("Flash")]
public Renderer[] renderers;
public Color flashColor = Color.red;
public float flashDuration = 0.1f;
Color[] originalColors;
void Awake()
{
currentHP = maxHP;
originalColors = new Color[renderers.Length];
for (int i = 0; i < renderers.Length; i++)
{
originalColors[i] = renderers[i].material.color;
}
}
public void TakeDamage(int damage)
{
Debug.Log($"{gameObject.name} takes {damage}");
currentHP -= damage;
ShowDamagePopup(damage);
StartCoroutine(Flash());
if (currentHP <= 0)
{
Destroy(gameObject);
}
}
void ShowDamagePopup(int damage)
{
if (damagePopupPrefab == null)
return;
Vector3 pos = popupSpawnPoint != null
? popupSpawnPoint.position
: transform.position + Vector3.up;
GameObject popup = Instantiate(damagePopupPrefab, pos, Quaternion.identity);
popup.GetComponent<DamagePopup>().Setup(damage);
}
IEnumerator Flash()
{
foreach (var r in renderers)
r.material.color = flashColor;
yield return new WaitForSeconds(flashDuration);
for (int i = 0; i < renderers.Length; i++)
renderers[i].material.color = originalColors[i];
}
}
これでEnemyにアタッチして設定します。


DamageReceiver.csの設定
設定はこんな感じ。


Damage Popup Prefab さっきプレハブにしたキャンバスを指定
Popup Spawn Point ダメージ数値が出現する場所。空のオブジェクトで敵の頭上に配置し、追従するよう敵の子にしてあります。


レンダラー 敵のメッシュを指定。被ダメージで色を変えるのに使う
Flash Color 被ダメージで点滅させる色を指定
Flash Duration 被ダメージで点滅させる時間を指定
これで動いた!
これで無事に動きました!
動かない場合の対処法など
今回少しハマったけど、最初に書いたEnemyDamageReceiverコンポーネントを削除せずオフにしていたことが原因だった。
どこが原因か突き止めるためにデバッグログを出していたので、動かない場合はこれをやると解決するかも。
①DamagePopup 側で「呼ばれたか」を確認する
DamagePopup.cs をこう書き換えてください(デバッグ用)
public void Setup(int damage)
{
Debug.Log("DamagePopup.Setup called with damage = " + damage);
// 強制的に値を書き換え
text.text = damage.ToString();
// 強制的に色も変更(分かりやすく)
text.color = Color.green;
Destroy(gameObject, lifeTime);
}
ここで見るポイント
- Console に
DamagePopup.Setup called with damage = 10が出るか?
❌ ログが出ない
→ Setup() が呼ばれていない
⭕ ログは出るが TextMeshProが初期値 のまま
→ TextMeshPro の参照が取れていない
②参照が取れているかを強制チェック
DamagePopup.cs の Start() に追加
void Start()
{
if (text == null)
{
Debug.LogError("TextMeshPro reference is NULL!");
}
else
{
// 起動直後に強制的に書き換えてみる
text.text = "START";
text.color = Color.cyan;
}
}
❌ Console に
TextMeshPro reference is NULL!
と出る場合、
→ Inspector で Text を割り当てていない
⭕TextMeshProのテキストが START に変わる
→ 参照はOK、Setup() が呼ばれていないのが原因
③DamageReceiver 側が本当に呼ばれているか
DamageReceiver.cs の ShowDamagePopup にログ追加
void ShowDamagePopup(int damage)
{
Debug.Log("ShowDamagePopup called damage = " + damage);
if (damagePopupPrefab == null)
{
Debug.LogError("damagePopupPrefab is NULL");
return;
}
GameObject popup = Instantiate(damagePopupPrefab, transform.position + Vector3.up, Quaternion.identity);
DamagePopup dp = popup.GetComponent<DamagePopup>();
if (dp == null)
{
Debug.LogError("DamagePopup component NOT found on prefab");
return;
}
dp.Setup(damage);
}
ここまでで分かること
| 状態 | 原因 |
|---|---|
| ShowDamagePopup が出ない | TakeDamage が呼ばれてない |
| ShowDamagePopup 出る / Setup 出ない | GetComponent 失敗 |
| Setup 出る / 999 のまま | Text 未割り当て |
| START にも変わらない | Canvas / TMP 設定ミス |
TextMeshProの数値 が変わらない「一番多い原因」
90% これです👇
DamagePopup Prefab の Inspector で
DamagePopup (Script)- Text フィールドが None のまま
👉 Hierarchy 内の
TextMeshProUGUI をドラッグしてセットしてください。
Prefab 化したあとに
参照が外れるのは Unity あるあるです。
念のためチェックリスト(上から順に)
- ✅ Console に
ShowDamagePopup called出る? - ✅
DamagePopup.Setup called出る? - ✅
TextMeshPro reference is NULL!出てない? - ✅ Prefab の Text が割り当てられている?
- ✅ Canvas が World Space?
- ✅ Scale が 0.01 前後?
ここまでやれば必ず原因が特定できます
あとは最初からやり直したりするとうまくいくかもしれません。
ちょっとしたことで動かなくなるため、見落としがある場合もあります(^_^;)
おわりに
少しハマったけどダメージ表示が実装できて良かった。
これで結構ゲームっぽくなってきたかな?
また続きが書けたら追記します(*^_^*)






