「とりあえず 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 をプロジェクトに追加
- Unity の Project ビューで、
Scriptsフォルダ(なければ作成)を開く。 - 右クリック → Create > C# Script を選択し、名前を
ColorTintにする。 - 自動生成された中身をすべて削除し、上のフルコードをコピペして保存。
手順②:対象オブジェクトにアタッチ
- プレイヤーキャラクター(3Dモデル or 2Dスプライト)
- 敵キャラクター(状態異常がつく敵)
- 動く床(乗ると炎上する床、毒の床など)
など、「色で状態を表現したい」オブジェクトに ColorTint をアタッチします。
- 対象の GameObject(例:
Player)を選択。 - Inspector の Add Component ボタンから
ColorTintを追加。 - Renderer の位置に応じて、
searchInParentを設定。
・プレイヤーの子にMeshRendererがある場合 →searchInParent = true(デフォルトのままでOK)
・色を変えたいSpriteRendererが子オブジェクトにある場合 →searchInParent = false - 必要に応じて、
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 をアタッチして、常に毒色にしておくこともできます。
- 床の GameObject に
ColorTintをアタッチ。 initialStateを Poisoned に設定。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 に任せる」というルールをチーム内で共有しておくと、プロジェクトが大きくなっても破綻しにくくなりますね。
