Menu
CoddyTech

敵のAI

CoddyのUnityジャーニー「Unityにおけるゲームアーキテクチャ」セクションの一部。レッスン 31/50。

敵の脳は、振る舞いごとに1つの状態を持つステートマシンです。このスケルトンは持ち場の間を歩き、6ユニット以内に入ったプレイヤーを追いかけ、近づくと1秒に1回剣を振ります。各状態はそれぞれ独自の退出方法を決め、スケルトンは状態間で共有するヘルパーを保持します。

public class Skeleton : MonoBehaviour
{
    public Transform player;
    public Transform[] posts;
    public float speed = 2f;
    public StateMachine machine = new StateMachine();
    public IState patrol, chase, attack;

    void Start()
    {
        patrol = new PatrolState(this);
        chase = new ChaseState(this);
        attack = new AttackState(this);
        machine.ChangeState(patrol);
    }

    void Update() { machine.Tick(); }

    public float PlayerDistance() { return Vector3.Distance(transform.position, player.position); }

    public void WalkTo(Vector3 target)
    {
        target.y = transform.position.y;
        transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
    }
}

public class PatrolState : IState
{
    private Skeleton skel;
    private int next;
    public PatrolState(Skeleton skel) { this.skel = skel; }
    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        if (skel.PlayerDistance() < 6f) { skel.machine.ChangeState(skel.chase); return; }
        Vector3 post = skel.posts[next].position;
        post.y = skel.transform.position.y;
        skel.WalkTo(post);
        if (skel.transform.position == post) next = (next + 1) % skel.posts.Length;
    }
}

public class ChaseState : IState
{
    private Skeleton skel;
    public ChaseState(Skeleton skel) { this.skel = skel; }
    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        float d = skel.PlayerDistance();
        if (d > 6f) { skel.machine.ChangeState(skel.patrol); return; }
        if (d < 1.5f) { skel.machine.ChangeState(skel.attack); return; }
        skel.WalkTo(skel.player.position);
    }
}

public class AttackState : IState
{
    private Skeleton skel;
    private float cooldown;
    public AttackState(Skeleton skel) { this.skel = skel; }
    public void Enter() { cooldown = 0f; }
    public void Exit() { }

    public void Tick()
    {
        if (skel.PlayerDistance() > 1.5f) { skel.machine.ChangeState(skel.chase); return; }
        cooldown -= Time.deltaTime;
        if (cooldown <= 0f) { Debug.Log("Swing!"); cooldown = 1f; }
    }
}

状態は一度だけ作成され、スケルトンが存在する間ずっと保持されるため、そのフィールドは状態を再訪したときにも維持されます。PatrolState の next は追跡後もそのままなので、スケルトンは向かっていた持ち場まで歩き続けます。Enter はリセットすべきものだけをリセットします。AttackState では cooldown を 0 に設定するため、スケルトンがプレイヤーに到達するとすぐに最初の一振りを行います。

移動はすべて WalkTo を通ります。ここでは transform に対する MoveTowards を使っており、壁を通り抜けて歩きます。代わりに Rigidbody でスケルトンを動かすには、WalkTo で本体の速度を設定し、スケルトンが FixedUpdate でマシンを更新するようにします。このとき、Time.deltaTime は物理演算のステップです。3つの状態は変更しません。どこへ行くかを状態が決め、どのように移動するかを所有者が決めます。

challenge icon

チャレンジ

中級

騎士が城の中庭を守っています。矢印キーでPlayerを動かします。PlayerのHealthにはTakeDamage(int amount)があります(どちらも準備済みです)。Knight.csには、Inspectorで設定するフィールド(waypointsのW0からW2、player、playerHealth、speedの3、および以下の範囲)と、Rigidbodyを使うヘルパーがあります。DistanceTo(point)とPlayerDistance()は地面に沿った距離を測り、WalkTo(target)は指定した地点に向かって歩き、Stop()はその場で停止します。騎士に頭脳を与えましょう。

  • Knight.csで、マシンと3つの状態(フィールドpatrol、chase、attack)を作成し、パトロールを開始して、FixedUpdateでマシンを更新します。
  • KnightStates.csで、PatrolState、ChaseState、AttackStateを記述します。それぞれのコンストラクターで騎士を受け取ります。
  • パトロール: waypoints[next]まで歩きます。その地点から0.3以内に来たら次の地点に向かい、最後の地点の後は最初の地点に戻ります。プレイヤーがsightRange(5)より近づくと、追跡します。追跡が終わると、パトロールは向かっていたウェイポイントから続行します。
  • 追跡: プレイヤーに向かって歩きます。attackRange(1.5)より近づくと攻撃します。loseRange(8)より遠ざかるとパトロールします。5から8の間では追跡を続けるため、視界の境界で騎士が2つの状態を行き来することはありません。
  • 攻撃: その場で停止し、attackRange以内にいる間、プレイヤーにすぐ1ダメージを与え、その後は毎秒攻撃します。プレイヤーがそれより遠くに離れたら、再び追跡します。

Playを押し、騎士に見つかるまで待ち、攻撃を受けてから逃げて、Checkを押してください。

自分で試してみよう

using UnityEngine;

// PatrolState、ChaseState、AttackState
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Unityにおけるゲームアーキテクチャのすべてのレッスン