敵の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つの状態は変更しません。どこへ行くかを状態が決め、どのように移動するかを所有者が決めます。
チャレンジ
中級騎士が城の中庭を守っています。矢印キーで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
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。