Unityを触り始めた頃、「敵AIもプレイヤーも、とりあえず全部Updateに書いて動いたからOK!」となりがちですよね。
でも時間が経つと、Update() が巨大化して「どこを触ると何が壊れるのか分からない」「敵の挙動だけ少し変えたいのに地獄を見る」という状態になりやすいです。

こういうときに意識したいのが、1つのスクリプトに1つの責務(Single Responsibility) を持たせることです。
「移動はこのコンポーネント」「攻撃はこのコンポーネント」「ワープはこのコンポーネント」というように役割を分けておくと、挙動の追加・削除・差し替えがかなり楽になります。

この記事では、「歩かずに、数秒ごとにプレイヤーの背後や死角へワープする」 という、敵AIらしいイヤらしい挙動を
TeleportAI という1つのコンポーネントに切り出して実装してみます。

【Unity】背後からヌルっと出現!「TeleportAI」コンポーネント

今回の TeleportAI は、以下のようなことを行います。

  • 指定したプレイヤー(ターゲット)を常に追跡
  • 一定間隔ごとに、ターゲットの背後 or 斜め後ろの死角へワープ
  • ワープ位置が壁の中や地面の下にならないように簡易的な補正
  • ワープの前後でイベント(SE再生やエフェクト再生など)をフックできる

フルコード(TeleportAI.cs)


using UnityEngine;

/// <summary>
/// 指定したターゲット(主にプレイヤー)の背後・死角へ、
/// 一定間隔でワープするシンプルなAIコンポーネント。
/// 歩きモーションを持たない敵や、ホラー系の不意打ちに便利です。
/// </summary>
[DisallowMultipleComponent]
public class TeleportAI : MonoBehaviour
{
    // --- 参照系 ---

    [Header("ターゲット設定")]
    [Tooltip("ワープ先の基準となるターゲット(例: プレイヤー)。未設定の場合、Tag \"Player\" を自動検索します。")]
    [SerializeField] private Transform target;

    [Header("ワープ間隔")]
    [Tooltip("ワープするまでの最小待機時間(秒)。")]
    [SerializeField] private float minTeleportInterval = 2.0f;

    [Tooltip("ワープするまでの最大待機時間(秒)。")]
    [SerializeField] private float maxTeleportInterval = 4.0f;

    [Header("ワープ位置オフセット")]
    [Tooltip("ターゲットの背後にどれだけ離れて出現するか(メートル)。")]
    [SerializeField] private float behindDistance = 2.0f;

    [Tooltip("ターゲットの左右どちらにどれだけずらすか(死角感を出したいときに使用)。0 なら真後ろ。")]
    [SerializeField] private float sideOffset = 0.5f;

    [Tooltip("ワープ後の高さオフセット。キャラクターの足元位置に合わせて調整します。")]
    [SerializeField] private float heightOffset = 0.0f;

    [Header("ワープ安全設定")]
    [Tooltip("地面を検出するためのレイヤーマスク。地面となるレイヤーを指定してください。")]
    [SerializeField] private LayerMask groundLayer = ~0; // デフォルト: 全レイヤー

    [Tooltip("地面を探す最大距離(ワープ予定位置から下向きに飛ばすレイの長さ)。")]
    [SerializeField] private float groundCheckDistance = 5.0f;

    [Tooltip("ターゲットとの最小距離。これより近い位置にはワープしないようにします。")]
    [SerializeField] private float minDistanceFromTarget = 1.0f;

    [Header("回転設定")]
    [Tooltip("ワープ後にターゲットの方を向くかどうか。")]
    [SerializeField] private bool lookAtTargetAfterTeleport = true;

    [Tooltip("Y軸のみを回転させる(2D/TopDown でない3Dゲーム向け)。")]
    [SerializeField] private bool rotateOnlyY = true;

    [Header("デバッグ表示")]
    [Tooltip("Sceneビューにワープ予定位置などのガイドを表示します。")]
    [SerializeField] private bool drawGizmos = true;

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

    // 次にワープするまでの残り時間
    private float teleportTimer;

    // 乱数用:左右どちら側にワープするか
    private int lastSideSign = 1;

    // Gizmo用に最後に計算したワープ先
    private Vector3 lastPlannedTeleportPosition;
    private bool hasValidLastTeleportPosition = false;

    private void Reset()
    {
        // コンポーネント追加時に、よく使いそうなデフォルト値をセット
        minTeleportInterval = 2.0f;
        maxTeleportInterval = 4.0f;
        behindDistance = 2.0f;
        sideOffset = 0.5f;
        heightOffset = 0.0f;
        groundCheckDistance = 5.0f;
        minDistanceFromTarget = 1.0f;
        lookAtTargetAfterTeleport = true;
        rotateOnlyY = true;
        drawGizmos = true;
        groundLayer = ~0;
    }

    private void Awake()
    {
        // ターゲットが未設定なら Tag "Player" を自動で探す
        if (target == null)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            if (player != null)
            {
                target = player.transform;
            }
        }

        // 最初のワープまでの時間をランダムに設定
        teleportTimer = GetRandomInterval();
    }

    private void Update()
    {
        if (target == null)
        {
            // ターゲットがいない場合は何もしない
            return;
        }

        // タイマーを減少
        teleportTimer -= Time.deltaTime;

        if (teleportTimer 

使い方の手順

例として、「プレイヤーの背後にワープしてくる幽霊型の敵」を作る手順を説明します。

  1. プレイヤーにTagを設定する
    • プレイヤーキャラクターのGameObjectを選択
    • Inspector 上部の「Tag」を「Player」に設定
    • まだ「Player」タグが無ければ「Add Tag...」から追加しておきましょう
  2. 敵キャラクターのPrefab(またはScene上のオブジェクト)を用意
    • 3Dモデル or 2Dスプライトなど、敵の見た目となるGameObjectを作成
    • 必要なら AnimatorRigidbody を追加(このコンポーネント自体は必須ではありません)
    • 敵の足元が「Y=0」あたりになるように調整しておくと、地面へのスナップが綺麗に決まります
  3. TeleportAI コンポーネントを追加して設定
    • 敵のGameObjectに TeleportAI スクリプトをアタッチ
    • Target を手動で設定しなくても、Tag「Player」を自動で探してくれます
    • 以下のパラメータを調整しながらプレイしてみましょう:
    • Min Teleport Interval / Max Teleport Interval
      → ワープ頻度。ホラー寄りなら 3〜6 秒、アクション寄りなら 1〜3 秒など。
    • Behind Distance
      → どれだけ後ろに出現するか。近すぎると理不尽に感じるので 2〜4m くらいがおすすめ。
    • Side Offset
      → 0 だと完全な真後ろ。0.5〜1.5 くらいにすると「斜め後ろの死角」感が出ます。
    • Min Distance From Target
      → ワープ位置が近すぎるのを防ぐための最低距離。
    • Look At Target After Teleport
      → ワープした直後にプレイヤーの方を向くかどうか。
    • Ground Layer
      → 地面のレイヤーを指定(例: 「Ground」レイヤーを作って地面に割り当てておく)。
  4. 具体的な使用例
    • プレイヤーを追い詰める幽霊
      物理移動を一切させず、TeleportAI だけを付けた「幽霊」敵を作ると、
      常に背後に回り込んでくる不気味な存在になります。
    • ボスの第2形態のギミック
      通常は歩いて追ってくるボスに、体力が一定以下になると TeleportAI を有効化して、
      「瞬間移動モード」に切り替える、といった演出も簡単です。
    • 動く床+ワープ敵
      動く床の上にこの敵をスポーンさせると、プレイヤーが床を渡っている最中に背後にワープし、
      プレイヤーを落とそうとするギミックなども作りやすくなります。

メリットと応用

TeleportAI を1つのコンポーネントとして切り出しておくと、次のようなメリットがあります。

  • プレハブの再利用性が高い
    「ワープする敵」を作りたいときは、このコンポーネントをアタッチするだけ。
    既存の敵プレハブに追加するだけで、「通常移動+ワープ」の複合パターンも簡単に作れます。
  • レベルデザインが楽になる
    「この部屋の敵は2秒ごとにワープ」「このエリアの敵は6秒ごとにゆっくりワープ」など、
    パラメータを変えたプレハブを複数用意しておけば、ステージごとに違う緊張感を演出できます。
  • 責務がはっきりしている
    「このコンポーネントはワープだけを担当」という明確な役割があるので、
    AIロジックが増えても「歩きAI」「射撃AI」「ワープAI」といった形で分割しやすく、
    Godクラス化を防ぎやすいです。

さらに、イベント用のフック(OnBeforeTeleport / OnAfterTeleport)を
別コンポーネントから呼び出したり、TeleportAI を継承して
「ワープするときにだけ無敵になる」などのバリエーションも作りやすくなります。

改造案:ワープ前に一瞬だけフェードアウトする

例えば、ワープ直前に敵を半透明にして「消える」演出を入れたい場合、
以下のようなメソッドを別コンポーネントに追加して、TeleportAI のフックから呼び出すと良いです。


using UnityEngine;

/// <summary>
/// Renderer のマテリアルカラーを使って簡易的にフェードさせるコンポーネント。
/// TeleportAI の OnBeforeTeleport / OnAfterTeleport から呼び出して使います。
/// </summary>
[DisallowMultipleComponent]
public class SimpleFadeEffect : MonoBehaviour
{
    [SerializeField] private Renderer targetRenderer;
    [SerializeField] private float fadedAlpha = 0.1f;

    private Color originalColor;

    private void Awake()
    {
        if (targetRenderer == null)
        {
            targetRenderer = GetComponentInChildren<Renderer>();
        }

        if (targetRenderer != null)
        {
            originalColor = targetRenderer.material.color;
        }
    }

    /// <summary>
    /// 一瞬だけ薄くする(ワープ前に呼ぶ想定)。
    /// </summary>
    public void FadeOutInstant()
    {
        if (targetRenderer == null) return;

        Color c = originalColor;
        c.a = fadedAlpha;
        targetRenderer.material.color = c;
    }

    /// <summary>
    /// 元の不透明度に戻す(ワープ後に呼ぶ想定)。
    /// </summary>
    public void FadeInInstant()
    {
        if (targetRenderer == null) return;

        targetRenderer.material.color = originalColor;
    }
}

このように、「ワープするAI」と「見た目の演出」を別コンポーネントに分ける ことで、
後から「この敵はワープするけどフェードはしない」「この敵はフェードだけする」など、
組み合わせのバリエーションを柔軟に増やしていけるようになります。