Unityを触り始めた頃は、つい「とりあえず全部Updateに書いてしまう」コードになりがちですよね。
プレイヤーの入力、移動、UI更新、エフェクト制御、BGMのフェード…すべてを1つのスクリプトのUpdateに詰め込んでしまうと、

  • どの処理がどの演出に関係しているのか分かりづらい
  • ちょっとした調整のたびに巨大なスクリプトを開く必要がある
  • Prefab化したときに、不要な処理まで一緒についてきてしまう

という「Godクラス」状態になってしまいます。

この記事では、「発光演出だけ」を責務とする小さなコンポーネントとして、
WorldEnvironment の Glow 強度をサイン波で揺らしてネオンのような明滅を作る GlowPulse を用意します。

レベルデザイナーやエフェクト担当が、任意のライト・看板・オブジェクトにポン付けできる発光演出コンポーネントを目指しましょう。

【Unity】ネオン看板を一瞬でそれっぽく!「GlowPulse」コンポーネント

コンポーネントの概要

GlowPulse は、以下のようなことを自動でやってくれる MonoBehaviour です。

  • 指定した WorldEnvironment の Glow 強度を、時間経過とともにサイン波で揺らす
  • 最小値・最大値・速度・位相オフセットをインスペクターから調整できる
  • エディタ上でのプレビューをしやすくするため、Play中にリアルタイムで値が変わる
  • Glow を制御する対象を SerializeField で柔軟に差し替え可能

「環境全体の Glow をゆらゆらさせて、ネオン街っぽい雰囲気を出したい」ときに、その役割だけを切り出して使えるようにします。

フルコード


using UnityEngine;

/// <summary>
/// WorldEnvironment の Glow 強度をサイン波で揺らし、
/// ネオン看板のような明滅を作るコンポーネント。
/// 
/// ・Glow の最小値 / 最大値
/// ・揺れの速度(周波数)
/// ・ランダムな位相オフセット
/// などをインスペクターから調整できます。
/// 
/// ※このスクリプトは、Glow を制御できる "WorldEnvironment" 風の
///   コンポーネントを対象にしています。
///   ここではシンプルなインターフェースとして IGlowTarget を定義し、
///   それを通じて Glow 値を操作します。
/// </summary>
public class GlowPulse : MonoBehaviour
{
    /// <summary>
    /// Glow を制御する対象。
    /// 例: シーンルートにある WorldEnvironment、
///     あるいは独自の Glow コンポーネントなど。
    /// </summary>
    [SerializeField]
    private MonoBehaviour glowTargetBehaviour;

    /// <summary>
    /// Glow の最小強度。
    /// </summary>
    [SerializeField, Min(0f)]
    private float minGlow = 0.5f;

    /// <summary>
    /// Glow の最大強度。
    /// </summary>
    [SerializeField, Min(0f)]
    private float maxGlow = 2.0f;

    /// <summary>
    /// サイン波の速度(周波数)。値が大きいほど明滅が速くなります。
    /// </summary>
    [SerializeField, Min(0f)]
    private float pulseSpeed = 1.0f;

    /// <summary>
    /// サイン波の位相オフセット(ラジアン)。
    /// 複数の GlowPulse をずらして点滅させたいときに使います。
    /// </summary>
    [SerializeField]
    private float phaseOffset = 0f;

    /// <summary>
    /// 起動時に位相をランダムにずらすかどうか。
    /// ネオン看板を複数並べるときに、同じタイミングで
    /// 点滅しないようにしたい場合に便利です。
    /// </summary>
    [SerializeField]
    private bool randomizePhaseOnStart = true;

    /// <summary>
    /// サイン波のタイマー。
    /// </summary>
    private float time;

    /// <summary>
    /// 実際に Glow 値を操作するためのインターフェース。
    /// </summary>
    private IGlowTarget glowTarget;

    /// <summary>
    /// Glow を制御するためのシンプルなインターフェース。
    /// WorldEnvironment など、Glow を持つコンポーネント側で
    /// このインターフェースを実装しておけば、GlowPulse から
    /// 同じように操作できるようになります。
    /// </summary>
    public interface IGlowTarget
    {
        float GlowIntensity { get; set; }
    }

    private void Awake()
    {
        // glowTargetBehaviour に IGlowTarget 実装がアタッチされているかチェック
        if (glowTargetBehaviour != null)
        {
            glowTarget = glowTargetBehaviour as IGlowTarget;
            if (glowTarget == null)
            {
                Debug.LogError(
                    $"[GlowPulse] 指定された glowTargetBehaviour に IGlowTarget が実装されていません: {glowTargetBehaviour.name}",
                    this
                );
            }
        }
        else
        {
            Debug.LogError("[GlowPulse] glowTargetBehaviour が設定されていません。インスペクターで設定してください。", this);
        }

        // 最小値と最大値の関係がおかしい場合は自動で補正
        if (maxGlow < minGlow)
        {
            float tmp = maxGlow;
            maxGlow = minGlow;
            minGlow = tmp;
        }

        // ランダム位相オフセット
        if (randomizePhaseOnStart)
        {
            // 0〜2π の範囲でランダムにオフセット
            phaseOffset += Random.Range(0f, Mathf.PI * 2f);
        }
    }

    private void Update()
    {
        if (glowTarget == null)
        {
            // 対象が設定されていなければ何もしない
            return;
        }

        // 経過時間を加算
        time += Time.deltaTime * pulseSpeed;

        // サイン波を生成(-1〜1)
        float sin = Mathf.Sin(time + phaseOffset);

        // 0〜1 に正規化
        float normalized = (sin + 1f) * 0.5f;

        // 最小値〜最大値の範囲にマッピング
        float glow = Mathf.Lerp(minGlow, maxGlow, normalized);

        // Glow 値を反映
        glowTarget.GlowIntensity = glow;
    }

    /// <summary>
    /// インスペクターから動作をプレビューしやすくするための
    /// デバッグ用メソッド。
    /// 例えば、エディタ拡張から呼び出して値を一気に適用したりできます。
    /// </summary>
    public void SetInstantGlow(float normalizedValue)
    {
        if (glowTarget == null)
        {
            return;
        }

        // 0〜1 にクランプ
        normalizedValue = Mathf.Clamp01(normalizedValue);

        float glow = Mathf.Lerp(minGlow, maxGlow, normalizedValue);
        glowTarget.GlowIntensity = glow;
    }
}

/// <summary>
/// サンプル実装:
/// 環境全体の Glow を管理する簡易 WorldEnvironment 風コンポーネント。
/// 実際のプロジェクトでは、ポストプロセスやレンダーパイプラインの
/// パラメータに応じて実装を差し替えてください。
/// 
/// ここでは、単純に "glowIntensity" を保持しているだけですが、
/// シェーダーのグローバルプロパティに渡したり、
/// ポストプロセスの設定に反映したりすることを想定しています。
/// </summary>
public class SimpleWorldEnvironment : MonoBehaviour, GlowPulse.IGlowTarget
{
    [SerializeField, Min(0f)]
    private float glowIntensity = 1.0f;

    /// <summary>
    /// GlowPulse からアクセスされるプロパティ。
    /// </summary>
    public float GlowIntensity
    {
        get => glowIntensity;
        set
        {
            glowIntensity = Mathf.Max(0f, value);

            // 実際のプロジェクトでは、ここでシェーダーや
            // ポストプロセスに値を渡す処理を書きます。
            //
            // 例:
            // Shader.SetGlobalFloat("_GlobalGlowIntensity", glowIntensity);
        }
    }

    private void OnValidate()
    {
        // インスペクターで値をいじったときも 0 未満にならないように補正
        glowIntensity = Mathf.Max(0f, glowIntensity);

        // エディタ上での簡易プレビュー用途で、
        // ここで Shader.SetGlobalFloat を呼ぶのもアリです。
    }
}

使い方の手順

ここでは、シーン全体のネオン感を出すために、環境の Glow を脈動させる例で説明します。

  1. ① 環境用の GameObject を用意する
    シーンのルートに WorldEnvironmentRoot などの空の GameObject を作成します。
    そこに SimpleWorldEnvironment コンポーネントをアタッチします。
  2. ② GlowPulse をアタッチする
    同じ WorldEnvironmentRoot か、別の管理用 GameObject に GlowPulse を追加します。
    • Glow Target Behaviour に、先ほどアタッチした SimpleWorldEnvironment をドラッグ&ドロップ
    • Min Glow に 0.5、Max Glow に 2.0 など好みの値を設定
    • Pulse Speed を 0.5〜2.0 あたりで調整(値が大きいほど点滅が速い)
    • 複数の GlowPulse を使うなら Randomize Phase On Start をオンにしておくと自然なズレが出ます
  3. ③ 実際の見た目に Glow を反映する
    SimpleWorldEnvironmentGlowIntensity セッターの中で、
    使用しているレンダーパイプラインに応じて実際のポストプロセスやシェーダーに値を渡します。
    • 例1: Shader.SetGlobalFloat("_GlobalGlowIntensity", glowIntensity); として、ネオン看板用シェーダーで参照
    • 例2: URP/HDRP のポストプロセス Volume を参照し、Bloom の Intensity を glowIntensity に合わせて変更

    この記事内のサンプルでは実際のポストプロセス連携はコメントに留めていますが、
    自分のプロジェクトのレンダリング設定に合わせてここを書き換えてください。

  4. ④ プレイヤーやオブジェクトごとの応用例
    • プレイヤーが必殺技を使うときだけ GlowPulse を有効化
      GlowPulse コンポーネントの enabled を true/false で切り替えることで、
      発動中だけ環境が「ドクドク」脈動するような演出ができます。
    • 敵ボスの足元の魔法陣だけを脈動させる
      魔法陣用の SimpleWorldEnvironment(もしくは IGlowTarget 実装)をプレハブに仕込んでおき、
      その上に GlowPulse をアタッチ。ボスごとに pulseSpeedmin/maxGlow を変えると、
      強さの違いを簡単に演出できます。
    • 動く床やエレベーターの縁を光らせる
      床オブジェクトに IGlowTarget 実装コンポーネント+GlowPulse を追加しておけば、
      動く床のプレハブをシーンに置くだけで「それっぽいネオン床」が量産できます。

メリットと応用

GlowPulse を使うメリットは、「発光のゆらぎ」という責務だけに絞ったコンポーネントになっている点です。

  • Prefab 管理が楽
    ネオン看板用のプレハブに SimpleWorldEnvironment + GlowPulse を仕込んでおけば、
    シーンに配置するだけで同じ発光演出が使い回せます。
    演出の調整も、プレハブのインスペクターで minGlow / maxGlow / pulseSpeed をいじるだけです。
  • 責務が明確で、他の演出と干渉しない
    「Glow をサイン波で揺らす」という一点に責務を絞っているので、
    カメラ揺れやサウンド再生など別の演出とは疎結合にできます。
    何か問題が起きたときも、このコンポーネントだけを見ればよいのでデバッグが楽です。
  • レベルデザインの自由度が上がる
    IGlowTarget インターフェースさえ実装しておけば、
    「環境全体」「個別の看板」「敵の目」「床の縁」など、
    どこにでも同じ GlowPulse を再利用できます。

応用として、一時的に Glow の振れ幅を強くする関数を追加して、
イベント時にだけ派手に光らせる、といったことも簡単にできます。


    /// <summary>
    /// 一定時間だけ Glow の振れ幅を強くする簡易ブースト。
    /// 例えば、プレイヤーがアイテムを取得した瞬間などに呼び出すことで
    /// 一時的に派手なネオン演出にできます。
    /// </summary>
    public void BoostGlowForSeconds(float boostAmount, float duration)
    {
        // コルーチンで一時的に maxGlow を上書きする例
        StartCoroutine(BoostGlowCoroutine(boostAmount, duration));
    }

    private System.Collections.IEnumerator BoostGlowCoroutine(float boostAmount, float duration)
    {
        float originalMax = maxGlow;
        maxGlow += boostAmount;

        float timer = 0f;
        while (timer < duration)
        {
            timer += Time.deltaTime;
            yield return null;
        }

        maxGlow = originalMax;
    }

このように、小さなコンポーネントとして分割しておくと、
「Glow をちょっと盛り上げたい」「この敵だけ点滅を速くしたい」といった要望にも
最小限のコード変更で応えられるようになります。