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

おわりに


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

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

Source: https://habr.com/ru/post/J242977/


All Articles