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 などを割り当てる

処理の流れ

ここまでのクラスとオブジェクトが、どの順番で連携するのかを確認します。

  1. 認知: AttackRangeDetector がプレイヤーの侵入を検知し、CollisionDetector を介して EnemyMove に伝わる
  2. 追跡: EnemyMove が障害物の有無を確認しながら追尾する
  3. 攻撃: 攻撃範囲に入ると MobAttack.AttackIfPossible() で Attack 状態へ。MobAttackHitCollider が有効化される
  4. ダメージ: OnHitAttack() から、相手の Damage() が呼ばれる
  5. クールダウン: 一定時間後に通常状態へ戻る
  6. プレイヤーの攻撃: 入力から AttackIfPossible() が呼ばれる(以降は敵と同じ)

攻撃の部分を、敵とプレイヤーの2つの入口から並べた図が次のものです。どちらの入口から始まっても、AttackIfPossible() 以降は同じ流れになります。

まとめ

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

広告

Unity

Posted by hidepon