条件は

こんにちは、ハブロフスク! 前回の記事では、チームとそれらの使用方法について説明しましたが、今日はトピックを開発し、チームをステートマシンに接続する方法を説明します。 ハブに関するトピックは新しいものではないため、有限状態マシンとは何か、なぜ使用されるのかについては説明しませんが、実装に焦点を当てます。 チームは状態としてほとんど変更なしで使用されるため、すぐに理解するために前の記事を読むことをお勧めします。 始める前に、彼のコメントについてOnionFanに感謝したいと思います-すべての習慣が良いわけではなく、彼の質問は状態マシンのタイピングをより便利にしました。



問題

前回の記事へのコメントで、この例はあまり適切に選択されておらず、誰もが真剣に受け止めたわけではないという考えがあったため、少し振り返ってから、より実用的な意味を持つ例を選択することにしました。 したがって、今日の例はもう少し高いレベルであり、ゲームプレイ、より具体的にはほとんどのゲームシーンが通過する状態に関連します。

すぐに、各ゲームシーンが必ず通過する少なくとも3つのステージに名前を付けることができます:リソースとモデルの初期化、ゲームの状態自体(たとえば、ゲームメカニックが変化したり、シーンがカットされた場合、いくつかの異なる状態に分割できます)そして、ゲームの完了状態(進行状況の保存とリソースの解放)。 これは、特定のメソッドの呼び出しを延期したマネージャーのコルーチン、またはエディターを介したAwake()メソッドの呼び出し順序の微調整、または各Update()でシーンの準備状況が単純にチェックされることで解決される状況をよく目にします。 しかし、推測しやすいように、有限状態マシンを使用して、より快適でエレガントな方法を提供します。 すでにこの段階で、各段階がチーム(サブコマンドを使用することもできます)として配置され、現在の段階が完了した後にのみ次の段階に進むことが簡単にわかります。 また、ほとんどの場合コントローラーにアクセスする必要があるため、状態はコマンドを入力することに同意します。 すでにコードを書いてみましょう。または、どういうわけか大量の水があります。

シンプルだが既に型付けされたステートマシンクラスから始めましょう

コード
public class StateMachine<T> where T : MonoBehaviour { private readonly T _stateMachineController; private Command _currentState; public StateMachine (T stateMachineController) { this._stateMachineController = stateMachineController; } public TCommand ApplyState<TCommand> (params object[] args) where TCommand : CommandWithType<T> { if (_currentState != null) _currentState.Terminate (); _currentState = Command.ExecuteOn<TCommand> (_stateMachineController.gameObject, _stateMachineController, args); return _currentState as TCommand; } }
      
      







異常なことはありません。以前の状態がある場合は停止し、コントローラーオブジェクトで新しい状態を開始し、現在の状態として記憶し、念のために返します。

今すぐコントローラー
 public class SceneController : StateMachineHolder { public StateMachine<SceneController> StateMachine { get; private set; } public SceneController () { StateMachine = new StateMachine<SceneController> (this); } private void Start() { StateMachine.ApplyState<InitializeState> (); } }
      
      







ここでもすべてが簡単です。オートマトンオブジェクトのパブリックgeterで、Start()で初期化状態に移行します。 したがって、コントローラーのStart()はシーンへのエントリポイントになり、すべての状態の正しい呼び出しシーケンスに完全な信頼を与えます。

そしてすぐにシーンの状態の空白と最初の2つの状態のほとんど空のクラス:

あなたも見ることができません
 public class SceneState: CommandWithType<SceneController> { } class InitializeState : SceneState { protected override void OnStart (object[] args) { base.OnStart (args); //test UnityEngine.Debug.Log(string.Format("{0}", "Initialize state")); Controller.StateMachine.ApplyState<ReadyState> (); } } class ReadyState : SceneState { protected override void OnStart (object[] args) { base.OnStart (args); //test UnityEngine.Debug.Log(string.Format("{0}", "ready state")); } }
      
      







このアプローチでのゲームの状態は、初期化が完了して初めて満たされるようになると信じるのは簡単です。



どういうわけか少し判明した

ハリネズミゲームの状態自体は、上記の例のように単純ではないことは明らかです。 たとえば、ゲームの状態では、ポイントのカウント、UIの状態の更新、対戦相手とコインの作成、カメラの移動などが必要です。 そして、このコードをすべてゲームステートクラスに直接記述した場合、なぜ私はここにいるのでしょうか?

スコアリングを例にとってみましょう。 このために別のコマンドを記述し、ゲーム状態で起動します(MVCに精通するまで、スコアをコントローラーに直接記録します)。

プリミティブカウント
 public class UpdateScoreCommand : SceneState { protected override void OnStart (object[] args) { base.OnStart (args); StartCoroutine (UpdateScore()); } private IEnumerator UpdateScore () { while (true) { if (!IsRunning) yield break; yield return new WaitForSeconds (1); Controller.Score++; } } }
      
      







ゲームの状態
  class ReadyState : SceneState { private UpdateScoreCommand _updateScoreCommand; protected override void OnStart (object[] args) { base.OnStart (args); //test UnityEngine.Debug.Log(string.Format("{0}", "ready state")); _updateScoreCommand = Command.ExecuteOn<UpdateScoreCommand> (Controller.gameObject, Controller); } protected override void OnReleaseResources () { base.OnReleaseResources (); _updateScoreCommand.Terminate (); } }
      
      







私は、状態の開始と比較して、countコマンドの面倒な開始にすでに困惑しています。 また、実行中のすべてのコマンドへのすべてのリンクを常に維持する必要があるため、少なくとも状態クラスを押し下げて混乱させます。 もちろん、一部のチームへのリンクは維持する必要がありますが、得点の場合、チームはゲームの状態が終了するまで動作し、状態の遷移の瞬間に実行を停止して、追加しすぎないようにする必要があります。 ステートマシン自体でこのようなコマンドを簡単に追跡でき、完了時に実行中のすべてのコマンドをステートから停止するように指示できます。 彼にこの責任を負わせましょう。

StateMachine vol。 2.0
 public class StateMachine<T> where T : MonoBehaviour { private readonly T _stateMachineController; private Command _currentState; private List<CommandWithType<T>> _commands; public StateMachine (T stateMachineController) { this._stateMachineController = stateMachineController; _commands = new List<CommandWithType<T>> (); } public TCommand ApplyState<TCommand> (params object[] args) where TCommand : CommandWithType<T> { if (_currentState != null) _currentState.Terminate (true); StopAllCommands (); _currentState = Command.ExecuteOn<TCommand> (_stateMachineController.gameObject, _stateMachineController, args); return _currentState as TCommand; } public TCommand Execute<TCommand> (params object[] args) where TCommand : CommandWithType<T> { TCommand command = Command.ExecuteOn<TCommand> (_stateMachineController.gameObject, _stateMachineController, args); _commands.Add (command); return command as TCommand; } private void StopAllCommands() { for (int i = 0; i < _commands.Count; i++) { _commands [i].Terminate (); } } }
      
      







ここで、ApplyState()メソッドを使用して状態を開始し、Execute()メソッドを使用してこの状態でコマンドを実行し、状態が完了すると、実行中のすべてのコマンドを自動的に終了します。 そして、それは補助コマンドの呼び出しをより良くしました

呼び出しサブコマンド
  class ReadyState : SceneState { protected override void OnStart (object[] args) { base.OnStart (args); //test UnityEngine.Debug.Log(string.Format("{0}", "ready state")); Controller.StateMachine.Execute<UpdateScoreCommand> (); } }
      
      







これで、補助コマンドを実行して忘れることができるようになり、マシンは時間が来るとそれらを記憶します。

すべてがシンプルかつ美しくなったため、コールの管理とチームの停止に最低限の注意を払う必要があり、すべてが適切なタイミングで合格することが保証されています。



少しの喜び

ステートマシンは完全に使用する準備ができており、1つの小さな便利さについて話すだけです。 この実装では、状態間の遷移を状態自体に記録する必要があり、これは分岐または意思決定システムに非常に便利です。 ただし、状態ツリーがそれほど複雑ではない場合があります。この場合、状態のチェーン全体を1か所に登録すると便利です。

この機能を追加する前に、状態はチーム以外の何物でもないことを思い出してください。実装のチームには、成功と失敗の2つの結果があります。 これは、単純な動作ツリーを構築するのに十分であり、ループ(撮影、リロード、撮影、そして誰がいるのかを尋ねる)の可能性もあります。

コマンドを呼び出す方法のため、必要なすべてのコマンドをすぐにインスタンス化し、必要なときにそれらを使用することはできません。 したがって、必要なコマンドのタイプのリストの形式でチェーン(またはツリー)全体を格納するという事実に焦点を当てます。 しかし、最初に、そのようなシステムでは、コマンドクラスをわずかに修正して、型付きの呼び出しメソッドだけでなく、目的のコマンドのタイプとコマンドを完了するための成功フラグを渡すことができるメソッドを持たせる必要があります。

チームの変更のみを行います
  public bool FinishResult { get; private set; } public static T ExecuteOn<T>(GameObject target, params object[] args) where T : Command { return ExecuteOn (typeof(T), target, args) as T; } public static Command ExecuteOn(Type type, GameObject target, params object[] args) { Command command = (Command)target.AddComponent (type); command._args = args; return command; } protected void FinishCommand(bool result = true) { if (!IsRunning) return; OnReleaseResources (); OnFinishCommand (); FinishResult = result; if (result) CallbackToken.FireSucceed (); else CallbackToken.FireFault (); Destroy (this, 1f); }
      
      







説明することは何もないので、説明しません。 次に、ターゲットコマンドのタイプと、ターゲットが正常に完了した場合と失敗した場合の次のコマンドのタイプを含むコンテナを作成しましょう。

蒸気容器
  public sealed class CommandPair { public readonly Type TargetType; public readonly Type SuccesType; public readonly Type FaultType; public CommandPair (Type targetType, Type succesType, Type faultType) { this.TargetType = targetType; this.SuccesType = succesType; this.FaultType = faultType; } public CommandPair (Type targetType, Type succesType) { this.TargetType = targetType; this.SuccesType = succesType; this.FaultType = succesType; }
      
      







次のコマンドの1つのタイプのみがコンストラクターに渡される場合、分岐はなく、対応するタイプのコマンドがターゲットコマンドの結果で呼び出されることに注意してください。

これで、キューはペアのコンテナに移動します。

コンテナコンテナ
  public sealed class CommandFlow { private List<CommandPair> _commandFlow; public CommandFlow () { this._commandFlow = new List<CommandPair>(); } public void AddCommandPair(CommandPair commandPair) { _commandFlow.Add (commandPair); } public Type GetNextCommand(Command currentCommand) { CommandPair nextPair = _commandFlow.FirstOrDefault (pair => pair.TargetType.Equals (currentCommand.GetType ())); if (nextPair == null) return null; if (currentCommand.FinishResult) return nextPair.SuccesType; return nextPair.FaultType; } }
      
      







コマンドのペアを保存することに加えて、このコンテナは現在の状態で次に登録されている状態も検索します。 状態を変更できるように、実行順序を状態マシンにバインドするだけです。

StateMachine vol。 3.0
 public class StateMachine<T> where T : MonoBehaviour { private readonly T _stateMachineController; private readonly CommandFlow _commandFlow; private Command _currentState; private List<CommandWithType<T>> _commands; public StateMachine (T stateMachineController) { this._stateMachineController = stateMachineController; _commands = new List<CommandWithType<T>> (); } public StateMachine (T _stateMachineController, CommandFlow _commandFlow) { this._stateMachineController = _stateMachineController; this._commandFlow = _commandFlow; _commands = new List<CommandWithType<T>> (); } public TCommand ApplyState<TCommand> (params object[] args) where TCommand : CommandWithType<T> { return ApplyState (typeof(TCommand), args) as TCommand; } public Command ApplyState(Type type, params object[] args) { if (_currentState != null) _currentState.Terminate (true); StopAllCommands (); _currentState = Command.ExecuteOn (type ,_stateMachineController.gameObject, _stateMachineController, args); _currentState.CallbackToken.AddCallback (new Callback<Command>(OnStateFinished, OnStateFinished)); return _currentState; } private void OnStateFinished (Command command) { if (_commandFlow == null) return; Type nextCommand = _commandFlow.GetNextCommand (command); if (nextCommand != null) ApplyState (nextCommand); } public TCommand Execute<TCommand> (params object[] args) where TCommand : CommandWithType<T> { TCommand command = Command.ExecuteOn<TCommand> (_stateMachineController.gameObject, _stateMachineController, args); _commands.Add (command); return command as TCommand; } private void StopAllCommands() { for (int i = 0; i < _commands.Count; i++) { _commands [i].Terminate (); } } }
      
      







マシン自体にシーケンスを保持させ、表示されるように状態自体を変更しますが、前に準備したシーケンスなしでマシンを起動する機会を残します。

今では、これをすべて使用することを学ぶだけです。

使用する
  public class SceneController : StateMachineHolder { public int Score = 0; public StateMachine<SceneController> StateMachine { get; private set; } public SceneController () { CommandFlow commandFlow = new CommandFlow (); commandFlow.AddCommandPair (new CommandPair(typeof(InitializeState), typeof(ReadyState), typeof(OverState))); StateMachine = new StateMachine<SceneController> (this, commandFlow); } private void Start() { StateMachine.ApplyState<InitializeState> (); } } class InitializeState : SceneState { protected override void OnStart (object[] args) { base.OnStart (args); //test UnityEngine.Debug.Log(string.Format("{0}", "Initialize state")); FinishCommand (Random.Range (0, 100) < 50); } }
      
      







出来上がり! これで、状態分岐を便利に使用するために、一連のコマンドを登録し、それを状態マシンに転送して最初の状態を実行するだけで、その後はすべてが参加なしで発生します。 トピックは完全に公開されました。 記述がすべて終わった後、堅牢で柔軟で管理しやすい有限状態マシンがあります。 ご清聴ありがとうございました。



All Articles