一般に、各ゲーム オブジェクトには複数の状態があり、各状態は特定のアニメーションに対応します。たとえば、ゲーム キャラクタには、静止状態、移動状態、および攻撃状態がある場合があります。各状態には対応するアニメーションがあります。プレーヤーがどのコントロールを制御してアニメーションを切り替えるかを単純に if 文で判断すると、プログラムの可読性や保守性が悪くなります。FSM (Finite State Machine) を使用すると、各状態を個別にカプセル化できるため、システム構造がより明確になります。
まず例のゲーム シーンを構築しましょう。
キャラクター モデルと一連のキャラクター アニメーションを含むマテリアル Character Pack: Free Sample をインポートします。ここでは、アイドル、ラン、およびウェーブ アニメーションを使用して
、アニメーター コンポーネントをキャラクターに追加します。ブール値 IsRun はアイドルとランの切り替えに使用され、トリガー Wave はアイドルとウェーブの切り替えに使用されます。
1 メソッドとしての状態のカプセル化
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum State {
idle,
run,
wave
}
public class TestFSM_1 : MonoBehaviour
{
private Animator ani;
private State state = State.idle;
// Start is called before the first frame update
void Start()
{
ani = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
switch (state) {
case State.idle:
idle();
break;
case State.run:
run();
break;
case State.wave:
wave();
break;
}
}
void idle() {
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(horizontal, 0, vertical);
if (dir == Vector3.zero) {
ani.SetBool("IsRun", false);
} else {
state = State.run;
}
if (Input.GetKeyDown(KeyCode.Space)) {
state = State.wave;
}
}
void run() {
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(horizontal, 0, vertical);
if (dir != Vector3.zero) {
ani.SetBool("IsRun", true);
transform.rotation = Quaternion.LookRotation(dir);
transform.Translate(Vector3.forward * 3 * Time.deltaTime);
} else {
state = State.idle;
}
}
void wave() {
ani.SetTrigger("Wave");
if (!ani.GetCurrentAnimatorStateInfo(0).IsName("wave")) {
state = State.idle;
}
}
}
まず、列挙型 State を作成して、各状態の名前を保存します。
ここでは、idle() run() wave() メソッドを作成します。idle() メソッドで、プレイヤーが矢印キーを押して状態を State.run に切り替え、スペースを押すと状態が State.wave に切り替わったことが判明した場合
run()メソッド内でキャラクターの移動を実装しており、方向が(0,0,0)の場合はプレイヤーが矢印キーを離してアイドル状態に切り替わることを意味します。
wave() メソッドでトリガーウェーブを設定します。ここでは、手を振るアニメーションが終了したときに状態をアイドル状態に戻したいと考えています。GetCurrentAnimatorStateInfo メソッドを使用して判断できます。
2 状態をクラスとしてカプセル化する
状態基本クラス
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class FSMState
{
public int StateID;
public MonoBehaviour Mono;
public FSMManager Manager;
public FSMState(int stateID, MonoBehaviour mono, FSMManager manager) {
StateID = stateID;
Mono = mono;
Manager = manager;
}
public abstract void OnEnter();
public abstract void OnUpdate();
}
状態基本クラスは状態の ID (つまり、State 列挙体の値) を保存します。Mono には状態を使用するゲーム オブジェクトが含まれ、Manager は状態のマネージャーです (コード ビハインドで実装されます)。
OnEnter (状態に入るときに実行されるメソッド) と OnUpdate (状態内のフレームごとに呼び出されるメソッド) は、状態基本クラスで定義されます。これら 2 つのメソッドはサブクラスで実装されます
FSManager クラス
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FSMManager
{
public List<FSMState> StateList = new List<FSMState>();
public int CurrentIndex = -1;
public void ChangeState(int StateID) {
CurrentIndex = StateID;
StateList[CurrentIndex].OnEnter();
}
public void Update() {
if (CurrentIndex != -1) {
StateList[CurrentIndex].OnUpdate();
}
}
}
このクラスでは、リスト StateList を使用して各状態を保存します。ChangeState メソッドは状態を変更するために使用され、新しい状態の OnEnter メソッドは状態が変更された後に 1 回呼び出されます。Update メソッドは、現在の状態の OnUpdate を実行するために使用されます。
これら 2 つのクラスを作成した後、ステート マシン フレームワークを使用してゲーム プログラムを実装し始めます。
1 アイドル状態
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IdleState : FSMState
{
public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {
}
public override void OnEnter() {
Mono.GetComponent<Animator>().SetBool("IsRun", false);
}
public override void OnUpdate() {
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(horizontal, 0, vertical);
if (dir != Vector3.zero) {
Manager.ChangeState((int)State.run);
}
if (Input.GetKeyDown(KeyCode.Space)) {
Manager.ChangeState((int)State.wave);
}
}
}
残りの状態の場合は、OnEnter で IsRun パラメータを false に設定します。OnUpdate メソッドで、矢印キーが押されて状態が State.run に切り替わったかどうか、およびスペースが押されて状態が State.wave に切り替わったかどうかを検出します。
2 実行状態
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RunState : FSMState
{
public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {
}
public override void OnEnter() {
Mono.GetComponent<Animator>().SetBool("IsRun", true);
}
public override void OnUpdate() {
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(horizontal, 0, vertical);
if (dir != Vector3.zero) {
Mono.transform.rotation = Quaternion.LookRotation(dir);
Mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
} else {
Manager.ChangeState((int)State.idle);
}
}
}
OnEnter メソッドで、アニメーター パラメーター IsRun を true に設定します。プレイヤーが矢印キーを押して状態をアイドル状態に切り替えなかった場合、OnUpdate メソッドでキャラクターの動きを制御します。
3 波形状態
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaveState : FSMState
{
public WaveState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {
}
public override void OnEnter() {
Mono.GetComponent<Animator>().SetTrigger("Wave");
}
public override void OnUpdate() {
if (!Mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("wave")) {
Manager.ChangeState((int)State.idle);
}
}
}
OnEnter メソッドで Wave Trigger をトリガーします。OnUpdate で現在のアニメーションを検出し、ウェーブ アニメーションの再生後に状態をアイドル状態に変更します。