[English]
以下のように戻り値がUniTaskになっているので、非同期に対応したステートマシーンを利用することができます。
public interface IState
{
void Init(BaseNode baseNode);
UniTask OnEnter(CancellationToken ct = default);
UniTask OnUpdate(CancellationToken ct = default);
UniTask OnExit(CancellationToken ct = default);
}
そのため以下の特徴があります
- OnEnterが終了するまでOnUpdateのループが始まらない
- OnExitが終了するまで次のステートに移行しない
- OnUpdateは1フレームごとに呼ばれるがawaitしている間は呼ばれない
またステートの移行方法は以下を対応しています
- Transition(矢印)に判定を持たせ、条件が一致したときに移行
- TransitionIDを指定して次のステートに移行
- 現在のステートが指定されたTransitionIDを持っていない場合は無視される
Editorツールも提供しているので、簡単にステートを組むことが可能です。
(スクリプト上で組むことも可能です)
パッケージ内でUniTaskを使用しているので、UniTaskがプロジェクト内に追加されている必要があります。
PackageManager,unitypackageのどちらで追加しても動作します。
UniRxから分割される前のUniTask(UniRx.Async)を使用している場合は、
ProjectSettings/Player/OtherSettings/ScriptingDefineSymbolで以下を定義することで使用可能になります。
BG_USE_UNIRX_ASYNC
Window/Package Managerを開き、add package from git URL...で以下を入力して追加してください。
https://github.com/k-okawa/UniTaskStateMachine.git?path=Assets/Bg/UniTaskStateMachine
openupm add com.bg.unitaskstatemachine
リリースページからダウンロード可能です。
StateMachineBehaviourをAddComponentします。
1で追加したStateMachineBehaviourのGraphEditorOpen、
またはWindow/BG UniTaskStateMachine/StateMachineGraphでグラフエディタを開くことができます。
StateMachineBehaviourがアタッチされているGameObjectにBaseStateComponentを継承したComponentを追加します。
※BaseStateComponentを継承したクラスをさらに継承はしないでください。
※BaseStateComponentを直接AddComponentしないでください。
※同じStateComponentを同じゲームオブジェクトにAddComponentしないでください。
例
namespace Bg.UniTaskStateMachine.Tests.BasicSceneTest
{
public class StartState : BaseStateComponent
{
public override async UniTask OnEnter(CancellationToken ct = default)
{
}
public override async UniTask OnUpdate(CancellationToken ct = default)
{
}
public override async UniTask OnExit(CancellationToken ct = default)
{
}
}
}
Graphエディタ上で右クリックし、CreateStateでState追加
追加されているクラス名が表示され選択可能になります。
Noneを選択した場合何もしないステートになります。
追加されているStateの上で右クリックし、MakeTransitionを選択することでTransitionを追加することができます。
StateMachineBehaviourがアタッチされているGameObjectのComponentの中のpublicで戻り値がbool、引数なしの関数をステートの遷移条件として使用することができます。
IsNegativeにチェックを入れることで条件を反対にすることができます。
またMethodNameの指定をNoneにすると、常に条件を満たさないTransitionになります。
後述するTriggerNextTransitionを呼び出すことでスクリプトから強制的に遷移を実行することも可能です。
最初に実行するStateを必ず指定する必要があります。
Stateを右クリックし、Set as Entryを選択することで設定することができます。
トランジションに条件を指定する以外にも、IDを指定してステートを遷移させることが可能です。
トランジションIDはC#のフィールド名で使用可能なアッパーキャメルケースを推奨します。
すべてのトランジションIDを決めた後、GenerateTransitionIdConstボタンを使用して定数のテンプレートコードを生成できます。
最後に、"StateMachine.TriggerNextTransition(string transitionId)"を呼び出すだけで好きなタイミングでステート遷移が可能です
引数に文字列を直接渡すことができますが、生成されたクラスの読み取り専用のフィールドで渡すことを推奨します。
StateMachineBehaviourのStateMachineプロパティからアクセスできます。
// エントリーステート
public BaseNode EntryNode;
// 現在のステートノード
public BaseNode CurrentNode { get; private set; }
// 現在のStateMachineの実行状態(STOP,START,PAUSE)
public State CurrentState { get; private set; } = State.STOP;
// OnUpdateを呼ぶタイミング
public PlayerLoopTiming LoopTiming = PlayerLoopTiming.Update;
/// <summary>
/// ステートマシンを開始する
/// </summary>
public async void Start();
/// <summary>
/// ステートマシーンを完全に停止する
/// </summary>
public void Stop();
/// <summary>
/// 現在のステートを一時停止状態にする
/// </summary>
public void Pause();
/// <summary>
/// 現在のステートを再開する
/// </summary>
public void Resume();
/// <summary>
/// エントリーステートからステートマシンを再実行
/// </summary>
public async UniTask ReStart(CancellationToken ct = default);
/// <summary>
/// 強制的に次のステートに遷移させる
/// </summary>
/// <param name="transitionId">Graphエディターで指定したtransitionId</param>
public void TriggerNextTransition(string transitionId);
/// <summary>
/// 現在実行中のステートと等しいかどうか調べる
/// </summary>
/// <param name="type">ステートタイプ</param>
/// <returns>現在のステートが引数のtypeと一致した場合trueを返す</returns>
public bool IsMatchCurrentStateType(Type type);
/// <summary>
/// ほとんどIsMatchCurrentStateTypeと同じ
/// 違いは引数が可変長引数になっていること
/// </summary>
/// <param name="types">ステートタイプ</param>
/// <returns>現在のステートが引数のいずれかのtypeと一致した場合trueを返す</returns>
public bool IsMatchAnyCurrentStateType(params Type[] types);
// 現在のステートのNode(StateとTransitionが一緒になっているもの)
protected BaseNode baseNode;
public virtual void Init(BaseNode baseNode);
public virtual async UniTask OnEnter(CancellationToken ct = default);
public virtual async UniTask OnUpdate(CancellationToken ct = default);
public virtual async UniTask OnExit(CancellationToken ct = default);
public readonly string Id;
public readonly StateMachine StateMachine;
public bool IsUpdate { get; private set; } = true;
/// <summary>
/// いずれかの遷移条件がマッチしているか
/// </summary>
/// <returns>ひとつでも遷移条件にマッチしているものがあればtrueを返す</returns>
public bool IsMatchAnyCondition();
/// <summary>
/// TriggerNextTransitionによって強制的に遷移する状態になっているか
/// </summary>
/// <returns>ひとつでも強制的に遷移する状態のものがあればtrueを返す</returns>
public bool IsExistForceTransition();
/// <summary>
/// Graphエディター上の矢印(Transition)の基底クラスを取得する
/// </summary>
/// <param name="id">Graphエディター上のTransitionId</param>
public BaseCondition GetCondition(string id);
/// <summary>
/// Nodeが持っているTransitionのIdをすべて取得する
/// </summary>
public IEnumerable<string> GetTransitionIds();
public BaseNode NextNode { get; }
public Func<bool> ConditionCheckCallback { get; }
public string TransitionId { get; }
public bool IsNegative => isNegative;
public bool IsForceTransition => isForceTransition;