「とりあえず Update() に全部書いてしまう」──Unityを触り始めた頃はよくあるパターンですよね。
移動、入力、エフェクト、状態管理、UI更新…すべて1つのスクリプトの Update() に詰め込んでいくと、最初は動いていても、だんだん「どこを触れば何が変わるのか」分からなくなってきます。

特に「状態に応じて見た目を変える」処理(凍結したら青く、炎上したら赤く…など)は、ついプレイヤー制御スクリプトや敵AIスクリプトの中に直接書いてしまいがちです。

そこでこの記事では、「見た目の色変化」だけを担当する小さなコンポーネント 「ColorTint」 を用意して、
・状態の管理(凍結・炎上など)
・見た目の反映(色の変更)
をきれいに分離していくやり方を紹介します。

【Unity】状態異常の色をスッと切り替える!「ColorTint」コンポーネント

今回作る ColorTint コンポーネントは、

  • 「凍結」「炎上」などの状態を列挙型で管理
  • 状態に応じたターゲットカラーを決定
  • 現在の色からターゲットカラーへ、時間をかけて滑らかに補間(Lerp)
  • 親オブジェクト(または自分自身)の SpriteRenderer / MeshRenderer / Renderer の色を変更

という役割だけに責務を絞ったコンポーネントです。
プレイヤーや敵の「状態管理」は別コンポーネントに任せて、そちらから SetState() を呼んであげるイメージですね。


フルコード:ColorTint.cs


using UnityEngine;

/// <summary>状態に応じて親(または自分自身)の色を滑らかに変えるコンポーネント</summary>
[DisallowMultipleComponent]
public class ColorTint : MonoBehaviour
{
    /// <summary>見た目の状態を表すシンプルな列挙型</summary>
    public enum VisualState
    {
        Normal,     // 通常
        Frozen,     // 凍結
        Burning,    // 炎上
        Poisoned,   // 毒
        Custom      // 任意の色(外部から指定)
    }

    [Header("対象レンダラーの探索設定")]
    [SerializeField]
    private bool searchInParent = true; 
    // true: 親階層から Renderer を探す / false: 自分の子階層から探す

    [Header("色変化の設定")]
    [SerializeField]
    private float lerpSpeed = 5f; 
    // 色を滑らかに変える速さ。大きいほど素早く変わる

    [Tooltip("Normal 状態のときの色")]
    [SerializeField]
    private Color normalColor = Color.white;

    [Tooltip("Frozen (凍結) 状態のときの色")]
    [SerializeField]
    private Color frozenColor = new Color(0.6f, 0.8f, 1f, 1f); // 少し青みがかった色

    [Tooltip("Burning (炎上) 状態のときの色")]
    [SerializeField]
    private Color burningColor = new Color(1f, 0.4f, 0.2f, 1f); // オレンジっぽい赤

    [Tooltip("Poisoned (毒) 状態のときの色")]
    [SerializeField]
    private Color poisonedColor = new Color(0.5f, 1f, 0.5f, 1f); // 黄緑

    [Tooltip("Custom 状態用の色(外部からも変更可能)")]
    [SerializeField]
    private Color customColor = Color.magenta;

    [Header("初期状態")]
    [SerializeField]
    private VisualState initialState = VisualState.Normal;

    // 実際に色を変更する対象の Renderer
    private Renderer targetRenderer;

    // 実際に操作するマテリアルインスタンス
    private Material targetMaterial;

    // 現在の状態
    private VisualState currentState;

    // 現在の色(Updateで補間に使う)
    private Color currentColor;

    // 目標とする色
    private Color targetColor;

    private static readonly int ColorPropertyId = Shader.PropertyToID("_Color");

    private void Awake()
    {
        // 対象となる Renderer を探す
        CacheRenderer();

        if (targetRenderer == null)
        {
            Debug.LogWarning(
                $"[ColorTint] Renderer が見つかりませんでした。ゲームオブジェクト: {gameObject.name}",
                this
            );
            enabled = false;
            return;
        }

        // マテリアルインスタンスを取得
        // 注意: .material はインスタンスを複製するので、動的な色変更にはこちらを推奨
        targetMaterial = targetRenderer.material;

        // 初期状態のセット
        currentState = initialState;
        targetColor = GetColorByState(currentState);

        // 初期色をマテリアルから取得できればそれを使う
        if (targetMaterial.HasProperty(ColorPropertyId))
        {
            currentColor = targetMaterial.GetColor(ColorPropertyId);
        }
        else
        {
            // _Color プロパティが無いシェーダーの場合は targetColor で初期化
            currentColor = targetColor;
        }

        // 初期色を即座に反映
        ApplyColorImmediate(currentColor);
    }

    private void Update()
    {
        if (targetMaterial == null) return;

        // 現在の状態に応じたターゲットカラーを計算
        targetColor = GetColorByState(currentState);

        // 時間経過で滑らかに色を補間
        currentColor = Color.Lerp(currentColor, targetColor, Time.deltaTime * lerpSpeed);

        // マテリアルに反映
        ApplyColor(currentColor);
    }

    /// <summary>
    /// 外部から状態を変更するための公開メソッド
    /// 例: GetComponent<ColorTint>().SetState(ColorTint.VisualState.Frozen);
    /// </summary>
    public void SetState(VisualState newState)
    {
        currentState = newState;
        // 状態が変わった瞬間に、ターゲットカラーだけ更新しておく
        targetColor = GetColorByState(currentState);
    }

    /// <summary>
    /// Custom 状態用の色を外部から設定する
    /// </summary>
    public void SetCustomColor(Color color)
    {
        customColor = color;
        if (currentState == VisualState.Custom)
        {
            targetColor = customColor;
        }
    }

    /// <summary>
    /// 自身または親階層から Renderer を探してキャッシュする
    /// </summary>
    private void CacheRenderer()
    {
        if (searchInParent)
        {
            // 親階層から最初に見つかった Renderer を取得
            targetRenderer = GetComponentInParent<Renderer>();
        }
        else
        {
            // 自分自身または子階層から Renderer を取得
            targetRenderer = GetComponentInChildren<Renderer>();
        }
    }

    /// <summary>
    /// 状態に応じた色を返す
    /// </summary>
    private Color GetColorByState(VisualState state)
    {
        switch (state)
        {
            case VisualState.Normal:
                return normalColor;
            case VisualState.Frozen:
                return frozenColor;
            case VisualState.Burning:
                return burningColor;
            case VisualState.Poisoned:
                return poisonedColor;
            case VisualState.Custom:
                return customColor;
            default:
                return normalColor;
        }
    }

    /// <summary>
    /// 実際にマテリアルへ色を反映する
    /// </summary>
    private void ApplyColor(Color color)
    {
        if (targetMaterial == null) return;

        if (targetMaterial.HasProperty(ColorPropertyId))
        {
            targetMaterial.SetColor(ColorPropertyId, color);
        }
    }

    /// <summary>
    /// 即座に色を反映したい場合に使用(Awake など)
    /// </summary>
    private void ApplyColorImmediate(Color color)
    {
        currentColor = color;
        ApplyColor(color);
    }

    /// <summary>
    /// 現在の状態を取得したいとき用(他コンポーネントから参照)
    /// </summary>
    public VisualState GetCurrentState()
    {
        return currentState;
    }

    /// <summary>
    /// 現在の色を取得したいとき用(デバッグなど)
    /// </summary>
    public Color GetCurrentColor()
    {
        return currentColor;
    }
}

使い方の手順

ここからは、実際に「プレイヤー」「敵」「動く床」などで使う具体的な手順を見ていきましょう。

手順①:ColorTint.cs をプロジェクトに追加

  1. Unity の Project ビューで、Scripts フォルダ(なければ作成)を開く。
  2. 右クリック → Create > C# Script を選択し、名前を ColorTint にする。
  3. 自動生成された中身をすべて削除し、上のフルコードをコピペして保存。

手順②:対象オブジェクトにアタッチ

  • プレイヤーキャラクター(3Dモデル or 2Dスプライト)
  • 敵キャラクター(状態異常がつく敵)
  • 動く床(乗ると炎上する床、毒の床など)

など、「色で状態を表現したい」オブジェクトに ColorTint をアタッチします。

  1. 対象の GameObject(例: Player)を選択。
  2. Inspector の Add Component ボタンから ColorTint を追加。
  3. Renderer の位置に応じて、searchInParent を設定。
    ・プレイヤーの子に MeshRenderer がある場合 → searchInParent = true(デフォルトのままでOK)
    ・色を変えたい SpriteRenderer が子オブジェクトにある場合 → searchInParent = false
  4. 必要に応じて、normalColor / frozenColor / burningColor / poisonedColor を好みの色に調整。

手順③:状態を切り替えるスクリプトから呼び出す

ColorTint は「状態の見た目」を担当するだけなので、「いつ凍結したか」「いつ炎上したか」といったロジックは別コンポーネントに任せるのがおすすめです。

例として、プレイヤーに簡単な状態異常を付与するスクリプトを作ってみます。


using UnityEngine;

/// <summary>デモ用の簡易状態異常コントローラー</summary>
[DisallowMultipleComponent]
public class DemoStatusController : MonoBehaviour
{
    [SerializeField]
    private ColorTint colorTint; // Inspector で ColorTint をアサイン

    private void Reset()
    {
        // 同じ GameObject 上から ColorTint を自動で探してアサイン
        if (colorTint == null)
        {
            colorTint = GetComponent<ColorTint>();
        }
    }

    private void Update()
    {
        // キーボード入力で状態を切り替える簡易デモ
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            colorTint.SetState(ColorTint.VisualState.Normal);
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            colorTint.SetState(ColorTint.VisualState.Frozen);
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            colorTint.SetState(ColorTint.VisualState.Burning);
        }
        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            colorTint.SetState(ColorTint.VisualState.Poisoned);
        }

        // Custom 状態にして、色も一緒に変える例
        if (Input.GetKeyDown(KeyCode.C))
        {
            colorTint.SetCustomColor(Random.ColorHSV());
            colorTint.SetState(ColorTint.VisualState.Custom);
        }
    }
}

このスクリプトをプレイヤーにアタッチしてゲームを再生すると、

  • 1キー: 通常色
  • 2キー: 凍結色
  • 3キー: 炎上色
  • 4キー: 毒色
  • Cキー: ランダムな Custom 色

にスムーズに切り替わるのが確認できます。

手順④:敵や動く床への応用例

敵キャラクターへの応用

敵が炎のトラップに触れたら「炎上」、氷の魔法を受けたら「凍結」にするようなスクリプトは、例えばこんな形になります。


using UnityEngine;

[RequireComponent(typeof(ColorTint))]
public class EnemyStatus : MonoBehaviour
{
    private ColorTint colorTint;

    private void Awake()
    {
        colorTint = GetComponent<ColorTint>();
    }

    public void ApplyBurning()
    {
        colorTint.SetState(ColorTint.VisualState.Burning);
    }

    public void ApplyFrozen()
    {
        colorTint.SetState(ColorTint.VisualState.Frozen);
    }

    public void ClearStatus()
    {
        colorTint.SetState(ColorTint.VisualState.Normal);
    }
}

トラップ側からは other.GetComponent<EnemyStatus>()?.ApplyBurning(); のように呼び出すだけで、見た目の色変化は ColorTint にお任せできます。

動く床への応用

例えば「乗ると毒状態になる床」を作りたい場合、床オブジェクトに ColorTint をアタッチして、常に毒色にしておくこともできます。

  1. 床の GameObject に ColorTint をアタッチ。
  2. initialStatePoisoned に設定。
  3. poisonedColor を好きな毒っぽい色に調整。

これだけで、シーンビュー上でも「この床は毒だな」と一目で分かるようになります。
実際の状態付与ロジック(プレイヤーに毒を付ける処理)は別スクリプトに切り出せばOKです。


メリットと応用

コンポーネント分割のメリット

  • 責務が明確
    ColorTint は「状態に応じた色の変化」だけを担当します。
    プレイヤー制御や敵AIの巨大な Update() に色変えロジックを書かなくて済むので、コードが読みやすくなります。
  • プレハブの再利用性が高まる
    一度 ColorTint を組み込んだプレハブを作っておけば、別のシーンや別のプロジェクトに持っていっても、
    「状態 → 色変化」の仕組みをそのまま流用できます。
  • レベルデザインが楽になる
    レベルデザイナーは Inspector 上で normalColor / frozenColor / burningColor / poisonedColor を調整するだけで、
    「この敵は凍結したらもっと青くしたい」「毒床はもっとドギツイ色にしたい」といった要望に対応できます。
    コードを書き換える必要がないので、試行錯誤がしやすいですね。
  • 状態管理ロジックとの分離
    状態異常の持続時間、ダメージ量、スタック数などのゲームロジックは別コンポーネントに閉じ込めておき、
    その結果だけを ColorTint に伝える形にすると、テストやデバッグもやりやすくなります。

改造案:フェードアウトしながら色を戻す

例えば「炎上が終わるとき、少しだけ暗くなりつつ通常色に戻す」といった演出を入れたい場合、
ColorTint に簡単なフェードアウト関数を追加することもできます。


    /// <summary>
    /// 一定時間かけて通常状態へ戻しつつ、徐々に暗くする演出
    /// コルーチンから呼び出して使う想定
    /// </summary>
    private System.Collections.IEnumerator FadeBackToNormal(float duration)
    {
        float time = 0f;
        Color startColor = currentColor;
        Color endColor = normalColor * 0.8f; // ほんの少し暗めの通常色

        while (time < duration)
        {
            float t = time / duration;
            Color lerped = Color.Lerp(startColor, endColor, t);
            ApplyColor(lerped);
            time += Time.deltaTime;
            yield return null;
        }

        // 最後に完全な通常状態へ
        currentState = VisualState.Normal;
        ApplyColorImmediate(normalColor);
    }

このように、小さなコンポーネントにしておくと、「ちょっとした演出」を後から追加・削除するのも簡単です。
「状態の見た目は ColorTint に任せる」というルールをチーム内で共有しておくと、プロジェクトが大きくなっても破綻しにくくなりますね。