Unityを触り始めた頃って、つい「とりあえず Update() に全部書いちゃえ!」となりがちですよね。
プレイヤーの移動処理、入力処理、当たり判定、バフ処理…すべてを1つの巨大なスクリプトに押し込めると、最初は動いていても、あとから仕様変更が入ったときに地獄を見ます。

例えば「一定時間だけ移動速度を上げるバフ」を実装したいとき、移動スクリプトの中にタイマーや倍率、状態管理を全部書き足していくと、どんどん肥大化していきます。
そこで今回は、

  • 「速度上昇」という1つの責務だけを持つ
  • 「一定時間だけ、親の移動コンポーネントの speed を1.5倍にする」

という小さなコンポーネント SpeedBuff を用意して、移動処理とは分離して管理してみましょう。

【Unity】一時的な速度アップをコンポーネント化!「SpeedBuff」コンポーネント

ここでは、

  • 移動系コンポーネント」側に speed 変数がある
  • SpeedBuff は「親のどこかにある移動系コンポーネント」を探して、その speed を一定時間だけ倍率アップする

という前提で進めます。

移動系コンポーネントの実装は人それぞれですが、この記事では例として SimpleMover というシンプルな移動コンポーネントも一緒に載せておきます。
実際のプロジェクトでは、あなたの移動スクリプトの speed 変数を対象にすればOKです。


SpeedBuff コンポーネントのフルコード


using System.Collections;
using UnityEngine;

/// <summary>
/** 
 * 一定時間だけ、親階層にある「移動系コンポーネント」の speed を倍率アップするコンポーネント。
 * 
 * - 想定する「移動系コンポーネント」は、MonoBehaviour を継承し、
 *   public または [SerializeField] な float speed フィールド/プロパティを持っているもの。
 * - SpeedBuff 自身は「どのコンポーネントの speed を触るか」を
 *   SerializedField で指定できるようにしています。
 */
/// </summary>
public class SpeedBuff : MonoBehaviour
{
    [Header("バフ設定")]

    [SerializeField]
    [Tooltip("速度を何倍にするか(例: 1.5 で 1.5倍)")]
    private float speedMultiplier = 1.5f;

    [SerializeField]
    [Tooltip("バフの継続時間(秒)")]
    private float duration = 3f;

    [Header("対象コンポーネント設定")]

    [SerializeField]
    [Tooltip("親階層から検索する、移動系コンポーネント(speed を持つコンポーネント)")]
    private MonoBehaviour targetMovementComponent;

    // バフ適用前の元の speed 値を保持
    private float originalSpeed;

    // すでにバフ中かどうか
    private bool isBuffActive = false;

    // speed フィールド/プロパティへのアクセス用キャッシュ
    private System.Reflection.FieldInfo speedFieldInfo;
    private System.Reflection.PropertyInfo speedPropertyInfo;

    private Coroutine buffCoroutine;

    private void Reset()
    {
        // デフォルトでは、親階層から最初に見つかった MonoBehaviour を仮でセットしない。
        // 明示的な指定を推奨するため、ここでは何もしない。
    }

    private void Awake()
    {
        // targetMovementComponent が指定されていなければ、親階層から自動検出を試みる
        if (targetMovementComponent == null)
        {
            // 親階層にある MonoBehaviour を全て取得し、
            // その中から "speed" を持っているものを探す
            var behaviours = GetComponentsInParent<MonoBehaviour>(includeInactive: true);
            foreach (var behaviour in behaviours)
            {
                if (behaviour == this) continue; // 自分自身は除外

                if (TryCacheSpeedMember(behaviour))
                {
                    targetMovementComponent = behaviour;
                    break;
                }
            }

            if (targetMovementComponent == null)
            {
                Debug.LogWarning(
                    $"[SpeedBuff] 親階層に speed を持つコンポーネントが見つかりませんでした。オブジェクト: {name}",
                    this
                );
            }
        }
        else
        {
            // すでにインスペクタで指定されている場合は、そのコンポーネントから speed をキャッシュ
            if (!TryCacheSpeedMember(targetMovementComponent))
            {
                Debug.LogWarning(
                    $"[SpeedBuff] 指定されたコンポーネントに speed フィールド/プロパティが見つかりません。オブジェクト: {name}",
                    this
                );
            }
        }
    }

    /// <summary>
    /// バフを開始する。すでにバフ中の場合は、残り時間をリセットして再スタートする。
    /// 外部からも呼び出せるように public にしている。
    /// </summary>
    public void ActivateBuff()
    {
        if (targetMovementComponent == null)
        {
            Debug.LogWarning("[SpeedBuff] 対象の移動コンポーネントが設定されていないため、バフを開始できません。", this);
            return;
        }

        // speed メンバーへのアクセスがキャッシュされていない場合、再度トライ
        if (speedFieldInfo == null && speedPropertyInfo == null)
        {
            if (!TryCacheSpeedMember(targetMovementComponent))
            {
                Debug.LogWarning("[SpeedBuff] 対象コンポーネントに speed が存在しません。", this);
                return;
            }
        }

        // すでにコルーチンが動いていれば止める(時間リセットのため)
        if (buffCoroutine != null)
        {
            StopCoroutine(buffCoroutine);
        }

        buffCoroutine = StartCoroutine(BuffRoutine());
    }

    /// <summary>
    /// 実際に一定時間だけ speed を倍率アップするコルーチン。
    /// </summary>
    private IEnumerator BuffRoutine()
    {
        isBuffActive = true;

        // 現在の speed を取得して保存
        originalSpeed = GetSpeed();

        // 倍率を掛けた値を設定
        float buffedSpeed = originalSpeed * speedMultiplier;
        SetSpeed(buffedSpeed);

        // duration 秒だけ待つ
        float timer = 0f;
        while (timer < duration)
        {
            timer += Time.deltaTime;
            yield return null;
        }

        // 元の speed に戻す
        SetSpeed(originalSpeed);

        isBuffActive = false;
        buffCoroutine = null;
    }

    /// <summary>
    /// Reflection を使って、targetMovementComponent が持つ "speed" フィールドまたはプロパティをキャッシュする。
    /// </summary>
    private bool TryCacheSpeedMember(MonoBehaviour movementComponent)
    {
        var type = movementComponent.GetType();

        // public / non-public のインスタンスフィールドを検索
        speedFieldInfo = type.GetField(
            "speed",
            System.Reflection.BindingFlags.Instance |
            System.Reflection.BindingFlags.Public |
            System.Reflection.BindingFlags.NonPublic
        );

        if (speedFieldInfo != null && speedFieldInfo.FieldType == typeof(float))
        {
            speedPropertyInfo = null;
            return true;
        }

        // フィールドがなければ、プロパティを検索
        speedPropertyInfo = type.GetProperty(
            "speed",
            System.Reflection.BindingFlags.Instance |
            System.Reflection.BindingFlags.Public |
            System.Reflection.BindingFlags.NonPublic
        );

        if (speedPropertyInfo != null && speedPropertyInfo.PropertyType == typeof(float))
        {
            speedFieldInfo = null;
            return true;
        }

        // どちらも見つからなければ失敗
        speedFieldInfo = null;
        speedPropertyInfo = null;
        return false;
    }

    /// <summary>
    /// キャッシュしている "speed" から現在値を取得する。
    /// </summary>
    private float GetSpeed()
    {
        if (speedFieldInfo != null)
        {
            return (float)speedFieldInfo.GetValue(targetMovementComponent);
        }

        if (speedPropertyInfo != null)
        {
            return (float)speedPropertyInfo.GetValue(targetMovementComponent);
        }

        Debug.LogError("[SpeedBuff] speed メンバーへの参照が失われています。", this);
        return 0f;
    }

    /// <summary>
    /// キャッシュしている "speed" に値を設定する。
    /// </summary>
    private void SetSpeed(float value)
    {
        if (speedFieldInfo != null)
        {
            speedFieldInfo.SetValue(targetMovementComponent, value);
            return;
        }

        if (speedPropertyInfo != null)
        {
            speedPropertyInfo.SetValue(targetMovementComponent, value);
            return;
        }

        Debug.LogError("[SpeedBuff] speed メンバーへの参照が失われています。", this);
    }

    /// <summary>
    /// デバッグ用途:インスペクタからボタン風に呼び出すためのメソッド。
    /// 必要なければ削除してOK。
    /// </summary>
    [ContextMenu("Activate Buff")]
    private void ContextMenuActivateBuff()
    {
        ActivateBuff();
    }
}

参考:シンプルな移動コンポーネント例(SimpleMover)

SpeedBuff の動作確認用として、speed を持つ簡単な移動コンポーネントを載せておきます。
キーボード入力で 2D/3D 共通の平面移動をするだけのシンプルな例です。


using UnityEngine;

/// <summary>
/// 非常にシンプルな移動コンポーネントの例。
/// - 水平・垂直入力に応じて Transform を移動させるだけ。
/// - SpeedBuff からは、このコンポーネントの speed が書き換えられる。
/// </summary>
public class SimpleMover : MonoBehaviour
{
    [SerializeField]
    [Tooltip("移動速度(1秒あたりの移動量)")]
    private float speed = 5f;

    // SpeedBuff から Reflection 経由でアクセスされるため、
    // speed は private でも OK(SerializeField であればインスペクタから編集可能)。
    // ただし、外部から直接触りたい場合は public プロパティを用意しても良い。

    private void Update()
    {
        // 水平方向(A, D / ←, →)
        float x = Input.GetAxisRaw("Horizontal");
        // 垂直方向(W, S / ↑, ↓)
        float z = Input.GetAxisRaw("Vertical");

        Vector3 direction = new Vector3(x, 0f, z).normalized;

        if (direction.sqrMagnitude > 0f)
        {
            transform.position += direction * speed * Time.deltaTime;
        }
    }
}

使い方の手順

例として、「プレイヤーに一時的な速度アップアイテムを取らせる」ケースで説明します。

手順① プレイヤーに移動コンポーネントを付ける

  1. Hierarchy でプレイヤーの GameObject(例: Player)を選択します。
  2. Add Component ボタンから、上の例の SimpleMover またはあなたの移動スクリプトを追加します。
  3. インスペクタで speed の値を調整して、通常時の移動速度を決めておきます。

手順② SpeedBuff コンポーネントを追加する

  1. 同じく Player オブジェクト、もしくはその子オブジェクト(例: Player/Status)を選択します。
  2. Add Component から SpeedBuff を追加します。
  3. Target Movement Component に、移動スクリプト(例: SimpleMover)をドラッグ&ドロップで割り当てます。
    ※ 未指定でも、親階層から自動検出を試みますが、明示的に指定しておく方が安全です。
  4. Speed Multiplier に 1.5、Duration に 3 など好みの値を設定します。

手順③ バフを発動するトリガーを作る(例: アイテム)

「バフアイテム」によって SpeedBuff.ActivateBuff() を呼び出してみましょう。
以下は、プレイヤーが触れたらバフを発動して自壊するアイテムの例です。


using UnityEngine;

/// <summary>
/// プレイヤーが触れると、プレイヤーの SpeedBuff を発動させるアイテムの例。
/// </summary>
[RequireComponent(typeof(Collider))]
public class SpeedBuffItem : MonoBehaviour
{
    [SerializeField]
    [Tooltip("バフを付与する対象プレイヤーのルートオブジェクト")]
    private GameObject playerRoot;

    private SpeedBuff playerSpeedBuff;

    private void Awake()
    {
        if (playerRoot != null)
        {
            // プレイヤーのどこかにある SpeedBuff を取得
            playerSpeedBuff = playerRoot.GetComponentInChildren<SpeedBuff&gt();
        }

        // 当たり判定はトリガーにしておく
        var col = GetComponent<Collider>();
        col.isTrigger = true;
    }

    private void OnTriggerEnter(Collider other)
    {
        // プレイヤーが触れたら発動する想定
        if (other.gameObject == playerRoot)
        {
            if (playerSpeedBuff != null)
            {
                playerSpeedBuff.ActivateBuff();
            }
            else
            {
                Debug.LogWarning("[SpeedBuffItem] Player に SpeedBuff が見つかりません。", this);
            }

            // アイテム自体は消える
            Destroy(gameObject);
        }
    }
}

このスクリプトを「SpeedItem」などの GameObject に付け、playerRoot にプレイヤーのルートオブジェクトを指定すれば、
プレイヤーがアイテムに触れたときに 一定時間だけ speed が1.5倍 になります。

手順④ 他の例:敵の一時的な強化や、動く床の速度アップにも

  • 敵キャラクター
    敵の移動コンポーネント(パトロールAIなど)に speed があるなら、その敵の子に SpeedBuff を付けておき、
    「怒り状態」になったときに ActivateBuff() を呼ぶことで、一時的に高速化させることができます。
  • 動く床
    動く床の移動スクリプトに speed があれば、ギミック発動中だけ床の速度を上げる、といった演出も簡単です。

メリットと応用

SpeedBuff をコンポーネントとして分離しておくと、次のようなメリットがあります。

  • 移動ロジックとバフロジックが分離される
    移動コンポーネントは「どう動くか」だけに集中でき、
    SpeedBuff は「一時的に speed をいじる」だけに責務を絞れます。
    結果として、どちらのコードも読みやすく、テストしやすくなります。
  • プレハブの再利用性が高まる
    プレイヤー、敵、動く床など、「speed を持つオブジェクト」なら何にでも同じ SpeedBuff を使い回せます。
    レベルデザイン時には、シーン上のオブジェクトにペタペタと SpeedBuff を貼って、倍率と時間だけ調整すればOKです。
  • バフの組み合わせがしやすくなる
    将来的に「攻撃力バフ」「ジャンプ力バフ」などを追加したくなっても、
    それぞれ専用のコンポーネントとして増やしていけば、巨大なステータス管理クラスを作らずに済みます。

改造案:バフ終了時にコールバックを飛ばす

例えば「バフが切れたタイミングでエフェクトを消したい」「UIのアイコンを消したい」といったケースでは、
バフ終了時にイベントを飛ばせるようにすると便利です。

SpeedBuff に次のようなメソッドとイベントフィールドを追加してみましょう(抜粋):


using UnityEngine.Events;

// SpeedBuff クラスのメンバとして追加
[SerializeField]
[Tooltip("バフが終了したときに呼ばれるイベント")]
private UnityEvent onBuffEnded;

/// <summary>
/// バフ終了時に呼び出すヘルパー。
/// </summary>
private void InvokeBuffEndEvent()
{
    onBuffEnded?.Invoke();
}

そして BuffRoutine() の最後、SetSpeed(originalSpeed); の直後に次を追加します。


SetSpeed(originalSpeed);
InvokeBuffEndEvent();

こうしておけば、インスペクタ上で onBuffEnded にエフェクト制御用のメソッドや UI 更新メソッドを登録するだけで、
バフ終了タイミングに合わせた演出を簡単に追加できます。

このように、小さなコンポーネント単位で責務を分けておくと、
後からの仕様変更や演出追加にも柔軟に対応しやすくなります。ぜひ、自分のプロジェクトでもバフ系ロジックをコンポーネント化してみてください。