Unityを触り始めた頃って、つい Update() の中に「入力処理」「移動」「エフェクト」「UI更新」「時間の演出」…と、全部を詰め込みがちですよね。
特に「スロー演出(スローモーション)」を入れたくなったときに、Time.timeScale をあちこちから直接いじり始めると、

  • どこで timeScale が書き換わっているか分からなくなる
  • 一時停止や演出用スローがバッティングしてバグる
  • プレイヤーや敵のスクリプトが Time.timeScale 依存だらけになる

といった「時間管理のスパゲッティコード」に陥りやすいです。

そこでこの記事では、「スロー演出」という機能だけに責任を絞ったコンポーネント
SlowMotion(スロー化トリガー) を用意して、
「特定条件でだけ Time.timeScale を変更する」 役割を1つの小さなスクリプトに閉じ込めてしまいましょう。

【Unity】条件付きスローモーションをスマートに管理!「SlowMotion」コンポーネント

ここでは Unity 6(C#)で使える、トリガー発火型のスローモーションコンポーネントを作ります。

  • コライダーに入ったときに一時的にスローにする
  • 任意のスクリプトから「ここでスローして!」と呼び出す
  • 一定時間が経ったら自動で元の速度に戻す

といったことが簡単にできるようになります。
ゲーム全体の時間は Time.timeScale で制御しつつ、
その操作は SlowMotion コンポーネントに一本化して管理する、というイメージですね。


フルコード:SlowMotion.cs


using UnityEngine;
using System.Collections;

/// <summary>
/// 特定条件で Time.timeScale をスロー化するトリガーコンポーネント。
/// - コライダー侵入時に自動スロー
/// - 任意のスクリプトからの手動スロー
/// の両方に対応。
/// 
/// ※このコンポーネント自身が「時間管理の窓口」になる想定です。
///   他のスクリプトから直接 Time.timeScale をいじるのは極力避けましょう。
/// </summary>
[DisallowMultipleComponent]
public class SlowMotion : MonoBehaviour
{
    // --- 基本設定 ---

    [Header("基本スロー設定")]

    [Tooltip("スロー中の Time.timeScale の値(0.0〜1.0 推奨)")]
    [SerializeField] private float slowTimeScale = 0.2f;

    [Tooltip("スローを維持する実時間(秒)。0以下なら自動復帰しない")]
    [SerializeField] private float slowDurationRealTime = 1.0f;

    [Tooltip("スロー開始時のフェード時間(秒)。0なら即時変更")]
    [SerializeField] private float slowInFadeDuration = 0.1f;

    [Tooltip("スロー終了時のフェード時間(秒)。0なら即時復帰")]
    [SerializeField] private float slowOutFadeDuration = 0.2f;


    // --- トリガー設定 ---

    [Header("トリガー設定")]

    [Tooltip("このコンポーネントの Collider が Trigger のとき、侵入で自動スローするか")]
    [SerializeField] private bool useTrigger = true;

    [Tooltip("トリガー対象とするタグ。空文字ならタグを問わず発火")]
    [SerializeField] private string targetTag = "Player";

    [Tooltip("同じオブジェクトが再度トリガーに入ったとき、前のスローを上書きするか")]
    [SerializeField] private bool allowRetriggerWhileSlowing = true;


    // --- 内部状態 ---

    /// <summary>スロー前の Time.timeScale を記録しておく(復帰用)</summary>
    private float originalTimeScale = 1.0f;

    /// <summary>現在スロー中かどうか</summary>
    private bool isSlowing = false;

    /// <summary>現在動作中のスロー用コルーチン</summary>
    private Coroutine slowRoutine = null;


    // ================================
    //  パブリック API
    // ================================

    /// <summary>
    /// 外部スクリプトから明示的にスローを開始するためのメソッド。
    /// 例: 弾が敵にヒットした瞬間にスローしたい、など。
    /// </summary>
    /// <param name="customDurationRealTime">スロー時間(実時間秒)。nullならインスペクタの設定値を使用。</param>
    public void TriggerSlowMotion(float? customDurationRealTime = null)
    {
        float duration = customDurationRealTime ?? slowDurationRealTime;

        // すでにスロー中の場合の扱い
        if (isSlowing)
        {
            if (!allowRetriggerWhileSlowing)
            {
                // 上書き禁止なら何もしない
                return;
            }

            // 上書き許可なら、現在のスロー処理を止めて再スタート
            if (slowRoutine != null)
            {
                StopCoroutine(slowRoutine);
            }
        }

        slowRoutine = StartCoroutine(SlowMotionRoutine(duration));
    }

    /// <summary>
    /// 外部から強制的にスローを解除したい場合に呼ぶ。
    /// 例: ポーズメニュー表示時に通常速度に戻したい、など。
    /// </summary>
    public void ForceResetTimeScale()
    {
        // 実行中のスロー処理を停止
        if (slowRoutine != null)
        {
            StopCoroutine(slowRoutine);
            slowRoutine = null;
        }

        // 即時復帰
        Time.timeScale = originalTimeScale;
        isSlowing = false;
    }


    // ================================
    //  Unity イベント
    // ================================

    private void Awake()
    {
        // 起動時点の timeScale を記録しておく
        // (基本的には 1.0 想定だが、タイトルから遷移するなどで変わっている可能性も考慮)
        originalTimeScale = Time.timeScale;
    }

    private void OnDisable()
    {
        // コンポーネントが無効化されたとき、スロー中なら元に戻しておく
        if (isSlowing)
        {
            Time.timeScale = originalTimeScale;
            isSlowing = false;
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (!useTrigger)
        {
            return;
        }

        // タグ指定が空なら無条件発火
        if (!string.IsNullOrEmpty(targetTag))
        {
            if (!other.CompareTag(targetTag))
            {
                return;
            }
        }

        TriggerSlowMotion();
    }


    // ================================
    //  スロー本体コルーチン
    // ================================

    /// <summary>
    /// 実時間ベースでスローを行うコルーチン。
    /// Time.timeScale を変更しているため、WaitForSecondsRealtime を使用するのがポイント。
    /// </summary>
    private IEnumerator SlowMotionRoutine(float durationRealTime)
    {
        isSlowing = true;

        // 現在の timeScale を記録(他のシステムが変更している可能性もあるため、その時点の値を採用)
        originalTimeScale = Time.timeScale;

        // --- フェードイン(通常速度 → スロー) ---
        if (slowInFadeDuration > 0f)
        {
            float elapsed = 0f;
            float startScale = Time.timeScale;
            float targetScale = Mathf.Max(0.0f, slowTimeScale);

            while (elapsed < slowInFadeDuration)
            {
                elapsed += Time.unscaledDeltaTime; // 実時間ベース
                float t = Mathf.Clamp01(elapsed / slowInFadeDuration);
                Time.timeScale = Mathf.Lerp(startScale, targetScale, t);
                yield return null;
            }

            Time.timeScale = Mathf.Max(0.0f, slowTimeScale);
        }
        else
        {
            // 即時スロー
            Time.timeScale = Mathf.Max(0.0f, slowTimeScale);
        }

        // --- スロー維持 ---
        if (durationRealTime > 0f)
        {
            // 実時間ベースで待機(timeScale の影響を受けない)
            yield return new WaitForSecondsRealtime(durationRealTime);
        }
        else
        {
            // 0以下の場合は「維持のみ」。ここでコルーチンを終わらせるとフェードアウトが走らないので、
            // そのままフェードアウト処理に進む(外部から ForceResetTimeScale で戻す前提など)。
        }

        // --- フェードアウト(スロー → 元の速度) ---
        if (slowOutFadeDuration > 0f)
        {
            float elapsed = 0f;
            float startScale = Time.timeScale;
            float targetScale = originalTimeScale;

            while (elapsed < slowOutFadeDuration)
            {
                elapsed += Time.unscaledDeltaTime; // 実時間ベース
                float t = Mathf.Clamp01(elapsed / slowOutFadeDuration);
                Time.timeScale = Mathf.Lerp(startScale, targetScale, t);
                yield return null;
            }

            Time.timeScale = originalTimeScale;
        }
        else
        {
            // 即時復帰
            Time.timeScale = originalTimeScale;
        }

        isSlowing = false;
        slowRoutine = null;
    }
}

使い方の手順

ここでは代表的な3パターンを例にして使い方を説明します。

手順①:コンポーネントを用意する

  1. SlowMotion.cs をプロジェクトの Scripts フォルダなどに保存します。
  2. Unity エディタでコンパイルが終わると、SlowMotion コンポーネントが追加できるようになります。

手順②:トリガーゾーンでスロー演出(例:プレイヤーが「集中ゾーン」に入ったとき)

  1. 空の GameObject を作成し、名前を SlowZone などにします。
  2. Box Collider など任意の Collider を追加し、Is Trigger にチェックを入れます。
  3. 同じオブジェクトに SlowMotion コンポーネントを追加します。
  4. インスペクタで以下のように設定します:
    • Slow Time Scale0.2(好みで調整)
    • Slow Duration Real Time1.0(1秒だけスロー)
    • Slow In Fade Duration0.1
    • Slow Out Fade Duration0.2
    • Use Trigger:チェックを入れる
    • Target TagPlayer
  5. プレイヤーオブジェクトのタグを Player に設定します。

これで、プレイヤーが SlowZone のコライダーに侵入した瞬間に、
ゲーム全体が一時的にスローになり、1秒後に自動で元の速度に戻ります。

手順③:ヒット演出でスロー(例:敵が倒れた瞬間にスロー)

次は、敵が倒れた瞬間にだけスローを発動する例です。
敵のプレハブに SlowMotion をアタッチしておき、HPが0になったときに呼び出します。


using UnityEngine;

public class EnemyHealth : MonoBehaviour
{
    [SerializeField] private int maxHp = 3;
    private int currentHp;

    // 同じオブジェクトにアタッチした SlowMotion を取得
    [SerializeField] private SlowMotion slowMotion;

    private void Awake()
    {
        currentHp = maxHp;

        // インスペクタでアサインし忘れた場合の保険
        if (slowMotion == null)
        {
            slowMotion = GetComponent<SlowMotion>();
        }
    }

    public void TakeDamage(int damage)
    {
        currentHp -= damage;

        if (currentHp <= 0)
        {
            OnDead();
        }
    }

    private void OnDead()
    {
        // 死亡時にだけスロー演出を入れる
        if (slowMotion != null)
        {
            // 0.5秒だけスローしたい場合
            slowMotion.TriggerSlowMotion(0.5f);
        }

        // ここでアニメーション再生やエフェクトなど
        // Destroy(gameObject, 1.0f); などもOK
    }
}

このように「ダメージ処理」と「時間制御」を分けておくと、
スロー演出の有無やパラメータ調整をコンポーネント単位で簡単に変更できるようになります。

手順④:動く床やギミックの「スローエリア」として使う

例えば、ステージの一部だけ時間がゆっくり進む「スローエリア」を作りたい場合:

  1. ステージのそのエリアに合わせて大きな Box Collider(Is Trigger)を配置。
  2. そのオブジェクトに SlowMotion をアタッチ。
  3. Use Trigger をオン、Target TagPlayer をセット。
  4. Slow Duration Real Time0 に設定(=自動復帰しない)。

この場合、

  • プレイヤーが侵入した瞬間にスロー開始
  • スローは維持され続ける(自動では戻らない)
  • エリアを抜けたときに別のスクリプトから ForceResetTimeScale() を呼ぶ

といった形で、「エリア滞在中だけスロー」という表現も作れます。


メリットと応用

SlowMotion コンポーネントを用意しておくと、

  • Time.timeScale をいじる場所が一箇所にまとまるので、バグの原因を追いやすい
  • 「プレイヤーの攻撃ヒット時だけ」「特定ゾーンに入ったときだけ」など、スローの条件を各コンポーネントに分離できる
  • プレハブに仕込んでおけば、レベルデザイン時に配置するだけでスロー演出が生える
  • フェードイン/アウト時間をインスペクタから調整できるので、ゲーム全体の「時間演出の統一感」を出しやすい

特にレベルデザイン面では、

  • 「ここで敵が大量に出るから、ゾーンに SlowMotion を置いておこう」
  • 「ボスの必殺技エリアだけ時間がゆっくり流れるようにしたい」

といった要望に対して、スクリプトを書き直さずにプレハブとコンポーネントの組み合わせだけで対応できるのが大きなメリットですね。

改造案:入力に応じて「集中モード」スローを発動する

最後に、プレイヤーがボタンを押したときだけ一時的にスローになる「集中モード」を追加する改造案です。
新しいスクリプトを作り、SlowMotion と同じオブジェクト(またはプレイヤー)にアタッチして使います。


using UnityEngine;

/// <summary>
/// キー入力で SlowMotion を発火する簡易サンプル。
/// 実運用では Input System に置き換えてもOKです。
/// </summary>
public class SlowMotionInputTrigger : MonoBehaviour
{
    [SerializeField] private SlowMotion slowMotion;

    [Tooltip("スローを発動するキー")]
    [SerializeField] private KeyCode triggerKey = KeyCode.LeftShift;

    [Tooltip("連打を防ぐためのクールダウン時間(秒)")]
    [SerializeField] private float cooldown = 2.0f;

    private float lastTriggerTime = -999f;

    private void Awake()
    {
        if (slowMotion == null)
        {
            slowMotion = GetComponent<SlowMotion>();
        }
    }

    private void Update()
    {
        // クールダウン中なら無視
        if (Time.unscaledTime - lastTriggerTime < cooldown)
        {
            return;
        }

        if (Input.GetKeyDown(triggerKey) && slowMotion != null)
        {
            // インスペクタ設定の時間でスローを発動
            slowMotion.TriggerSlowMotion();
            lastTriggerTime = Time.unscaledTime;
        }
    }
}

このように、小さなコンポーネントを組み合わせていくと、
巨大な God クラスに頼らずに、柔軟で保守しやすい「時間演出システム」を組み立てられます。
ぜひ自分のプロジェクトに合わせてカスタマイズしてみてください。