Unityを触り始めると、ついなんでもかんでも Update() に書いてしまいがちですよね。プレイヤーの入力処理、敵のAI、エフェクト制御、UI更新…全部ひとつのスクリプトに押し込んでしまうと、数百行を超えたあたりから「どこを触れば何が変わるのか」が分からなくなってきます。
特に「敵AI」は肥大化しやすい代表例です。追いかける、攻撃する、逃げる、巡回する…といったロジックを1つのクラスに詰め込むと、挙動をちょっと変えたいだけでも大手術が必要になってしまいます。
そこで今回は、「HPが減ったらプレイヤーから逃げる」という挙動だけに責務を絞ったコンポーネント FleeBehavior を作ってみましょう。
「追うAI」「攻撃AI」といった他のコンポーネントと組み合わせやすい、小さな逃走専用コンポーネントです。
【Unity】瀕死になったら全力で逃げろ!「FleeBehavior」コンポーネント
この FleeBehavior は、ざっくり言うと以下のことをやります。
- 敵の現在HPと最大HPをもとに「逃走モードに入るか」を判定
- プレイヤーの位置から逆方向に向きを計算
- Rigidbody を使って、一定速度でプレイヤーから離れるように移動
- 逃走中かどうかを他のスクリプトからも参照できるようにする
「逃げる」という単機能に絞ることで、追跡AIや攻撃AIと分離して実装でき、コンポーネント指向らしいスッキリした構成になります。
フルコード:FleeBehavior.cs
using UnityEngine;
/// <summary>
/// HPが一定以下になると、ターゲット(プレイヤーなど)から
/// 逆方向へ全力で逃走する挙動を付与するコンポーネント。
/// Rigidbody ベースの移動を想定しています。
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class FleeBehavior : MonoBehaviour
{
// ====== 逃走条件関連 ======
[Header("逃走条件")]
[Tooltip("最大HP。現在HP / 最大HP が 逃走開始HP割合 を下回ると逃走開始。")]
[SerializeField] private float maxHp = 100f;
[Tooltip("現在HP。外部のダメージ処理から減らしていく値。")]
[SerializeField] private float currentHp = 100f;
[Tooltip("この割合を下回ったら逃走開始(0〜1)。例: 0.3 なら 30% 以下で逃げる。")]
[Range(0f, 1f)]
[SerializeField] private float fleeHpRatioThreshold = 0.3f;
// ====== 移動関連 ======
[Header("移動設定")]
[Tooltip("逃走時の移動速度(m/s)。")]
[SerializeField] private float fleeSpeed = 5f;
[Tooltip("向きを補正する際の回転速度(度/秒)。0 にすると即座に向きが変わる。")]
[SerializeField] private float rotationSpeed = 360f;
[Tooltip("Y方向の移動を無視して、水平面(X-Z 平面)だけで逃げる場合は true。")]
[SerializeField] private bool constrainToHorizontalPlane = true;
// ====== ターゲット関連 ======
[Header("ターゲット設定")]
[Tooltip("逃走対象(通常はプレイヤー)。指定されていない場合、自動で Player タグを検索。")]
[SerializeField] private Transform target;
[Tooltip("ターゲットとの距離がこの値より大きいときは、移動をやめる(逃げ切った扱い)。0 なら常に逃げ続ける。")]
[SerializeField] private float stopFleeDistance = 0f;
// ====== 内部状態 ======
private Rigidbody _rigidbody;
/// <summary> 現在逃走中かどうか(外部からも参照可能)。 </summary>
public bool IsFleeing { get; private set; }
private void Awake()
{
// 同一オブジェクト上の Rigidbody をキャッシュ
_rigidbody = GetComponent<Rigidbody>();
}
private void Start()
{
// ターゲットが未設定なら Player タグを自動検索
if (target == null)
{
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null)
{
target = playerObj.transform;
}
else
{
Debug.LogWarning(
$"[FleeBehavior] ターゲットが設定されておらず、Player タグのオブジェクトも見つかりませんでした。" +
$" ゲーム開始前に target を設定するか、Player にタグを付けてください。", this);
}
}
// 念のため、現在HPが最大HPを超えないようにクランプ
currentHp = Mathf.Clamp(currentHp, 0f, maxHp);
}
private void FixedUpdate()
{
// 1. 逃走条件の判定
UpdateFleeState();
// 2. 実際の移動処理
if (IsFleeing && target != null)
{
FleeFromTarget();
}
else
{
// 逃走していないときは、逃走用の速度成分をゼロにしておくと扱いやすい
// (他の移動コンポーネントと併用する場合に速度が残らないようにする)
// ここでは XZ のみをゼロにし、重力Yは維持する例にしています。
Vector3 velocity = _rigidbody.velocity;
velocity.x = 0f;
velocity.z = 0f;
_rigidbody.velocity = velocity;
}
}
/// <summary>
/// HP と距離に基づいて、逃走状態に入るかどうかを更新する。
/// </summary>
private void UpdateFleeState()
{
// HP割合を計算(maxHp が 0 の場合は 0 とみなす)
float hpRatio = maxHp > 0f ? currentHp / maxHp : 0f;
bool shouldFleeByHp = hpRatio <= fleeHpRatioThreshold;
bool isFarEnough = false;
if (stopFleeDistance > 0f && target != null)
{
float distance = Vector3.Distance(transform.position, target.position);
isFarEnough = distance >= stopFleeDistance;
}
// HP 条件を満たしていて、まだ十分に離れていない場合は逃走
if (shouldFleeByHp && !isFarEnough)
{
IsFleeing = true;
}
else
{
IsFleeing = false;
}
}
/// <summary>
/// ターゲットから逆方向に向きを計算し、Rigidbody を使って移動させる。
/// </summary>
private void FleeFromTarget()
{
// ターゲットから自分へのベクトル(=追われる方向)
Vector3 toSelf = transform.position - target.position;
if (constrainToHorizontalPlane)
{
// 水平面だけで逃げたい場合は Y を無視
toSelf.y = 0f;
}
// ゼロベクトルの場合は何もしない(位置が完全に同じなど)
if (toSelf.sqrMagnitude < 0.0001f)
{
return;
}
// 逃走方向(正規化ベクトル)
Vector3 fleeDir = toSelf.normalized;
// 向きをゆっくり変えたい場合
if (rotationSpeed > 0f)
{
Quaternion targetRotation = Quaternion.LookRotation(fleeDir, Vector3.up);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.fixedDeltaTime
);
}
else
{
// 0 の場合は即座に向きを変える
transform.rotation = Quaternion.LookRotation(fleeDir, Vector3.up);
}
// Rigidbody に速度を設定して移動
Vector3 velocity = fleeDir * fleeSpeed;
if (constrainToHorizontalPlane)
{
// Y 方向の速度は維持(重力など)しつつ、XZ だけ上書きする
velocity.y = _rigidbody.velocity.y;
}
_rigidbody.velocity = velocity;
}
// ====== 外部から制御するための公開メソッド群 ======
/// <summary>
/// ダメージを与える。マイナス値を渡すと回復として扱う。
/// 外部の攻撃スクリプトから呼び出して使うことを想定。
/// </summary>
/// <param name="amount">与えるダメージ量。</param>
public void ApplyDamage(float amount)
{
currentHp -= amount;
currentHp = Mathf.Clamp(currentHp, 0f, maxHp);
}
/// <summary>
/// HP を直接設定したい場合に使う(デバッグ用など)。
/// </summary>
public void SetCurrentHp(float hp)
{
currentHp = Mathf.Clamp(hp, 0f, maxHp);
}
/// <summary>
/// 最大HPを設定しつつ、必要なら現在HPも合わせて更新する。
/// </summary>
public void SetMaxHp(float newMaxHp, bool alsoResetCurrent = true)
{
maxHp = Mathf.Max(1f, newMaxHp);
if (alsoResetCurrent)
{
currentHp = maxHp;
}
else
{
currentHp = Mathf.Clamp(currentHp, 0f, maxHp);
}
}
/// <summary>
/// 逃走対象(ターゲット)を動的に差し替えたい場合に使う。
/// 例: プレイヤーが乗り移ったロボットなど、別の Transform を追う/逃げるとき。
/// </summary>
public void SetTarget(Transform newTarget)
{
target = newTarget;
}
}
使い方の手順
ここからは、実際に敵キャラクターにこの逃走AIを組み込む手順を見ていきましょう。
手順①:敵キャラクターに Rigidbody を設定する
- Hierarchy で敵キャラクターの GameObject を選択します(例:
EnemyGoblin)。 Add ComponentボタンからRigidbodyを追加します。
・Use Gravity:地面の上を歩かせるなら ON のままでOK
・Constraints:2D 的な動きにしたい場合はFreeze Rotation X/Zなどをチェックしておきましょう
手順②:FleeBehavior コンポーネントを追加する
- 同じ敵 GameObject に、
FleeBehaviorスクリプトをアタッチします。 - インスペクターで以下の値を設定します:
- Max Hp:敵の最大HP(例: 100)
- Current Hp:初期HP(通常は Max Hp と同じ)
- Flee Hp Ratio Threshold:逃走開始HP割合(例: 0.3 なら 30% 以下で逃げる)
- Flee Speed:逃げる速度(例: 6)
- Rotation Speed:向きの回転速度(例: 360)
- Constrain To Horizontal Plane:地面の上を走る敵なら ON 推奨
- Target:空のままでも OK(Player タグを自動検索)
- Stop Flee Distance:例: 15 とすると、プレイヤーと 15m 以上離れたら逃走終了
手順③:プレイヤーをターゲットとして認識させる
- プレイヤーの GameObject に
Playerタグを設定します。
・Hierarchy でプレイヤーを選択 → Inspector 上部の「Tag」ドロップダウン →Playerを選択 - もし複数のターゲットを使いたい場合や、プレイヤーが
Playerタグを持っていない場合は、
敵のインスペクターで Target フィールドに直接 Transform をドラッグ&ドロップしてください。
手順④:ダメージ処理から HP を減らす
逃走AIは「HPが減ったこと」を知らないと動きません。
攻撃判定などから ApplyDamage() を呼び出すだけの、シンプルなダメージスクリプト例を示します。
using UnityEngine;
/// <summary>
/// 非常にシンプルなダメージテスト用スクリプト。
/// スペースキーを押すたびに、敵に 10 ダメージを与える例です。
/// 実際のゲームでは、攻撃ヒット時などから呼び出してください。
/// </summary>
public class DamageTest : MonoBehaviour
{
[SerializeField] private FleeBehavior targetFleeBehavior;
[SerializeField] private float damageAmount = 10f;
private void Update()
{
// スペースキーでダメージを与える簡易テスト
if (Input.GetKeyDown(KeyCode.Space) && targetFleeBehavior != null)
{
targetFleeBehavior.ApplyDamage(damageAmount);
Debug.Log($"敵に {damageAmount} ダメージを与えました。現在HPは逃走AIのインスペクターで確認してください。");
}
}
}
この DamageTest を空の GameObject に付けて、
Target Flee Behavior に敵の FleeBehavior をアサインしておけば、
プレイ中にスペースキーを押すたびに敵のHPが減り、閾値を下回った瞬間からプレイヤーと逆方向に逃げ始めます。
具体的な使用例
- 例1:ゴブリンが瀕死で逃げ出す
・通常時は別のコンポーネント(例:ChasePlayer)でプレイヤーを追いかける。
・HPが30%を切るとFleeBehaviorが有効になり、追跡を止めて逃走モードへ。
・IsFleeingフラグを他のコンポーネントから参照して、「逃げている間は攻撃を無効化」なども簡単にできます。 - 例2:ボス戦でのフェーズ切り替え
・ボスのHPが一定以下になると、プレイヤーから距離を取るように逃げ回る。
・その間に弾幕をばらまく、召喚を行う、といった「第二形態」演出がしやすくなります。 - 例3:動く床がプレイヤーから離れていくギミック
・動く足場にFleeBehaviorを付けて、ターゲットをプレイヤーに設定。
・HPの代わりに「スイッチが押されたらSetCurrentHp()で一気に減らす」ことで、
スイッチON → 足場がプレイヤーから遠ざかる逃げ床ギミックを簡単に作れます。
メリットと応用
FleeBehavior を単独コンポーネントとして切り出すメリットはかなり多いです。
- プレハブの再利用性が高い
「追う」「攻撃する」「逃げる」を別々のコンポーネントにしておくことで、
ゴブリン、オーク、ボスなど、さまざまな敵プレハブに対して
必要なコンポーネントだけを組み合わせてAIを構築できます。 - レベルデザインが楽になる
逃走開始HP割合や逃走速度、逃走終了距離をインスペクターから調整できるので、
「このステージの敵はすぐ逃げる」「このボスはしぶとく戦う」などを
スクリプトを書き換えずに調整できます。 - デバッグしやすい
「逃走ロジックだけ」がこのコンポーネントに閉じているため、
想定通り逃げないときに見るべき場所が明確です。
HPの値、ターゲットの設定、閾値の設定をチェックすればだいたい原因が分かります。
さらに、他のコンポーネントと組み合わせることで、
「追いかける → 瀕死で逃げる → 逃げ切ったら消える」といった複雑なAIも
1つの巨大なクラスを書くことなく実現できます。
改造案:逃走中にランダムなジグザグ移動を加える
単純にプレイヤーと逆方向に逃げるだけだと、動きが単調になりがちです。
「逃走中だけジグザグに揺れる」ようなノイズを加えると、より生き物らしい挙動になります。
例えば、FleeBehavior 内に次のようなメソッドを追加し、
FleeFromTarget() の逃走方向に混ぜて使うことができます。
/// <summary>
/// 逃走方向に対して、ランダムなジグザグ成分を追加する例。
/// 振れ幅とスピードは引数で調整可能。
/// </summary>
private Vector3 AddZigZagNoise(Vector3 baseDirection, float amplitude = 0.5f, float frequency = 2f)
{
// 時間に応じて -1〜1 の値を生成
float noise = Mathf.Sin(Time.time * frequency);
// baseDirection に対して左右方向(法線ベクトル)を計算
Vector3 right = Vector3.Cross(Vector3.up, baseDirection).normalized;
// 左右方向に揺らぎを加える
Vector3 offset = right * noise * amplitude;
// 元の方向と合成して正規化
Vector3 result = (baseDirection + offset).normalized;
return result;
}
このように、小さなコンポーネントとして逃走ロジックを切り出しておくと、
「ノイズを足す」「アニメーションを連動させる」「逃走中だけ足音SEを鳴らす」などの改造も
安全かつ局所的に行えるようになります。
Update に全部書くスタイルから卒業して、コンポーネント指向でAIを組み立てていきましょう。




