Unityのもう1つのシンプルなステートマシン





Unityのステートマシン(ステートマシン)の別の実施形態を共有したいと思います。 UnityやC#と組み合わせた有限状態マシンに関する記事はすでにHabréに掲載されています。たとえば、 ここにありますが、Unityコンポーネントの使用に基づいて少し異なるアプローチを示したいと思います。



記事のコード使用しunitypackageにリンクします



ステートマシンが何であるかまだわからない場合
のどが渇いた定義については、ウィキペディアへのリンクを提供します。



状態マシン (ロシア語)

有限状態マシン (英語)



ゲームの例を使用して、簡単な言語で説明しようとします。



ステートマシンは、たとえばキャラクターの状態など、 一連の状態です。

  • アイドル(残り)
  • 走る
  • ジャンプ
  • 戦う
  • 死んだ


現在の時点でアクティブにできるのは1つだけです。 つまり、示されたリストに従って、キャラクターは次のことができます。

  • リラックスするか
  • 走るか
  • ジャンプするか
  • どちらかの戦い
  • 死んでいるか


そして、その間の遷移は 、たとえば次のような所定の条件が満たされたときに実行されます

  • レスト->モーションキーが押された場合に実行
  • Rest->ジャンプキーが押されたらジャンプ
  • 敵と衝突した場合は、実行->戦闘
  • ファイト->体力がなくなったら死ぬ
  • ...


明確にするために、上記の有限状態マシンをグラフ形式で想像してください。緑は状態マシンの初期状態、赤は最終状態です。









実装



ステートマシンの実装は、同じ名前のファイルにあるStateMachineStateTransitionの3つのクラスで構成されます。 3つのクラスはすべてMonoBehaviourから継承されますStateMachineクラスは直接使用されますが、抽象StateおよびTransitionから特定の状態および遷移を継承することが提案されています。 その結果、ステートマシン自体とそのすべての状態と遷移はコンポーネントであり、シーン内のオブジェクトに割り当てる必要があります。 さて、状態を切り替えるには、コンポーネントのオン/オフを切り替える既存のメカニズム( enabledプロパティ)を使用できます。 これにより、ステートマシン、オン/オフチェックなどに特化したコールバックを作成する必要がなくなります。 代わりに、通常のUnityイベント関数OnEnableOnDisableUpdateAwakeなどが使用されます。 確かに、2つの微妙な点があります。



  1. Startイベントには注意する必要があります。最初は、ステートマシンの状態と遷移を「オフ」にする必要があります。オフのコンポーネントの場合、このイベントはシーンの開始時ではなく、初めて「オン」になったときに発生します。 これがUnityの標準的な動作です。
  2. 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つのスクリプトが必要です。これはタイマー遷移クラスです。

タイマー遷移
 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"); } }
      
      



状態用の3つのクラス。 TransformコンポーネントのTranslateメソッドを使用してオブジェクトを移動するモーション状態の基本クラス:

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オブジェクトを選択すると、インスペクターで状態遷移をリアルタイムで監視できます。



おわりに



結論として、このステートマシンの実装には欠点がないわけではありませんが、小規模なプロジェクトでの使用には非常に適しています。 私の意見では、より大規模なプロジェクトの場合、インスペクターでコンポーネントをドラッグアンドドロップするのはかなり不快な仕事です。



建設的な批判は大歓迎です。



All Articles