Unityを触り始めた頃、Update() の中に「入力処理」「移動」「カメラ追従」「UI更新」など、全部まとめて書いてしまいがちですよね。動くには動くのですが、少し機能を足したり調整したりするたびに、巨大なスクリプトをスクロールしまくる羽目になります。
特にカメラ処理は「とりあえずプレイヤーの座標に追従させるだけ」と軽く考えて、プレイヤーのスクリプトにべったり書きがちです。すると、
- プレイヤー以外のオブジェクトに同じ追従処理を使い回せない
- カメラの挙動を変えるたびにプレイヤーのコードを触ることになる
- 責務が混ざってテストしづらい
といった問題が出てきます。
そこでこの記事では、「追従する」という機能だけに責務を絞ったコンポーネント 「SmoothPursuit(滑らか追従)」 を作っていきます。カメラにアタッチしてターゲットを指定するだけで、Lerp を使った「少し遅れて追いかける」気持ちいいカメラを実現していきましょう。
【Unity】遅れてヌルっと追いかけるカメラ!「SmoothPursuit」コンポーネント
フルコード
using UnityEngine;
/// <summary>
/// 指定したターゲットの位置へ、Lerp で滑らかに追従させるコンポーネント。
/// 主にカメラ用だが、UIやライトなど「追いかけたいもの」にも使える。
/// </summary>
[DisallowMultipleComponent]
public class SmoothPursuit : MonoBehaviour
{
[Header("追従ターゲット")]
[Tooltip("追いかけたい Transform(例: プレイヤーのTransform)")]
[SerializeField] private Transform target;
[Header("追従設定")]
[Tooltip("ターゲットとのオフセット(カメラなら、プレイヤーからどれだけ離すか)")]
[SerializeField] private Vector3 positionOffset = new Vector3(0f, 5f, -10f);
[Tooltip("追従のスムーズさ。大きいほど素早く追いかける(推奨: 3〜10)")]
[SerializeField] private float followSpeed = 5f;
[Tooltip("特定の軸をロックして追従させない場合に使用(例: 2D横スクロールでZを固定など)")]
[SerializeField] private bool lockX = false;
[SerializeField] private bool lockY = false;
[SerializeField] private bool lockZ = false;
[Header("初期位置設定")]
[Tooltip("開始時に現在位置を基準にオフセットを自動計算する")]
[SerializeField] private bool autoCalculateOffset = false;
// LateUpdate を使うことで、キャラクターの移動が終わったあとに追従処理を行う
private void LateUpdate()
{
// ターゲットが設定されていない場合は何もしない
if (target == null)
{
return;
}
// 追従したい「目標位置」を計算
Vector3 desiredPosition = target.position + positionOffset;
// 特定軸をロックしたい場合は、現在位置の値を維持する
Vector3 currentPosition = transform.position;
if (lockX)
{
desiredPosition.x = currentPosition.x;
}
if (lockY)
{
desiredPosition.y = currentPosition.y;
}
if (lockZ)
{
desiredPosition.z = currentPosition.z;
}
// Lerp を使って現在位置から目標位置へ滑らかに補間
// followSpeed に Time.deltaTime を掛けて、フレームレートに依存しない追従速度にする
float t = followSpeed * Time.deltaTime;
// Lerp の第3引数は 0〜1 の範囲が基本だが、
// t が 1 を超えても Mathf.Lerp / Vector3.Lerp 内部でクランプされるので問題はない
Vector3 smoothedPosition = Vector3.Lerp(currentPosition, desiredPosition, t);
// 実際に位置を反映
transform.position = smoothedPosition;
}
private void Start()
{
// 初期化時にオフセットを自動計算したい場合
if (autoCalculateOffset && target != null)
{
// 現在の自分の位置とターゲットの位置の差分をオフセットとして保存
positionOffset = transform.position - target.position;
}
}
/// <summary>
/// 追従ターゲットをコードから動的に差し替えたい場合に使用。
/// 例: プレイヤーが乗り物に乗ったとき、カメラの注目対象を乗り物に切り替えるなど。
/// </summary>
/// <param name="newTarget">新しいターゲットの Transform</param>
/// <param name="recalculateOffset">オフセットを現在位置から再計算するかどうか</param>
public void SetTarget(Transform newTarget, bool recalculateOffset = true)
{
target = newTarget;
if (target == null)
{
return;
}
if (recalculateOffset)
{
// 現在の位置から新ターゲットまでの差分をオフセットとして再計算
positionOffset = transform.position - target.position;
}
}
/// <summary>
/// 現在のターゲットを取得するためのアクセサ。
/// 外部からターゲットを確認したいときに利用。
/// </summary>
public Transform GetTarget()
{
return target;
}
}
使い方の手順
-
コンポーネントを作成する
Unity の Project ビューで右クリック → Create → C# Script を選び、SmoothPursuitという名前で作成します。
自動生成された中身をすべて削除し、上のフルコードをコピペして保存しましょう。 -
カメラにアタッチする
Hierarchy でメインカメラ(例:Main Camera)を選択し、Inspector の「Add Component」ボタンからSmoothPursuitを追加します。
これでカメラは「追従する能力」を持ったコンポーネントになります。 -
ターゲットを設定する
例としてプレイヤーキャラクターを追従させたい場合:- Hierarchy からプレイヤーの GameObject(例:
Player)を、SmoothPursuitの Target フィールドにドラッグ&ドロップします。 - カメラの位置をシーンビューで「ちょうどいい位置」に動かし、
autoCalculateOffsetにチェックを入れておくと、再生開始時にその差分をオフセットとして自動計算してくれます。 - もしくは、
positionOffsetを手動で調整(例:(0, 5, -10))してもOKです。
- Hierarchy からプレイヤーの GameObject(例:
-
追従のスピードや軸ロックを調整する
followSpeed: 値を大きくするとターゲットに素早く追いつき、小さくすると「ふわ〜っと遅れてついてくる」感じになります。3〜10あたりから試してみると良いですね。lockX / lockY / lockZ: 例えば 2D 横スクロールゲームならlockY = true, lockZ = trueにして、X 方向だけ追従させる、といった使い方ができます。
プレイヤー以外にも:
- ボス戦だけ「ボスを中心にカメラを寄せたい」→ ボスの Transform を
SetTarget()で設定。 - 動く床に乗ったときだけ床を追従したい → 床にアタッチしたダミーオブジェクトをターゲットにする。
メリットと応用
SmoothPursuit をコンポーネントとして分離しておくと、
- 「プレイヤーのロジック」と「カメラの追従ロジック」が完全に分かれるので、どちらかを変更しても相手に影響しにくい
- プレイヤープレハブを量産しても、カメラ側の設定は変えずに済む
- レベルデザイン時に「このシーンだけ追従スピードを遅く」「このシーンだけY軸ロック解除」といった調整が、Inspector の数値をいじるだけで完結する
- カメラ以外にも、ライト・エフェクト・UI など「何かを追いかけたいオブジェクト」にそのまま使い回せる
特にプレハブ管理の観点では、
- プレイヤー側は「自分がどう動くか」だけに集中したスクリプトにできる
- カメラ側は「誰をどう追いかけるか」だけを管理するプレハブにできる
という形で責務が分かれ、シーンごとに「このカメラはボスを追う」「このカメラはプレイヤーを追う」といった差し替えも簡単になります。
応用として、例えば「ターゲットから一定距離以上離れたら一気に追いつく」ような挙動を追加するのも面白いです。下記はそのための簡単な改造案です。
/// <summary>
/// ターゲットから一定距離以上離れている場合は、瞬時に目標位置へワープさせる例。
/// 追従がもたつきすぎないようにするセーフティ機構として使える。
/// </summary>
/// <param name="maxDistance">これ以上離れていたら即座に追いつく距離</param>
public void SnapIfTooFar(float maxDistance)
{
if (target == null)
{
return;
}
Vector3 desiredPosition = target.position + positionOffset;
float distance = Vector3.Distance(transform.position, desiredPosition);
if (distance > maxDistance)
{
// 距離が離れすぎている場合は、Lerp を使わずに一気に目標位置へ移動
transform.position = desiredPosition;
}
}
このように、小さな責務のコンポーネントとして作っておくと、「追従する」という軸でいろいろなバリエーションを積み上げていけます。
巨大なカメラスクリプトを1本育てるよりも、シーンや用途に応じて組み合わせられる小さなコンポーネント群を作っていく方が、長期的にメンテしやすいプロジェクトになりますね。




