Unityを触り始めた頃、「とりあえず全部 Update() に書いておけば動くし楽!」となりがちですよね。
マウスホバーの判定、マテリアルの切り替え、クリック処理、アニメーションの制御…と、どんどん1つのスクリプトに詰め込んでいくと、最終的には何をどこで触っているのか分からない巨大なGodクラスが出来上がってしまいます。

そうなると、

  • 別オブジェクトでも同じ処理を使い回したいのに、依存関係がぐちゃぐちゃで再利用できない
  • ちょっとだけ仕様を変えたいのに、他の機能まで巻き込んでバグを生む
  • デザイナーやレベルデザイナーがプレハブを触りにくい(インスペクターで調整できない)

といった問題が出てきます。

この記事では、「マウスホバー時にだけ輪郭線を出したい」というよくある要件を、
OutlineShader という小さなコンポーネントに切り出して実装してみます。

役割はシンプルに、

  • マウスが乗ったら:親の SpriteRenderer のマテリアルに「輪郭線オン」のフラグを立てる
  • マウスが離れたら:フラグを戻す

だけに絞ります。クリック処理やドラッグ処理は別コンポーネントに分けることで、
「見た目(ホバー演出)」と「ゲームロジック(クリックで何かする)」を綺麗に分離できるようにしましょう。


【Unity】マウスホバーで光らせる!「OutlineShader」コンポーネント

まずは完成コードから載せます。
この記事のコードは Unity 6 / C# を想定し、2Dの SpriteRenderer を対象にしています。
(3D MeshRenderer に変えたい場合のヒントは後半で触れます)

フルコード:OutlineShader.cs


using UnityEngine;

/// <summary>
/// マウスホバー時に親オブジェクトのスプライトマテリアルを操作して
/// 輪郭線(アウトライン)を表示するコンポーネント。
///
/// - 役割は「見た目の切り替え」のみに限定
/// - クリック処理などのゲームロジックは別コンポーネントに任せる
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider2D))]
public class OutlineShader : MonoBehaviour
{
    // 親にある SpriteRenderer を使う想定
    // (自分自身に SpriteRenderer がある場合は、インスペクターで自分を指定してもOK)
    [SerializeField]
    private SpriteRenderer targetSpriteRenderer;

    // ホバーしていない通常時のマテリアル
    [SerializeField]
    private Material defaultMaterial;

    // ホバー時に使うアウトライン付きマテリアル
    [SerializeField]
    private Material outlineMaterial;

    // マテリアル切り替えではなく、シェーダーのプロパティだけを変えたい場合に使うフラグ
    // 例: 1つのマテリアルで _OutlineEnabled という float プロパティを切り替える
    [Header("シェーダープロパティ制御(任意)")]
    [SerializeField]
    private bool useShaderPropertyToggle = false;

    // useShaderPropertyToggle が true のときに操作するプロパティ名
    [SerializeField]
    private string outlinePropertyName = "_OutlineEnabled";

    // useShaderPropertyToggle が true のときに ON / OFF に使う値
    [SerializeField]
    private float outlineOnValue = 1f;

    [SerializeField]
    private float outlineOffValue = 0f;

    // 現在ホバー中かどうか(他のコンポーネントから参照したい場合に備えて)
    public bool IsHovered { get; private set; }

    // 内部で元のマテリアルを退避しておく(defaultMaterial が未指定の場合用)
    private Material _originalMaterial;

    private void Reset()
    {
        // コンポーネント追加時に、よくある設定を自動で埋める

        // 親から SpriteRenderer を探して自動設定
        if (targetSpriteRenderer == null)
        {
            targetSpriteRenderer = GetComponentInParent<SpriteRenderer>();
        }

        // 自分自身に Collider2D が無い場合は、仮で BoxCollider2D を付けておく
        // (RequireComponent で Collider2D は必須にしているが、具体的な形状はここで補う)
        if (GetComponent<Collider2D>() == null)
        {
            gameObject.AddComponent<BoxCollider2D>();
        }
    }

    private void Awake()
    {
        // SpriteRenderer が未設定なら、親から自動取得を試みる
        if (targetSpriteRenderer == null)
        {
            targetSpriteRenderer = GetComponentInParent<SpriteRenderer>();
        }

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

        // 元のマテリアルを退避
        _originalMaterial = targetSpriteRenderer.sharedMaterial;

        // defaultMaterial が未指定なら、元のマテリアルを使う
        if (defaultMaterial == null)
        {
            defaultMaterial = _originalMaterial;
        }

        // outlineMaterial が未指定で、かつ useShaderPropertyToggle が false の場合は警告
        if (!useShaderPropertyToggle && outlineMaterial == null)
        {
            Debug.LogWarning(
                $"[OutlineShader] outlineMaterial が設定されていません。アウトライン用マテリアルをインスペクターで指定してください。オブジェクト: {name}",
                this
            );
        }
    }

    /// <summary>
    /// マウスカーソルがこのオブジェクトの Collider2D に入ったときに呼ばれる(Unityイベント)
    /// </summary>
    private void OnMouseEnter()
    {
        IsHovered = true;
        ApplyOutline(true);
    }

    /// <summary>
    /// マウスカーソルがこのオブジェクトの Collider2D から出たときに呼ばれる(Unityイベント)
    /// </summary>
    private void OnMouseExit()
    {
        IsHovered = false;
        ApplyOutline(false);
    }

    /// <summary>
    /// アウトラインのON/OFFを実際に適用するメソッド。
    /// - マテリアルを丸ごと差し替える方法
    /// - シェーダープロパティだけを切り替える方法
    /// の2通りに対応。
    /// </summary>
    /// <param name="enable">true ならアウトラインON、false ならOFF</param>
    private void ApplyOutline(bool enable)
    {
        if (targetSpriteRenderer == null)
        {
            return;
        }

        if (useShaderPropertyToggle)
        {
            // 1つのマテリアルでシェーダープロパティを切り替える場合
            var mat = targetSpriteRenderer.material;

            if (!string.IsNullOrEmpty(outlinePropertyName) && mat.HasProperty(outlinePropertyName))
            {
                mat.SetFloat(outlinePropertyName, enable ? outlineOnValue : outlineOffValue);
            }
            else
            {
                Debug.LogWarning(
                    $"[OutlineShader] マテリアルにプロパティ \"{outlinePropertyName}\" が存在しません。オブジェクト: {name}",
                    this
                );
            }
        }
        else
        {
            // マテリアルを丸ごと入れ替える場合
            if (enable)
            {
                if (outlineMaterial != null)
                {
                    targetSpriteRenderer.material = outlineMaterial;
                }
            }
            else
            {
                if (defaultMaterial != null)
                {
                    targetSpriteRenderer.material = defaultMaterial;
                }
                else if (_originalMaterial != null)
                {
                    targetSpriteRenderer.material = _originalMaterial;
                }
            }
        }
    }

    /// <summary>
    /// 他のスクリプトから明示的にアウトライン状態を切り替えたいとき用の公開メソッド。
    /// 例: 選択中のオブジェクトだけ常にアウトラインを出す、など。
    /// </summary>
    /// <param name="enable">true ならアウトラインON、false ならOFF</param>
    public void SetOutline(bool enable)
    {
        IsHovered = enable;
        ApplyOutline(enable);
    }
}

使い方の手順

ここでは「2Dゲームの宝箱オブジェクトに、マウスホバーで輪郭線を出す」例で説明します。
プレイヤー、敵、動く床などでも手順は同じです。

  1. スプライト付きのオブジェクトを用意する
    • Hierarchy で右クリック → 2D Object > Sprite などからオブジェクトを作成
    • 名前を ChestEnemy など、分かりやすいものに変更
    • SpriteRenderer に適当な画像を設定
  2. Collider2D を付ける
    • オブジェクトを選択し、インスペクターで Add Component ボタンを押す
    • BoxCollider2DCircleCollider2D を追加
    • 見た目に合わせてサイズを調整(クリック判定領域になります)
    • OutlineShader を追加したときに自動で BoxCollider2D を補う処理も書いていますが、
      自分で明示的に設定しておいた方が意図しやすいです。
  3. マテリアルを2種類用意する
    • Project ウィンドウで右クリック → Create > Material を2つ作成
      • Chest_Default(通常用)
      • Chest_Outline(アウトライン付き)
    • お好みのスプライト用シェーダーを設定し、
      Chest_Outline の方にはアウトラインが出るシェーダーや設定を適用
    • オブジェクトの SpriteRendererChest_Default を割り当てておく
  4. OutlineShader コンポーネントを追加して設定する
    • Project ウィンドウに OutlineShader.cs を保存(上記コードをそのまま貼り付け)
    • コンパイル後、対象オブジェクトを選択して Add ComponentOutlineShader を追加
    • インスペクターで以下のように設定
      • Target Sprite Renderer:親または自分自身の SpriteRenderer をドラッグ
      • Default MaterialChest_Default
      • Outline MaterialChest_Outline
      • Use Shader Property Togglefalse(マテリアル差し替え方式の場合)
    • ゲームを再生し、Scene/Game ビュー上でマウスをオブジェクトに乗せると、
      ホバー中だけマテリアルが切り替わり、輪郭線が表示されます。

もし「マテリアルを増やしたくない」「1つのマテリアルでアウトラインのオンオフをしたい」場合は、

  • シェーダー側に _OutlineEnabled などの float プロパティを用意しておく
  • OutlineShader のインスペクターで
    • Use Shader Property Toggletrue
    • Outline Property Name_OutlineEnabled
    • Outline On Value1
    • Outline Off Value0

と設定すれば、マテリアルはそのまま・プロパティだけ切り替える動きになります。


メリットと応用

OutlineShader コンポーネントを導入するメリットを整理してみます。

1. プレハブ化して量産しやすい

  • 宝箱、敵、インタラクティブなオブジェクトなどに OutlineShader を付けてプレハブ化しておけば、
    レベルデザイナーは シーンにドラッグ&ドロップするだけで「ホバーで光るオブジェクト」 を量産できます。
  • ホバー演出の仕様を変えたい場合も、OutlineShader だけを修正すれば、
    全てのプレハブに一括で反映されます。

2. 見た目とロジックを分離できる

  • クリック処理(宝箱を開ける・敵を選択するなど)は別スクリプトに分けておけるため、
    「見た目だけ変えたいのにゲームロジックを巻き込みたくない」という場合に非常に便利です。
  • 例えば、
    • ClickableObject:クリックされたら何かする
    • OutlineShader:ホバー時にアウトラインを出す

    のようにコンポーネントを分けておけば、別のオブジェクトでは

    • ホバー演出だけ欲しい(クリックはいらない)
    • クリックだけ欲しい(ホバー演出はいらない)

    といった組み合わせも簡単にできます。

3. マテリアル/シェーダーの切り替え戦略を柔軟に変えられる

  • 最初はマテリアル差し替え方式で実装し、後から「プロパティ切り替え方式」に変えたくなっても、
    コンポーネントのインスペクター設定を変えるだけで対応できます。
  • シェーダー名やプロパティ名もインスペクターから指定できるので、
    アーティスト側が自由にシェーダーを入れ替えても、コード側の変更は最小限で済みます。

4. 応用例:選択中オブジェクトの常時アウトライン表示

OutlineShader には SetOutline(bool enable) という公開メソッドを用意しているので、
例えば「現在選択中のユニットだけ常にアウトラインを出す」といった使い方も簡単です。

以下は、選択状態の変化に応じてアウトラインを制御する簡単な改造案です。


using UnityEngine;

/// <summary>
/// クリックで選択状態を切り替え、選択中は常にアウトラインを表示する例。
/// OutlineShader コンポーネントと組み合わせて使用する。
/// </summary>
[RequireComponent(typeof(OutlineShader))]
public class SelectableWithOutline : MonoBehaviour
{
    [SerializeField]
    private OutlineShader outlineShader;

    private bool _isSelected;

    private void Reset()
    {
        outlineShader = GetComponent<OutlineShader>();
    }

    private void Awake()
    {
        if (outlineShader == null)
        {
            outlineShader = GetComponent<OutlineShader>();
        }
    }

    private void OnMouseDown()
    {
        // 左クリックで選択状態をトグル
        _isSelected = !_isSelected;

        // 選択中は常にアウトラインON、非選択時はホバー状態に任せる
        outlineShader.SetOutline(_isSelected || outlineShader.IsHovered);
    }

    private void OnMouseEnter()
    {
        if (!_isSelected)
        {
            outlineShader.SetOutline(true);
        }
    }

    private void OnMouseExit()
    {
        if (!_isSelected)
        {
            outlineShader.SetOutline(false);
        }
    }
}

このように、アウトラインの制御ロジックを小さなコンポーネントに閉じ込めておくと、
「ホバー時だけ」「選択中だけ」「ホバー中かつ選択中は色を変える」など、
細かな仕様変更にも対応しやすくなります。

マウスホバー演出をすべて Update() に書き殴るのではなく、
OutlineShader のような小さなコンポーネントに切り出して、
プレハブ単位・機能単位で気持ちよく再利用していきましょう。