メインコンテンツへスキップ

ステートマシン

はじめに

ステート・マシンは、状態管理を単純化するために設計されている。

ステートマシンは常に正確に1つの状態にあり、(トリガーによって定義された)特定の条件が満たされたときに状態間を遷移する。 ステート・グループは、複数のステート間で共有されるロジックを束ねる便利な方法だが、グループそのものはステートではない。 ステートと同じAPIを提供するが、すべてのサブステートに動作とトリガーを分散させる。

ステートマシンは3つの主要コンポーネントから構成される:

  • グループ
  • トリガー

ステートマシン

ステートマシンの定義

ステートマシン定義器

コンポーネント内部にステートマシンを作成する場合、StateMachineDefinerのインスタンスが使用されます。

プロパティ一覧
PropertyType商品説明
worldWorld世界への言及。
eideid現在のコンポーネントのエンティティID。
schemaAttributeWorldAttributeワールドスコープにおける現在のコンポーネントのスキーマへの参照。
dataAttributeWorldAttributeワールドスコープにおける現在のコンポーネントのデータへの参照。

次のコードは、空のステート・マシンを定義する方法の例である:

ecs.registerComponent({
...
stateMachine: ({world, eid}) => {
// ここで状態を定義する
},
})

ステートマシン定義

あるいは、コンポーネントから独立したステート・マシンを作成することもできる。

コンポーネントの外部でステート・マシンを作成する場合、StateMachineDefinition のインスタンスが使用されます。

プロパティ一覧
PropertyType商品説明
initialState (必須)stringステートマシンの開始状態の名前
states (必須)Record<string, State>州名とその定義を格納するマップ
groupsStateGroup[]州グループのリスト(オプション)。
const stateMachine = {
initialState: 'a'
states: {
'a': {
onExit: () => console.log('exit a'),
trigger: {
'b': [{ type: 'timeout', timeout: 1000 }],
},
},
'b': { onEnter: () => console.log('enter b') },
},
groups: [{
substates: ['a', 'b'],
listeners: [{
target: world.events.globalId,
name: ecs.input.SCREEN_TOUCH_START,
listener: (event) => console.log('touch'),
}].
}],
}

状態

ステートとは、ステートマシンの基本的な原子単位である。 これらは、直接定義することもできるし、上記の流暢なStateMachineDefiner APIを使って定義することもできる。 ステートマシンは、常に正確に1つの状態にあり、現在の状態に関連するユーザー定義のトリガーに従って遷移する。

プロパティ一覧

PropertyType商品説明
triggers (必須)Record<string, Trigger[]>ターゲット状態によってインデックス付けされた、発信トランジション
onEnter() => void状態移行時に呼び出される関数
onTick() => void状態にある間、毎フレーム呼び出される関数
onExit() => void状態を終了するときに呼ばれる関数
listenersListenerParams[]イベント・リスナーのパラメータ。入力時に自動的に追加され、終了時に削除される

州の定義

次のコードは、コンポーネント内のステート・マシン内で新しいステートを定義する方法の例です。

ecs.registerComponent({
...
stateMachine: ({world, eid}) => {
const foo = ecs.defineState('foo')
...
}
})

ID

StateIdsは遷移先の指定に使用される。 StateDefinerまたは状態名そのものを文字列で指定します。

const a = ecs.definestate('a').wait(1000, 'b')
const b = ecs.defineState('b').wait(1000, a)
tip

StateDefiner関数は「フルエント」、つまり同じインスタンスを返すので、1つのステートメントで複数の関数呼び出しを連鎖させることができる。

.initial()

ステートマシンの作成時に、この状態をステートマシンの現在の状態としてマークする。

ecs.defineState('myCustomState').initial()

.onEnter()

この状態になったときに実行するコールバックを設定する。

ecs.defineState('myCustomState').onEnter(() => {
// 何かをする
})

.onTick()

毎フレーム実行するコールバックを設定する。

ecs.defineState('myCustomState').onTick(() => {
// 何かをする
})

.onExit()

この状態を終了するときに実行するコールバックを設定します。

ecs.defineState('myCustomState').onExit(() => {
// 何かをする
})

.onEvent()

特定のイベントが呼び出されたときに遷移できるEventTriggerを、この状態から別の状態に追加するために呼び出します。

プロパティ一覧
パラメーターType商品説明
event (必須)stringリッスンするイベント名
nextState (必須)stateIdイベント発生時に遷移する状態
argsobject移行条件の決定に使用される引数
アーギュ
パラメーターType商品説明
targeteidイベントを受け取ることが期待されるエンティティ(デフォルトはステートマシンの所有者)
where(QueuedEvent) => booleanトランジションする前にチェックするオプションの条件。
ecs.defineState('myCustomState').onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)

.wait()

この状態から、設定された時間後に遷移する別の状態にTimeoutTriggerを追加するためのコール。

パラメーターType商品説明
timeoutnumber遷移するまでの時間(ミリ秒単位
nextStateStateId次に遷移する状態
ecs.defineState('myCustomState').wait(1000, 'myOtherCustomState')

.onTrigger()

CustomTriggerをこのステートから別のステートへ追加するコール。 ecs.defineTrigger()を使用して、手動で起動できるTriggerHandleを作成します。

パラメーターType商品説明
handleTriggerHandle手動で作動させたときにトランジションを起こすハンドル
nextStateStateId次に遷移する状態
const toOther = ecs.defineTrigger()
ecs.defineState('example').onTrigger(toOther, 'other')
...
toOther.trigger()

.listen()

このステートのリスナーセットにListenerParamsを追加するコール。 イベント・リスナーは、ステートに入ると自動的に追加され、ステートが終了すると削除される。

パラメーターType商品説明
targeteid または () => eidイベントを受信すると予想されるエンティティ
namestring注目のイベント
listener(QueuedEvent) => voidイベントがディスパッチされたときに呼び出される関数
const handleCollision = (event) => { ... }
ecs.defineState('example').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

州グループ

ステート・グループの定義

ステートグループ定義者

コンポーネント内にステートグループを作成する場合、StateGroupDefinerのインスタンスが使用されます。

パラメーターType商品説明
substates (必須)StateId[]このグループを構成する州のリスト。このパラメータを除外すると、すべての州をリストアップすることになる。
const fizz = ecs.defineState('fizz')
const buzz = ecs.defineState('buzz')

const fizzBuzz = ecs.defineStateGroup([fizz, 'buzz'])
tip

StateGroupDefiner関数は "fluent"、つまり同じインスタンスを返すので、1つのステートメントで複数の関数呼び出しを連鎖させることができます。

.onEnter()

このグループに入るときに実行するコールバックを設定します。

ecs.defineStateGroup(['a', 'b']).onEnter(() => {
// 何かをする
})

.onTick()

毎フレーム実行するコールバックを設定する。

ecs.defineStateGroup(['a', 'b']).onTick(() => {
// 何かをする
})

.onExit()

このグループから抜けるときに実行するコールバックを設定します。

ecs.defineStateGroup(['a', 'b']).onTick(() => {
// 何かをする
})

.onEvent()

このグループ内の任意のステートから、特定のイベントが呼び出されたときに遷移できる他のステートへのEventTriggerを追加するためのコール。

プロパティ一覧
パラメーターType商品説明
event (必須)stringリッスンするイベント名
nextState (必須)stateIdイベント発生時に遷移する状態
argsobject移行条件の決定に使用される引数
アーギュ
パラメーターType商品説明
targeteidイベントを受け取ることが期待されるエンティティ(デフォルトはステートマシンの所有者)
where(QueuedEvent) => booleanトランジションする前にチェックするオプションの条件。
ecs.defineStateGroup(['a', 'b']).onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)

.wait()

このグループ内の任意のステートから、設定された時間後に遷移する他のステートへのTimeoutTriggerを追加するためのコール。

パラメーターType商品説明
timeoutnumber遷移するまでの時間(ミリ秒単位
nextStateStateId次に遷移する状態
ecs.defineStateGroup(['a', 'b']).wait(1000, 'c')

.onTrigger()

CustomTriggerを、このグループ内の任意のステートから、ユーザーがいつでもすぐに遷移できる他のステートへ追加するためのコール。 ecs.defineTrigger()を使用して、手動で起動できるTriggerHandleを作成します。

パラメーターType商品説明
handleTriggerHandle手動で作動させたときにトランジションを起こすハンドル
nextStateStateId次に遷移する状態
const toC = ecs.defineTrigger()
ecs.defineStateGroup(['a', 'b']).onTrigger(toC, 'c')
...
toC.trigger()

.listen()

このステートのリスナーセットにListenerParamsを追加するコール。 イベント・リスナーは、ステートに入ると自動的に追加され、ステートが終了すると削除される。

パラメーターType商品説明
targeteid または () => eidイベントを受信すると予想されるエンティティ
namestring注目のイベント
listener(QueuedEvent) => voidイベントがディスパッチされたときに呼び出される関数
const handleCollision = (event) => { ... }
ecs.defineState('example').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

トリガー

さまざまな状況下で、移行には複数のトリガーがある。

ハンドル

状態間の任意の遷移を定義するためのオブジェクト。 ecs.defineTriggerで作成し、onTriggerまたはCustomTriggerで使用します。

パラメーターType商品説明
trigger()() => voidアクティブなCustomTriggerの遷移を発生させるために、この関数を呼び出します。
const go = ecs.defineTrigger()
const stopped = ecs.defineState('stoppe').onTick(() => {
if (world.input.getAction('start-going')){
go.trigger()
}
}).onTrigger(go, 'going')
const going = ecs.defineState('going')

種類

イベントトリガー

EventTriggersは、指定されたイベントが呼び出されたときに、任意で遷移するために使用される。 イベントデータは、トランジションするかどうかの実行時の判断に使用できる。

パラメーターType商品説明
type (必須)'event'トリガーのタイプを示す定数
event (必須)stringリッスンするイベント名
targeteidイベントを受信すると予想されるエンティティ
where(QueuedEvent) => boolean遷移する前にチェックするオプションの述語。
const example = {
trigger:
'other': [
{
type: 'event',
event: ecs.input.SCREEN_TOUCH_START,
target: world.events.globalId
where: (event) => event.data.position.y > 0.5
},
].
}

タイムアウトトリガー

TimeoutTriggersは、ステートまたはグループに入ってから一定時間後にトランジションを発生させるために使用されます。

パラメーターType商品説明
type (必須)'timeout'トリガーのタイプを示す定数
timeout (必須)number遷移する前に待つミリ秒数
const example = {
trigger:
'other': [
{
type: 'timeout',
timeout: 1000,
},
]
}

カスタムトリガー

CustomTriggers(カスタムトリガー)は、いつでもトリガーできるトランジションで、即座にトランジションを起こします。 ecs.defineTrigger()を使用して、手動で起動できるTriggerHandleを作成します。

パラメーターType商品説明
type (必須)'custom'トリガーのタイプを示す定数
timeout (必須)number遷移する前に待つミリ秒数
const toOther = ecs.defineTrigger()
const example = {
trigger:
'other': [
{
type: 'custom',
trigger: toOther,
},
]
}
...
toOther.trigger()