Unityを触り始めた頃は、つい Update() の中に「入力処理」「移動」「ダメージ計算」「UI更新」など、全部まとめて書いてしまいがちですよね。
敵にダメージを与えたときの「ダメージ数字のポップアップ」も、ついその巨大なスクリプトの中で Instantiate して、移動させて、フェードアウトさせて…と詰め込んでしまうと、すぐにカオスになります。

そこでこの記事では、「ダメージ数字のポップアップ表示」だけに責務を絞ったコンポーネント FloatingText を作っていきます。
プレイヤーや敵のスクリプトからは「ダメージ値を渡して呼ぶだけ」にして、見た目や挙動の細かい制御はこのコンポーネントに任せる構成にしてみましょう。

【Unity】ダメージ数字をふわっと表示!「FloatingText」コンポーネント

ここでは「ダメージ発生時に、親オブジェクトの頭上にテキストを生成して、上に浮かびながらフェードアウトして消える」挙動をまとめた FloatingText コンポーネントを実装します。

  • ダメージの数値を受け取ると
  • 親の頭上にテキストを生成し
  • 上方向に少し移動しながら、一定時間でフェードアウト
  • 終わったら自動で破棄

という流れを、1つの責務として切り出します。

前提:Canvas と TextMeshPro の利用

・ワールド空間に浮かぶテキストとして TextMeshProTextMeshProUGUI ではなく TextMeshPro)を使います。
・Unity6 でも TextMeshPro は標準パッケージとして利用できます。

構成イメージ

  • FloatingText:ダメージ表示を管理するコンポーネント(親側に付ける)
  • FloatingTextItem:1つ1つのダメージテキストの挙動を制御するコンポーネント(プレハブ側に付ける)

コンポーネントを小さく分けることで、1つの責務が明確になります。


フルコード:FloatingText & FloatingTextItem

以下をそのままコピペして使えます。

FloatingTextItem.cs(1つのテキストの挙動)


using UnityEngine;
using TMPro;

/// <summary>1つのダメージテキストの挙動を制御するコンポーネント</summary>
[RequireComponent(typeof(TextMeshPro))]
public class FloatingTextItem : MonoBehaviour
{
    [Header("移動設定")]
    [SerializeField] private Vector3 moveDirection = new Vector3(0f, 1.5f, 0f); // 上方向への移動
    [SerializeField] private float moveDuration = 0.6f;      // 何秒かけて移動するか

    [Header("フェード設定")]
    [SerializeField] private float fadeDuration = 0.6f;      // 何秒かけてフェードアウトするか

    [Header("ランダムオフセット")]
    [SerializeField] private float horizontalRandomRange = 0.3f; // 横方向のランダムずれ

    private TextMeshPro textMesh;
    private float elapsed;
    private Color initialColor;
    private Vector3 startPosition;
    private Vector3 targetPosition;

    private void Awake()
    {
        textMesh = GetComponent<TextMeshPro>();
        initialColor = textMesh.color;
    }

    /// <summary>
    /// テキスト内容と初期設定を行う初期化メソッド
    /// </summary>
    public void Initialize(string text, Color color, float scale = 1f)
    {
        // テキストと色を設定
        textMesh.text = text;
        initialColor = color;
        textMesh.color = initialColor;

        // スケールを設定(クリティカル時だけ大きくする等)
        transform.localScale = Vector3.one * scale;

        // 開始位置とターゲット位置を決定
        startPosition = transform.position;

        // 横方向に少しランダムなずれを加える
        float randomX = Random.Range(-horizontalRandomRange, horizontalRandomRange);
        Vector3 randomOffset = new Vector3(randomX, 0f, 0f);

        targetPosition = startPosition + moveDirection + randomOffset;

        // タイマー初期化
        elapsed = 0f;
    }

    private void Update()
    {
        elapsed += Time.deltaTime;

        // 進行度 0.0 ~ 1.0
        float moveT = Mathf.Clamp01(elapsed / moveDuration);
        float fadeT = Mathf.Clamp01(elapsed / fadeDuration);

        // イージング(少しふわっとした感じにする)
        float easedMoveT = 1f - Mathf.Pow(1f - moveT, 2f); // easeOutQuad っぽい

        // 位置補間
        transform.position = Vector3.Lerp(startPosition, targetPosition, easedMoveT);

        // アルファ値を徐々に 0 に
        Color c = initialColor;
        c.a = Mathf.Lerp(1f, 0f, fadeT);
        textMesh.color = c;

        // 両方の処理が終わったら破棄
        if (moveT >= 1f && fadeT >= 1f)
        {
            Destroy(gameObject);
        }
    }
}

FloatingText.cs(親に付ける、ダメージ発生時に呼び出す)


using UnityEngine;
using TMPro;

/// <summary>
/// 親オブジェクトの頭上にダメージテキストを生成して浮かばせるコンポーネント
/// </summary>
public class FloatingText : MonoBehaviour
{
    [Header("プレハブ参照")]
    [SerializeField] private FloatingTextItem floatingTextPrefab;

    [Header("表示位置設定")]
    [SerializeField] private Vector3 worldOffset = new Vector3(0f, 2f, 0f); 
    // 親の位置からどれだけ上に表示するか(キャラの身長に応じて調整)

    [Header("色設定")]
    [SerializeField] private Color normalColor = Color.white;
    [SerializeField] private Color criticalColor = Color.yellow;

    [Header("スケール設定")]
    [SerializeField] private float normalScale = 1f;
    [SerializeField] private float criticalScale = 1.4f;

    /// <summary>
    /// 通常ダメージのポップアップを表示する
    /// </summary>
    /// <param name="amount">ダメージ量</param>
    public void ShowDamage(int amount)
    {
        SpawnText(amount, isCritical: false);
    }

    /// <summary>
    /// クリティカルダメージのポップアップを表示する
    /// </summary>
    /// <param name="amount">ダメージ量</param>
    public void ShowCriticalDamage(int amount)
    {
        SpawnText(amount, isCritical: true);
    }

    /// <summary>
    /// 実際にテキストを生成して初期化する内部メソッド
    /// </summary>
    private void SpawnText(int amount, bool isCritical)
    {
        if (floatingTextPrefab == null)
        {
            Debug.LogWarning($"FloatingTextPrefab が未設定です: {name}", this);
            return;
        }

        // 表示位置(親の現在位置 + オフセット)
        Vector3 spawnPosition = transform.position + worldOffset;

        // プレハブを生成(ワールド空間)
        FloatingTextItem instance = Instantiate(
            floatingTextPrefab,
            spawnPosition,
            Quaternion.identity
        );

        // 表示文字列(必要に応じて "+" や "-" を付ける)
        string text = amount.ToString();

        // 色とスケールを選択
        Color color = isCritical ? criticalColor : normalColor;
        float scale = isCritical ? criticalScale : normalScale;

        // 生成したテキストを初期化
        instance.Initialize(text, color, scale);
    }
}

使い方の手順

  1. ① TextMeshPro のセットアップ
    • Unity メニューから Window > TextMeshPro > Import TMP Essential Resources を実行して、TextMeshPro を使えるようにしておきます。
  2. ② ダメージテキスト用プレハブ(FloatingTextItem)を作成
    1. ヒエラルキー上で 右クリック > 3D Object > Text - TextMeshPro を作成。
    2. 名前を FloatingTextItem に変更。
    3. フォントサイズや色、アウトラインなどを好みに調整。
    4. 上記の FloatingTextItem.cs をアタッチ。
    5. このオブジェクトを Project ウィンドウにドラッグしてプレハブ化 します。
  3. ③ 親オブジェクトに FloatingText を付ける
    例として、プレイヤーや敵キャラの GameObject に以下を行います。
    1. プレイヤー(または敵)オブジェクトを選択。
    2. Add Component から FloatingText を追加。
    3. Floating Text Prefab の欄に、手順②で作った FloatingTextItem プレハブをドラッグ&ドロップ。
    4. World Offset を調整して、「頭上のどのあたりに出すか」を調整(例:(0, 2, 0))。
    5. 色やスケールを好みに設定。
  4. ④ ダメージ処理から呼び出す
    プレイヤーや敵の「HP管理コンポーネント」から、ダメージを受けたタイミングで FloatingText を呼び出します。
    例として、敵に付けるシンプルな HP スクリプトはこんな感じです。
    
    using UnityEngine;
    
    public class EnemyHealth : MonoBehaviour
    {
        [SerializeField] private int maxHp = 100;
        private int currentHp;
    
        private FloatingText floatingText;
    
        private void Awake()
        {
            currentHp = maxHp;
            // 同じ GameObject 上の FloatingText を取得
            floatingText = GetComponent<FloatingText>();
        }
    
        /// <summary>ダメージを受ける</summary>
        public void TakeDamage(int amount, bool isCritical = false)
        {
            currentHp -= amount;
            currentHp = Mathf.Max(currentHp, 0);
    
            // ダメージポップアップを表示
            if (floatingText != null)
            {
                if (isCritical)
                {
                    floatingText.ShowCriticalDamage(amount);
                }
                else
                {
                    floatingText.ShowDamage(amount);
                }
            }
    
            if (currentHp <= 0)
            {
                Die();
            }
        }
    
        private void Die()
        {
            // 死亡処理(ここでは単純に消すだけ)
            Destroy(gameObject);
        }
    }
    

    これで、TakeDamage(20) を呼ぶたびに、敵の頭上に「20」というテキストがふわっと表示されるようになります。

同じように、プレイヤーに FloatingText を付けておけば、回復量 を緑色でポップアップさせる、といった使い方もできます。


メリットと応用

  • ダメージ表示の責務を分離できる
    ダメージ計算ロジックと「見た目の演出」を切り離せるので、HP 管理コンポーネントがシンプルになります。
    「数字の動きや色を変えたい」といった変更は FloatingTextItem 側だけを触ればよく、他のスクリプトを壊しにくくなります。
  • プレハブ化でレベルデザインが楽になる
    敵プレハブに FloatingText を仕込んでおけば、シーンに配置しただけで「ダメージポップアップ対応の敵」が量産できます。
    ボスだけクリティカルの色やスケールを変える、といった調整もプレハブのインスタンスごとにインスペクタから行えます。
  • 他の用途にも使い回しやすい
    「経験値+10」「コイン+1」「レベルアップ!」など、テキストの中身を変えるだけで、さまざまなポップアップ演出に流用できます。
    ShowDamage 以外に「任意テキスト表示メソッド」を足しておくと、汎用ポップアップとしても便利です。

改造案:任意テキストを表示するメソッドを追加

例えば、FloatingText に「好きな文字列を表示する汎用メソッド」を追加すると、ダメージ以外の用途にも簡単に使い回せます。


    /// <summary>
    /// 任意のテキストを指定の色とスケールで表示する
    /// 例:回復時に "+20" を緑色で表示するなど
    /// </summary>
    public void ShowCustomText(string text, Color color, float scale = 1f)
    {
        if (floatingTextPrefab == null)
        {
            Debug.LogWarning($"FloatingTextPrefab が未設定です: {name}", this);
            return;
        }

        Vector3 spawnPosition = transform.position + worldOffset;

        FloatingTextItem instance = Instantiate(
            floatingTextPrefab,
            spawnPosition,
            Quaternion.identity
        );

        instance.Initialize(text, color, scale);
    }

このメソッドを使えば、例えばプレイヤー回復時に


floatingText.ShowCustomText($"+{healAmount}", Color.green, 1.2f);

のように呼び出すだけで、回復ポップアップも簡単に実現できます。
責務ごとに小さくコンポーネントを分けておくと、こうした拡張もスムーズですね。