敵の頭脳
CoddyのUnityジャーニー「Unityのゲームアーキテクチャ」セクションの一部。レッスン 39/64。
敵の脳は、行動ごとに1つの状態を持つステートマシンです。この骨格は、巡回地点の間を歩き、6単位以内に近づいたプレイヤーを追いかけ、近くにいると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; }
}
}状態は一度だけ作成され、骨格が存在する間ずっと保持されるため、そのフィールドは状態を再訪したときにも維持されます。追跡後もnextの値はPatrolStateに保持され、骨格は向かっていた巡回地点まで歩き続けます。Enterは、最初からやり直すべきものだけをリセットします。AttackStateではcooldownを0に設定するため、骨格がプレイヤーに到達するとすぐに最初の一撃を放ちます。
移動はすべてWalkToを通して行います。ここではtransformに対するMoveTowardsを使っているため、壁を通り抜けて移動します。代わりにRigidbodyで骨格を動かすには、WalkToで物体の速度を設定し、骨格がFixedUpdateでステートマシンを更新するようにします。このとき、Time.deltaTimeは物理演算の時間ステップです。3つの状態は変わりません。行き先を決めるのは状態で、移動方法を決めるのは所有者です。
チャレンジ
中級KnightStates.csで騎士にPatrolStateとChaseStateを実装し、それぞれに騎士を渡します。
- 巡回:
waypoints[next]まで歩かせます。0.3以内に近づいたら、訪問順を循環させながらnextを進め、訪問の間もその値を保持します。sightRangeより近づいたら、追跡します。 - 追跡:プレイヤーまで歩かせます。
loseRangeより遠ざかったら、巡回します。 Knightでは、Awakeでマシンとステートを作成し、Startで巡回を開始し、FixedUpdateでティックを実行します。
「プレイ」を押してから、近づいて走ります。その後、「確認」を押します。
自分で試してみよう
using UnityEngine;
// PatrolStateとChaseState
Unityのゲームアーキテクチャのすべてのレッスン
自分で練習してみよう: Unityプレイグラウンド