Unityゲームにおける状態管理とキャラクター制御スクリプトの解説(敵の攻撃からプレイヤーの攻撃まで)
Unityで、プレイヤーと敵を同じ仕組み(Mob)で動かす設計を解説します。状態管理・攻撃・移動・当たり判定の役割分担と、「敵がプレイヤーを見つける → 追いかける → 攻撃する → ダメージ」の流れ、そして最後に、プレイヤー側からも同じ攻撃ロジックで攻撃できるようにする方法まで、順に見ていきます。
この記事で分かること
- プレイヤーと敵を同じ仕組み(Mob)で動かす設計
- 状態管理(
MobStatus)・攻撃(MobAttack)・移動(EnemyMove/PlayerController)・当たり判定(CollisionDetector)の役割分担 - 敵がプレイヤーを見つけ、追いかけ、攻撃し、ダメージを与えるまでの流れ
- プレイヤーの攻撃入力を、敵と同じ
MobAttackにつなぐ方法
関連記事: RPGキャラクターの当たり判定処理(3種類のコライダーの使い分け)/ 【Unity】アクションRPGクラス図 / 【Unity 6・C#入門】継承って何? MobStatusとEnemyStatusの関係を理解しよう
「Mob」とは
Mob は「mobile object」の略で、ゲーム内で自律的に動くキャラクターや敵、NPC など、動きや状態の変化を持つオブジェクト全般を指します。
- 状態管理: 通常(
Normal)・攻撃(Attack)・死亡(Die)などの状態を持つ - 行動制御: 移動・攻撃・ダメージ処理を
MobStatusやMobAttackが担当する - 再利用性: 同じ土台を使うことで、プレイヤーも敵も共通の仕組みで動かせる
全体像(クラス図)
まず、登場するクラスの関係を図で確認します。MobStatus を親クラスにして EnemyStatus と PlayerStatus が継承し、攻撃は MobAttack に、範囲の検知は CollisionDetector にまとめています。

STEP 1 状態管理とキャラクターの基本クラス
MobStatus(抽象クラス)
- 状態は
Normal/Attack/Dieの3つ IsMovable/IsAttackableで、今移動・攻撃できるかを返すLifeMaxとLifeでライフを管理。Damage(int)でライフが 0 以下になるとDieへ遷移するStart()でライフを満タンにし、Animator を取得するGoToAttackStateIfPossible/GoToNormalStateIfPossibleで状態を切り替える
EnemyStatus(MobStatus を継承)
- NavMeshAgent の速度を Animator の
MoveSpeedに反映する OnDie()で、一定時間後にオブジェクトを破棄する(コルーチン)
PlayerStatus(MobStatus を継承)
現時点では土台だけです。無敵時間や被ダメージ演出など、プレイヤー固有の要素をここに足していきます。
using UnityEngine;
public class PlayerStatus : MobStatus
{
// プレイヤー固有の状態は、ここに追加していく
}
STEP 2 攻撃の制御と関連オブジェクト
MobAttack
| メソッド | 役割 |
|---|---|
AttackIfPossible() | 攻撃できる状態なら Attack に遷移し、攻撃アニメーションを開始する |
OnAttackRangeEnter(Collider) | 攻撃範囲に対象が入ったときに呼ばれるハンドラ。中で AttackIfPossible() を呼ぶ |
OnAttackStart() | アニメーションから呼ばれ、攻撃判定用コライダーを有効化する |
OnHitAttack(Collider) | 攻撃が当たったとき、相手の MobStatus に Damage() を与える |
OnAttackFinished() | コライダーを無効化し、クールダウン後に通常状態へ戻す |
ポイント: 「範囲に入ったこと」を検知するのは CollisionDetector です。OnAttackRangeEnter は、その通知を受け取るハンドラで、名前は「攻撃範囲に入ったときの処理」という意味です。
CollisionDetector の onTriggerEnter に登録すれば範囲に入った瞬間に1回、onTriggerStay に登録すれば範囲内にいる間ずっと呼ばれます。onTriggerStay に登録した場合も、AttackIfPossible() が現在の状態を確認するので、攻撃中に重ねて攻撃が始まることはありません。
AttackRangeDetector(オブジェクト)
- 敵の子オブジェクトです。
BoxCollider(Is Triggerを有効)で「攻撃できる範囲」を定義します CollisionDetectorをアタッチし、通知先にMobAttack.OnAttackRangeEnterを設定します
MobAttackHitCollider(オブジェクト)
- 武器や手足の先端に置くトリガーです。攻撃アニメーションのタイミングだけ有効化します
CollisionDetectorの通知先にMobAttack.OnHitAttackを設定します
STEP 3 移動と入力
EnemyMove
- NavMeshAgent で経路探索をする
OnDetectObjectでプレイヤーを検知し、RaycastNonAllocで間に障害物がないかを判定する- 障害物がなければ追跡し、あれば停止する
PlayerController(攻撃入力つき)
Input System の PlayerInput から Move / Jump / Attack を受け取り、CharacterController で動かします。攻撃入力があれば、敵と同じ MobAttack.AttackIfPossible() を呼びます。
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(PlayerStatus))]
[RequireComponent(typeof(MobAttack))]
public class PlayerController : MonoBehaviour
{
[SerializeField] private Animator animator;
[SerializeField] private float moveSpeed = 3;
[SerializeField] private float jumpPower = 3;
private CharacterController _characterController;
private Transform _transform;
private InputAction _jump;
private InputAction _move;
private InputAction _attack;
private Vector3 _moveVelocity;
private PlayerStatus _status;
private MobAttack _mobAttack;
private void Start()
{
_characterController = GetComponent<CharacterController>();
_transform = transform;
_status = GetComponent<PlayerStatus>();
_mobAttack = GetComponent<MobAttack>();
var input = GetComponent<PlayerInput>();
input.currentActionMap.Enable();
_move = input.currentActionMap.FindAction("Move");
_jump = input.currentActionMap.FindAction("Jump");
_attack = input.currentActionMap.FindAction("Attack");
}
private void Update()
{
// 攻撃(例: マウス左クリック)
if (_attack.WasPressedThisFrame())
{
_mobAttack.AttackIfPossible();
}
// 移動できる状態のときだけ入力を反映
if (_status.IsMovable)
{
var moveValue = _move.ReadValue<Vector2>();
_moveVelocity.x = moveValue.x * moveSpeed;
_moveVelocity.z = moveValue.y * moveSpeed;
_transform.LookAt(_transform.position + new Vector3(_moveVelocity.x, 0, _moveVelocity.z));
}
else
{
_moveVelocity.x = 0;
_moveVelocity.z = 0;
}
// ジャンプと重力
if (_characterController.isGrounded)
{
if (_jump.WasPressedThisFrame())
{
_moveVelocity.y = jumpPower;
}
}
else
{
_moveVelocity.y += Physics.gravity.y * Time.deltaTime;
}
_characterController.Move(_moveVelocity * Time.deltaTime);
animator.SetFloat("MoveSpeed", new Vector3(_moveVelocity.x, 0, _moveVelocity.z).magnitude);
}
}
ポイント
RequireComponentで、必要なコンポーネントを強制する- 攻撃ロジックは
MobAttackに集約し、敵とプレイヤーで再利用する
プレイヤーの入力から攻撃までの流れは、次の図のとおりです。

STEP 4 当たり判定の共通部品 CollisionDetector
OnTriggerEnter / OnTriggerStay で、Inspector に登録した UnityEvent(TriggerEvent)を発火します。TriggerEvent は [Serializable] なので、通知先のメソッドをドラッグ&ドロップで設定できます。
3種類のコライダーの使い分けは、RPGキャラクターの当たり判定処理 で詳しく解説しています。
STEP 5 シーン上の配置と Inspector 設定
- AttackRangeDetector: 敵の子オブジェクト。プレイヤーを認識する範囲
- MobAttackHitCollider: 攻撃時のみ有効化。武器や手足の先端に配置
- 各 Collider は
Is Triggerを有効にする CollisionDetectorのTriggerEventに、EnemyMove.OnDetectObjectやMobAttack.OnHitAttackなどを割り当てる
処理の流れ
ここまでのクラスとオブジェクトが、どの順番で連携するのかを確認します。

- 認知:
AttackRangeDetectorがプレイヤーの侵入を検知し、CollisionDetectorを介してEnemyMoveに伝わる - 追跡:
EnemyMoveが障害物の有無を確認しながら追尾する - 攻撃: 攻撃範囲に入ると
MobAttack.AttackIfPossible()でAttack状態へ。MobAttackHitColliderが有効化される - ダメージ:
OnHitAttack()から、相手のDamage()が呼ばれる - クールダウン: 一定時間後に通常状態へ戻る
- プレイヤーの攻撃: 入力から
AttackIfPossible()が呼ばれる(以降は敵と同じ)
攻撃の部分を、敵とプレイヤーの2つの入口から並べた図が次のものです。どちらの入口から始まっても、AttackIfPossible() 以降は同じ流れになります。

まとめ
- 状態管理は
MobStatusが土台。EnemyStatus/PlayerStatusが継承して固有の処理を追加する - 攻撃は
MobAttackに集約。敵もプレイヤーも同じロジックでダメージ処理を共有する - 当たり判定は
CollisionDetectorで共通化し、Inspector でつなぐ - プレイヤー固有の要素(無敵時間、被ダメージ演出、UI連携)は、
PlayerStatus/PlayerControllerに追加していく









ディスカッション
コメント一覧
まだ、コメントがありません