
Unityのステートマシン(ステートマシン)の別の実施形態を共有したいと思います。 UnityやC#と組み合わせた有限状態マシンに関する記事はすでにHabréに掲載されています。たとえば、 ここにありますが、Unityコンポーネントの使用に基づいて少し異なるアプローチを示したいと思います。
記事のコードを使用してunitypackageにリンクします 。
ステートマシンが何であるかまだわからない場合
のどが渇いた定義については、ウィキペディアへのリンクを提供します。
状態マシン (ロシア語)
有限状態マシン (英語)
ゲームの例を使用して、簡単な言語で説明しようとします。
ステートマシンは、たとえばキャラクターの状態など、 一連の状態です。
現在の時点でアクティブにできるのは1つだけです。 つまり、示されたリストに従って、キャラクターは次のことができます。
そして、その間の遷移は 、たとえば次のような所定の条件が満たされたときに実行されます 。
明確にするために、上記の有限状態マシンをグラフ形式で想像してください。緑は状態マシンの初期状態、赤は最終状態です。
状態マシン (ロシア語)
有限状態マシン (英語)
ゲームの例を使用して、簡単な言語で説明しようとします。
ステートマシンは、たとえばキャラクターの状態など、 一連の状態です。
- アイドル(残り)
- 走る
- ジャンプ
- 戦う
- 死んだ
現在の時点でアクティブにできるのは1つだけです。 つまり、示されたリストに従って、キャラクターは次のことができます。
- リラックスするか
- 走るか
- ジャンプするか
- どちらかの戦い
- 死んでいるか
そして、その間の遷移は 、たとえば次のような所定の条件が満たされたときに実行されます 。
- レスト->モーションキーが押された場合に実行
- Rest->ジャンプキーが押されたらジャンプ
- 敵と衝突した場合は、実行->戦闘
- ファイト->体力がなくなったら死ぬ
- ...
明確にするために、上記の有限状態マシンをグラフ形式で想像してください。緑は状態マシンの初期状態、赤は最終状態です。

実装
ステートマシンの実装は、同じ名前のファイルにあるStateMachine 、 State 、 Transitionの3つのクラスで構成されます。 3つのクラスはすべてMonoBehaviourから継承されます 。 StateMachineクラスは直接使用されますが、抽象StateおよびTransitionから特定の状態および遷移を継承することが提案されています。 その結果、ステートマシン自体とそのすべての状態と遷移はコンポーネントであり、シーン内のオブジェクトに割り当てる必要があります。 さて、状態を切り替えるには、コンポーネントのオン/オフを切り替える既存のメカニズム( enabledプロパティ)を使用できます。 これにより、ステートマシン、オン/オフチェックなどに特化したコールバックを作成する必要がなくなります。 代わりに、通常のUnityイベント関数OnEnable 、 OnDisable 、 Update 、 Awakeなどが使用されます。 確かに、2つの微妙な点があります。
- Startイベントには注意する必要があります。最初は、ステートマシンの状態と遷移を「オフ」にする必要があります。オフのコンポーネントの場合、このイベントはシーンの開始時ではなく、初めて「オン」になったときに発生します。 これがUnityの標準的な動作です。
- Stateから継承する場合、 FixedUpdateメソッドをオーバーライドする必要があります(もちろん必要な場合) 。Stateクラスに実装されているため、状態の「有効化/無効化」チェックボックスは常にインスペクターに表示されます。 このチェックマークを使用すると、状態の切り替えをリアルタイムで観察できます。ほとんどが「ビジュアルデバッグ」です。
最後に、コードに移りましょう(ロシア語のコメント付き):
移行
using UnityEngine; /// . /// (disabled) Inspector'. public abstract class Transition : MonoBehaviour { /// ( ). /// Inspector'. [SerializeField] State targetState; /// . /// State . public State TargetState { get { return targetState; } } /// , /// true. /// State. public bool NeedTransit { get; protected set; } }
都道府県
using UnityEngine; using System.Collections.Generic; /// . /// (disabled) Inspector'. public abstract class State : MonoBehaviour { /// . /// Inspector'. [SerializeField, Tooltip("List of transitions from this state.")] List<Transition> transitions = new List<Transition> (); /// , /// , null. /// StateMachine. public virtual State GetNext() { foreach (var transition in transitions) { if (transition.NeedTransit ) return transition.TargetState; } return null; } /// . /// OnDisable, . public virtual void Exit() { if(enabled) { foreach(var transition in transitions) { transition.enabled = false; } enabled = false; } } /// . /// OnEnable, . public virtual void Enter() { if(!enabled) { enabled = true; foreach(var transition in transitions) { transition.enabled = true; } } } /// , Inspector' /// enabled/disabled . /// . protected virtual void FixedUpdate() { } }
ステートマシン
using UnityEngine; /// . public class StateMachine : MonoBehaviour { /// . /// Inspector'. [SerializeField] State startingState; /// . State current; /// . public State Current { get { return current; } } /// ( ). void Start() { Reset(); } /// . public void Reset() { Transit(startingState); } /// , /// . - . void Update () { if(current == null) return; var next = current.GetNext(); if(next != null) Transit(next); } /// , . /// , /// /// . void Transit(State next) { if(current != null) current.Exit(); current = next; if(current != null) current.Enter(); } }
結果のステートマシンを使用する
画面上でキューブを左右に動かす小さなテストプロジェクトを作成しましょう。 プロジェクトは2Dと3Dの両方で作成できますが、違いは視覚的なものでなければなりません。 シーンを作成するか、デフォルトを使用します。 既にカメラが入っているので、メニューGameObject-> Create Other-> Cubeを使用してキューブを追加します。 キューブは、X軸上の位置を-4に設定する必要があります。これは、キューブが各方向に8単位移動するためです。 キューブに加えて、ステートマシン用に空の子オブジェクトを作成します。 これを行うには、階層でキューブを選択し、メニューGameObject-> Create Empty Childを使用します。 StateMachineに名前を変更すると、より明確になります。
判明します
そのようなもの

次のステップは、スクリプトを作成することです。 4つのスクリプトが必要です。これはタイマー遷移クラスです。
タイマー遷移
状態用の3つのクラス。 TransformコンポーネントのTranslateメソッドを使用してオブジェクトを移動するモーション状態の基本クラス:
using UnityEngine; using System.Collections; /// . public class TimerTransition : Transition { /// . Inspector'. [SerializeField, Tooltip("Time in seconds.")] float time; /// "". /// NeedTransit. void OnEnable() { NeedTransit = false; StartCoroutine("Timer"); } /// , . /// NeedTransit true. IEnumerator Timer() { yield return new WaitForSeconds(time); NeedTransit = true; } /// "". /// . void OnDisable() { StopCoroutine("Timer"); } }
TranslateState
それから継承された特定の状態のクラス:
using UnityEngine; /// Transform Translate. public class TranslateState : State { /// Transform, Inspector'. [SerializeField] Transform transformToMove; /// . Inspector'. [SerializeField, Tooltip("Speed in units per second.")] Vector3 speed; /// Transform. void Update () { var step = speed * Time.deltaTime; transformToMove.Translate(step.x, step.y, step.z); } }
モベイト
/// . /// , /// "". public class MoveRight : TranslateState { }
移動左
/// . /// , /// "". public class MoveLeft : TranslateState { }
必要なクラスがすべて準備できたので、コンポーネントからステートマシンをアセンブルする必要があります。 これを行うには、階層でStateMachineという名前のオブジェクトを選択し、図のようにすべてのコンポーネントをその上にハングさせます。
写真
状態と遷移のコンポーネントを「オフにする」ことを忘れないでください。状態マシン自体は忘れないでください。

次のようにコンポーネントを入力します。
レディ状態マシン
状態と遷移用のフィールドは、対応するコンポーネントをドラッグアンドドロップすることで入力できます。 StartingStateステートマシンを設定し、遷移状態リストに遷移を追加することを忘れないでください!

これで、シーンを開始できます。 すべてが正しく行われると、キューブは画面を左右に移動します。 HierarchyでStateMachineオブジェクトを選択すると、インスペクターで状態遷移をリアルタイムで監視できます。
おわりに
結論として、このステートマシンの実装には欠点がないわけではありませんが、小規模なプロジェクトでの使用には非常に適しています。 私の意見では、より大規模なプロジェクトの場合、インスペクターでコンポーネントをドラッグアンドドロップするのはかなり不快な仕事です。
建設的な批判は大歓迎です。