Unityを触り始めたころは、つい「とりあえず全部Updateに書く」という実装をしがちですよね。プレイヤーの入力処理、カメラ追従、敵AI、エフェクト制御…すべて1つの巨大スクリプトに詰め込んでしまうと、次のような問題が出てきます。
- どの処理がどのオブジェクトの責務なのか分かりづらい
- 少し仕様変更するだけで、あちこちのコードを直す必要が出てくる
- プレハブ単位で機能を差し替え・再利用しにくい
敵AIも同じで、「プレイヤーを追いかける」「攻撃する」「パトロールする」などを1つのクラスに書き始めると、あっという間にGodクラス化してしまいます。
そこでこの記事では、「ターゲットへ向かって移動する」だけに責務を絞ったコンポーネント、「TargetFollower」を作っていきます。敵AI用の基本パーツとして、プレイヤーや特定のノードを追尾させる動きだけを切り出しておくことで、シンプルで再利用しやすい構成にしていきましょう。
【Unity】シンプル追尾でAIを分割!「TargetFollower」コンポーネント
コンポーネントの概要
TargetFollower は、指定した Transform(プレイヤーなど)に向かって、このコンポーネントがアタッチされたオブジェクト(親)を移動させるためのコンポーネントです。
- ターゲットの方向へ向きを変える(任意でオン/オフ)
- ターゲットとの距離が一定以下になったら停止する
- 追尾のオン/オフをスクリプトやインスペクタから切り替え可能
- 物理挙動(Rigidbody)にも対応できるよう、オプションで移動方法を切り替え
「追う」という行動だけを1コンポーネントに閉じ込めておくことで、敵AIの状態管理や攻撃ロジックは別コンポーネントに分離しやすくなります。
フルコード:TargetFollower.cs
using UnityEngine;
/// <summary>
/// 指定したターゲットに向かって、このオブジェクトを移動させるコンポーネント。
/// 敵AIの「追尾」部分だけを担当させることを想定。
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(Transform))]
public class TargetFollower : MonoBehaviour
{
// ===== 設定項目(インスペクタで調整) =====
[Header("ターゲット設定")]
[Tooltip("追尾対象となるTransform(プレイヤーなど)")]
[SerializeField] private Transform target;
[Header("移動設定")]
[Tooltip("1秒あたりの移動速度")]
[SerializeField] private float moveSpeed = 3.0f;
[Tooltip("この距離以下まで近づいたら移動を止める")]
[SerializeField] private float stopDistance = 1.0f;
[Tooltip("ターゲットがこの距離より遠い場合のみ追尾する(0以下で常に追尾)")]
[SerializeField] private float startChaseDistance = 0.0f;
[Header("回転設定")]
[Tooltip("ターゲットの方向へ回転させるかどうか")]
[SerializeField] private bool rotateTowardsTarget = true;
[Tooltip("回転速度(度/秒)。0以下で即座に向きを合わせる")]
[SerializeField] private float rotationSpeed = 360f;
[Header("移動モード")]
[Tooltip("Transform.Translate/MoveTowards で動かすか、Rigidbody で動かすか")]
[SerializeField] private MovementMode movementMode = MovementMode.Transform;
[Tooltip("Rigidbody を使う場合はここに参照を入れる(未設定なら自動取得を試みる)")]
[SerializeField] private Rigidbody rb;
[Header("動作フラグ")]
[Tooltip("追尾を有効にするかどうか(スクリプトからも切り替え可能)")]
[SerializeField] private bool followEnabled = true;
/// <summary>
/// どの方法で移動するか
/// </summary>
private enum MovementMode
{
Transform,
Rigidbody
}
// ===== プロパティ(外部から制御しやすくする) =====
/// <summary>
/// 現在のターゲット
/// </summary>
public Transform Target
{
get => target;
set => target = value;
}
/// <summary>
/// 追尾の有効/無効
/// </summary>
public bool FollowEnabled
{
get => followEnabled;
set => followEnabled = value;
}
// ===== Unity イベント =====
private void Reset()
{
// コンポーネント追加時に Rigidbody があれば拾っておく
if (rb == null)
{
rb = GetComponent();
}
}
private void Awake()
{
// Rigidbody モードなのに Rigidbody が設定されていない場合、自動取得を試みる
if (movementMode == MovementMode.Rigidbody && rb == null)
{
rb = GetComponent();
if (rb == null)
{
Debug.LogWarning(
$"[TargetFollower] Rigidbodyモードですが、Rigidbodyが見つかりません。" +
$" ゲームオブジェクト: {gameObject.name}",
this
);
}
}
}
private void Update()
{
if (!followEnabled) return; // 無効なら何もしない
if (target == null) return; // ターゲットがいなければ何もしない
// ターゲットとの距離を計算
Vector3 toTarget = target.position - transform.position;
float distance = toTarget.magnitude;
// 追尾開始距離が設定されている場合、その距離より遠いときだけ追尾
if (startChaseDistance > 0f && distance <= startChaseDistance)
{
return;
}
// 停止距離以内なら移動しない
if (distance <= stopDistance)
{
// 近づきすぎ防止のため、速度をゼロにしておく(Rigidbodyモードのみ)
if (movementMode == MovementMode.Rigidbody && rb != null)
{
rb.velocity = Vector3.zero;
}
// 回転だけは行うかどうかは好みだが、ここでは行わないことにする
return;
}
// 正規化された方向ベクトル
Vector3 direction = toTarget.normalized;
// 回転処理
if (rotateTowardsTarget)
{
RotateTowards(direction);
}
// 移動処理
MoveTowards(direction);
}
// ===== 内部処理 =====
/// <summary>
/// 指定された方向へオブジェクトを移動させる
/// </summary>
private void MoveTowards(Vector3 direction)
{
float delta = moveSpeed * Time.deltaTime;
switch (movementMode)
{
case MovementMode.Transform:
// Transform を直接動かすシンプルな方法
transform.position += direction * delta;
break;
case MovementMode.Rigidbody:
if (rb == null)
{
// Rigidbody がない場合は Transform で代用
transform.position += direction * delta;
return;
}
// Rigidbody の velocity を使って移動させる
// (地面判定などをする場合はここを拡張するとよい)
Vector3 newVelocity = direction * moveSpeed;
newVelocity.y = rb.velocity.y; // Y方向の速度は維持(重力など)
rb.velocity = newVelocity;
break;
}
}
/// <summary>
/// 指定された方向へ向くように回転させる
/// </summary>
private void RotateTowards(Vector3 direction)
{
if (direction.sqrMagnitude <= 0.0001f) return;
// Y軸だけを回転させたい場合(2D横スクロールや3Dキャラ用)
Vector3 lookDirection = direction;
lookDirection.y = 0f; // 水平面上での回転に限定
if (lookDirection.sqrMagnitude <= 0.0001f) return;
Quaternion targetRotation = Quaternion.LookRotation(lookDirection, Vector3.up);
if (rotationSpeed <= 0f)
{
// 即座に向きを合わせる
transform.rotation = targetRotation;
}
else
{
// なめらかに回転させる
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
}
// ===== デバッグ用の可視化 =====
private void OnDrawGizmosSelected()
{
// 停止距離と追尾開始距離をシーンビューに可視化する
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, stopDistance);
if (startChaseDistance > 0f)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(transform.position, startChaseDistance);
}
// ターゲットへのライン
if (target != null)
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, target.position);
}
}
}
使い方の手順
ここでは、敵キャラクターがプレイヤーを追いかけるケースを例に、基本的な使い方を説明します。
手順①:スクリプトをプロジェクトに追加
TargetFollower.csという名前で、上記のコードをそのまま保存します。- Unityエディタに戻り、コンパイルが通るのを待ちます。
手順②:敵キャラクターにコンポーネントをアタッチ
- ヒエラルキーで「Enemy」などの敵キャラクターの GameObject を選択します。
Add ComponentボタンからTargetFollowerを追加します。- もし敵が物理挙動で動くタイプなら、同じオブジェクトに
Rigidbodyを追加し、Movement ModeをRigidbodyに変更します。
手順③:ターゲット(プレイヤー)を指定
- プレイヤーの GameObject(例:
Player)をヒエラルキーからドラッグし、TargetFollowerの Target スロットにドロップします。 - Move Speed(移動速度)、Stop Distance(どこまで近づくか)、Start Chase Distance(どれだけ離れたら追いかけ始めるか)を好みの値に調整します。
- 敵の向きをプレイヤーに向けたい場合は、Rotate Towards Target にチェックを入れ、Rotation Speed を設定します。
手順④:動作確認と具体的な使用例
- プレイヤーを追いかける敵
プレイヤーが近づくと、Start Chase Distanceを超えたタイミングで敵が追いかけ始め、Stop Distanceまで近づくと止まる挙動になります。
攻撃処理は別コンポーネント(例:EnemyAttacker)に分離しておき、Stop Distance付近で攻撃アニメーションを再生すると、きれいに責務を分けられます。 - 巡回ポイントを追尾する敵
ターゲットをプレイヤーではなく、「巡回ポイント用のEmptyオブジェクト」に設定すると、「今向かうべきポイント」を追尾するコンポーネントとして使えます。
別コンポーネントで「次の巡回ポイントをTargetFollower.Targetにセットする」だけで、移動ロジックはそのまま使い回せます。 - 動く床がプレイヤーを追いかけるギミック
動く床の GameObject にTargetFollowerを付けて、プレイヤーをターゲットにするだけで、プレイヤーを追いかける床ギミックが作れます。
Rotate Towards Targetをオフにしておけば、床は回転せずに水平移動だけ行います。
メリットと応用
TargetFollower を用意しておくと、敵AIやギミックの実装がかなり楽になります。
- プレハブ管理がシンプルになる
「追尾する敵」「巡回する敵」「待ち伏せする敵」などを作るときも、TargetFollowerを共通パーツとしてプレハブに含めておき、ターゲットやパラメータだけを変える形にできます。
行動パターンの違いは別コンポーネントで表現できるため、プレハブのバリエーションが増えても、コードの重複は最小限で済みます。 - レベルデザインの自由度が上がる
レベルデザイナーは「この敵はプレイヤーを追いかける」「この敵は特定のポイントへ移動する」などを、インスペクタ上でターゲットを差し替えるだけで実現できます。
スクリプトを書き換えずに、シーンごとに挙動を細かく調整できるのが大きなメリットです。 - 責務が明確でテストしやすい
「ターゲットを追尾する」という単一の責務に絞っているため、バグの切り分けがしやすく、
「追尾が変」「攻撃タイミングがおかしい」などの問題を、それぞれ専用コンポーネント側で確認できます。
改造案:一時的に追尾を停止するメソッド
例えば、「ダメージを受けたときに少しの間だけ追尾を止める」などの演出を入れたい場合は、TargetFollower に次のようなメソッドを追加すると便利です。
/// <summary>
/// 指定した秒数だけ追尾を一時停止するコルーチン
/// (呼び出し側から StartCoroutine(TemporaryStop(1.0f)); のように使用)
/// </summary>
public System.Collections.IEnumerator TemporaryStop(float duration)
{
// すでに止まっている場合も考慮して、元の状態を覚えておく
bool previousState = followEnabled;
followEnabled = false;
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
yield return null;
}
followEnabled = previousState;
}
これを使えば、EnemyHealth や EnemyStunController など別コンポーネントから、
「スタン中は追尾しない」といった状態管理を簡単に組み込めます。
このように、「追尾する」という小さな責務のコンポーネントを用意しておくことで、敵AIやギミックの組み立てがぐっと楽になります。ぜひ自分のプロジェクトでも、機能ごとにコンポーネントを分割する設計を試してみてください。
