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;
}
}
}
使い方の手順
例として、「プレイヤーに一時的な速度アップアイテムを取らせる」ケースで説明します。
手順① プレイヤーに移動コンポーネントを付ける
- Hierarchy でプレイヤーの GameObject(例:
Player)を選択します。 Add Componentボタンから、上の例のSimpleMoverまたはあなたの移動スクリプトを追加します。- インスペクタで
speedの値を調整して、通常時の移動速度を決めておきます。
手順② SpeedBuff コンポーネントを追加する
- 同じく
Playerオブジェクト、もしくはその子オブジェクト(例:Player/Status)を選択します。 Add ComponentからSpeedBuffを追加します。- Target Movement Component に、移動スクリプト(例:
SimpleMover)をドラッグ&ドロップで割り当てます。
※ 未指定でも、親階層から自動検出を試みますが、明示的に指定しておく方が安全です。 - 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>();
}
// 当たり判定はトリガーにしておく
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 更新メソッドを登録するだけで、
バフ終了タイミングに合わせた演出を簡単に追加できます。
このように、小さなコンポーネント単位で責務を分けておくと、
後からの仕様変更や演出追加にも柔軟に対応しやすくなります。ぜひ、自分のプロジェクトでもバフ系ロジックをコンポーネント化してみてください。
