Zum Hauptinhalt springen

Zustandsmaschinen

Einführung

Zustandsautomaten wurden entwickelt, um die Zustandsverwaltung zu vereinfachen.

Ein Zustandsautomat befindet sich immer in genau einem Zustand und wechselt zwischen den Zuständen, wenn bestimmte Bedingungen (definiert durch die Auslöser) erfüllt sind. Zustandsgruppen sind eine bequeme Möglichkeit, die gemeinsame Logik mehrerer Zustände zu bündeln, aber die Gruppen sind selbst keine Zustände. Sie bieten größtenteils die gleiche API wie ein Zustand, verteilen aber Verhalten und Auslöser auf alle ihre Unterzustände.

Ein Zustandsautomat setzt sich aus drei Hauptkomponenten zusammen:

  • Staaten
  • Gruppen
  • Auslöser

Zustandsmaschine

Definieren eines Zustandsautomaten

StateMachineDefiner

Bei der Erstellung einer State Machine innerhalb einer Komponente wird eine Instanz von StateMachineDefiner verwendet.

Eigenschaften
EigenschaftTypBeschreibung
WeltWeltHinweis auf die Welt.
eideidDie Entitäts-ID der aktuellen Komponente
schemaAttributeWorldAttributeVerweis auf das Schema der aktuellen Komponente im World Scope.
dataAttributeWorldAttributeVerweis auf die Daten der aktuellen Komponente im World Scope.

Der folgende Code ist ein Beispiel für die Definition einer leeren Zustandsmaschine:

ecs.registerComponent({
...
stateMachine: ({world, eid}) => {
// Hier werden Zustände definiert
},
})

StateMachineDefinition

Alternativ können Sie auch eine State Machine unabhängig von einer Komponente erstellen.

Bei der Erstellung einer State Machine außerhalb einer Komponente wird eine Instanz von StateMachineDefinition verwendet.

Eigenschaften
EigenschaftTypBeschreibung
initialState (erforderlich)StringName des Ausgangszustands des Zustandsautomaten
Staaten (erforderlich)Record<string, State>Eine Karte, die die Namen der Staaten und ihre Definition speichert
GruppenStateGroup[]Eine optionale Liste von Zustandsgruppen.
const stateMachine = {
initialState: 'a'
states: {
'a': {
onExit: () => console.log('exit a'),
triggers: {
'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'),
}]
}],
}

Zustand

Ein Zustand ist die grundlegende atomare Einheit eines Zustandsautomaten. Sie können direkt oder über die oben beschriebene fließende StateMachineDefiner-API definiert werden. Ein Zustandsautomat befindet sich immer in genau einem Zustand und geht nach benutzerdefinierten Auslösern, die mit dem aktuellen Zustand verbunden sind, über.

Eigenschaften

EigenschaftTypBeschreibung
Auslöser (erforderlich)Record<string, Trigger[]>Ausgehende Übergänge, indiziert durch ihren Zielzustand
onEnter() => voidFunktion, die beim Eintritt in den Zustand aufgerufen wird
onTick() => voidFunktion, die bei jedem Frame aufgerufen wird, während sie sich im Zustand
onExit() => voidFunktion, die beim Verlassen des Zustands aufgerufen wird
HörerListenerParams[]Parameter für Ereignis-Listener, die beim Eintritt automatisch hinzugefügt und beim Austritt entfernt werden

Definieren eines Staates

Der folgende Code ist ein Beispiel dafür, wie ein neuer Zustand innerhalb einer State Machine in einer Komponente definiert wird.

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

ID

StateIds werden für die Angabe von Übergangszielen verwendet. Kann entweder ein StateDefiner oder der Zustandsname selbst als String sein.

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

StateDefiner-Funktionen sind "fließend", d.h. sie geben dieselbe Instanz zurück, so dass Sie mehrere Funktionsaufrufe in einer einzigen Anweisung verketten können.

.initial()

Kennzeichnen Sie diesen Zustand als den aktuellen Zustand des Zustandsautomaten, wenn er erstellt wird.

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

.onEnter()

Legen Sie einen Callback fest, der beim Eintritt in diesen Zustand ausgeführt wird.

ecs.defineState('myCustomState').onEnter(() => {
// Etwas tun
})

.onTick()

Legen Sie einen Callback fest, der bei jedem Frame ausgeführt wird.

ecs.defineState('myCustomState').onTick(() => {
// Etwas tun
})

.onExit()

Legen Sie einen Callback fest, der beim Verlassen dieses Zustands ausgeführt wird.

ecs.defineState('myCustomState').onExit(() => {
// Etwas tun
})

.onEvent()

Aufruf, um einen EventTrigger von diesem Zustand zu einem anderen hinzuzufügen, der in einen anderen übergehen kann, wenn ein bestimmtes Ereignis ausgelöst wird.

Eigenschaften
ParameterTypBeschreibung
event (erforderlich)StringDer Name des Ereignisses, nach dem gesucht werden soll
nextState (erforderlich)stateIdDer Zustand, in den bei Eintreten des Ereignisses übergegangen werden soll
argsObjektArgumente zur Bestimmung der Übergangsbedingungen
Args
ParameterTypBeschreibung
ZieleidDie Entität, die das Ereignis empfangen soll (standardmäßig der Besitzer des Zustandsautomaten)
wobei(QueuedEvent) => booleanEine optionale Bedingung, die vor dem Übergang zu prüfen ist; ist sie falsch, findet der Übergang nicht statt.
ecs.defineState('myCustomState').onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)

.wait()

Aufruf zum Hinzufügen eines TimeoutTriggers von diesem Zustand zu einem anderen, der nach einer bestimmten Zeitspanne übergeht.

ParameterTypBeschreibung
TimeoutNummerDie Dauer in Millisekunden vor dem Übergang
nextStateStateIdDer nächste Zustand, in den übergegangen wird
ecs.defineState('myCustomState').wait(1000, 'myOtherCustomState')

.onTrigger()

Aufruf zum Hinzufügen eines benutzerdefinierten Auslösers von diesem Zustand zu einem anderen, der jederzeit sofort vom Benutzer geändert werden kann. Verwenden Sie ecs.defineTrigger(), um einen TriggerHandle zu erstellen, der manuell aufgerufen werden kann.

ParameterTypBeschreibung
GriffTriggerHandleDer Griff, der bei manueller Betätigung einen Übergang auslöst
nextStateStateIdDer nächste Zustand, in den übergegangen wird
const toOther = ecs.defineTrigger()
ecs.defineState('example').onTrigger(toOther, 'other')
...
toOther.trigger()

.listen()

Aufruf zum Hinzufügen von ListenerParams zur Gruppe der Listener dieses Zustands. Ein Ereignis-Listener wird automatisch hinzugefügt, wenn der Zustand betreten wird, und beim Verlassen wieder entfernt.

ParameterTypBeschreibung
Zieleid oder () => eidDie Entität, von der erwartet wird, dass sie ein Ereignis erhält
nameStringDas Ereignis, auf das Sie achten sollten
Hörer(QueuedEvent) => voidDie Funktion, die aufgerufen wird, wenn das Ereignis ausgelöst wird
const handleCollision = (event) => { ... }
ecs.defineState('example').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

Staatliche Gruppen

Definieren einer Statusgruppe

StateGroupDefiner

Bei der Erstellung einer Zustandsgruppe innerhalb einer Komponente wird eine Instanz von StateGroupDefiner verwendet.

ParameterTypBeschreibung
substates (erforderlich)StateId[]Die Liste der Staaten, aus denen sich diese Gruppe zusammensetzt; das Ausschließen dieses Parameters entspricht der Auflistung aller Staaten
const fizz = ecs.defineState('fizz')
const buzz = ecs.defineState('buzz')

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

StateGroupDefiner-Funktionen sind "fließend", d. h. sie geben dieselbe Instanz zurück, so dass Sie mehrere Funktionsaufrufe in einer einzigen Anweisung verketten können.

.onEnter()

Legen Sie einen Callback fest, der beim Betreten dieser Gruppe ausgeführt wird.

ecs.defineStateGroup(['a', 'b']).onEnter(() => {
// Etwas tun
})

.onTick()

Legen Sie einen Callback fest, der bei jedem Frame ausgeführt wird.

ecs.defineStateGroup(['a', 'b']).onTick(() => {
// Etwas tun
})

.onExit()

Legen Sie einen Callback fest, der beim Verlassen dieser Gruppe ausgeführt wird.

ecs.defineStateGroup(['a', 'b']).onTick(() => {
// Etwas tun
})

.onEvent()

Aufruf zum Hinzufügen eines EventTriggers von einem beliebigen Zustand in dieser Gruppe zu einem anderen Zustand, der übergehen kann, wenn ein bestimmtes Ereignis ausgelöst wird

Eigenschaften
ParameterTypBeschreibung
event (erforderlich)StringDer Name des Ereignisses, nach dem gesucht werden soll
nextState (erforderlich)stateIdDer Zustand, in den bei Eintreten des Ereignisses übergegangen werden soll
argsObjektArgumente zur Bestimmung der Übergangsbedingungen
Args
ParameterTypBeschreibung
ZieleidDie Entität, die das Ereignis empfangen soll (standardmäßig der Besitzer des Zustandsautomaten)
wobei(QueuedEvent) => booleanEine optionale Bedingung, die vor dem Übergang zu prüfen ist; ist sie falsch, findet der Übergang nicht statt.
ecs.defineStateGroup(['a', 'b']).onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)

.wait()

Aufruf zum Hinzufügen eines TimeoutTriggers von einem beliebigen Zustand in dieser Gruppe zu einem anderen Zustand, der nach einer bestimmten Zeitspanne übergeht.

ParameterTypBeschreibung
TimeoutNummerDie Dauer in Millisekunden vor dem Übergang
nextStateStateIdDer nächste Zustand, in den übergegangen wird
ecs.defineStateGroup(['a', 'b']).wait(1000, 'c')

.onTrigger()

Aufruf zum Hinzufügen eines benutzerdefinierten Auslösers von einem beliebigen Zustand in dieser Gruppe zu einem anderen Zustand, der jederzeit sofort vom Benutzer geändert werden kann. Verwenden Sie ecs.defineTrigger(), um einen TriggerHandle zu erstellen, der manuell aufgerufen werden kann.

ParameterTypBeschreibung
GriffTriggerHandleDer Griff, der bei manueller Betätigung einen Übergang auslöst
nextStateStateIdDer nächste Zustand, in den übergegangen wird
const toC = ecs.defineTrigger()
ecs.defineStateGroup(['a', 'b']).onTrigger(toC, 'c')
...
toC.trigger()

.listen()

Aufruf zum Hinzufügen von ListenerParams zur Gruppe der Listener dieses Zustands. Ein Ereignis-Listener wird automatisch hinzugefügt, wenn der Zustand betreten wird, und beim Verlassen wieder entfernt.

ParameterTypBeschreibung
Zieleid oder () => eidDie Entität, von der erwartet wird, dass sie ein Ereignis erhält
nameStringDas Ereignis, auf das Sie achten sollten
Hörer(QueuedEvent) => voidDie Funktion, die aufgerufen wird, wenn das Ereignis ausgelöst wird
const handleCollision = (event) => { ... }
ecs.defineState('example').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

Auslöser

Es gibt mehrere Arten von Auslösern für den Übergang unter verschiedenen Umständen

Handgriff

Ein Objekt, das verwendet wird, um einen beliebigen Übergang zwischen Zuständen zu definieren. Er muss über ecs.defineTrigger erstellt werden und wird von onTrigger oder CustomTrigger verwendet.

ParameterTypBeschreibung
auslösen()() => voidRufen Sie diese Funktion auf, um alle aktiven CustomTrigger-Übergänge auszulösen
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')

Typen

EventTrigger

EventTrigger werden verwendet, um optional einen Übergang zu schaffen, wenn ein bestimmtes Ereignis ausgelöst wird. Die Ereignisdaten können verwendet werden, um zur Laufzeit zu entscheiden, ob ein Übergang stattfinden soll oder nicht.

ParameterTypBeschreibung
type (erforderlich)EreignisEine Konstante zur Angabe der Art des Auslösers
event (erforderlich)StringDer Name des Ereignisses, nach dem gesucht werden soll
ZieleidDie Entität, von der erwartet wird, dass sie ein Ereignis erhält
wobei(QueuedEvent) => booleanEin optionales Prädikat, das vor dem Übergang zu prüfen ist; wird false zurückgegeben, wird der Übergang verhindert.
const example = {
triggers:
'other': [
{
type: 'event',
event: ecs.input.SCREEN_TOUCH_START,
target: world.events.globalId
where: (event) => event.data.position.y > 0.5
},
]
}

TimeoutTrigger

TimeoutTrigger werden verwendet, um einen Übergang nach einer festen Zeitspanne ab dem Eintritt in einen Zustand oder eine Gruppe zu bewirken.

ParameterTypBeschreibung
type (erforderlich)ZeitüberschreitungEine Konstante zur Angabe der Art des Auslösers
timeout (erforderlich)NummerDie Anzahl der Millisekunden, die vor dem Übergang gewartet wird
const example = {
triggers:
'other': [
{
type: 'timeout',
timeout: 1000,
},
]
}

CustomTrigger

CustomTriggers sind Übergänge, die jederzeit ausgelöst werden können und einen sofortigen Übergang bewirken. Verwenden Sie ecs.defineTrigger(), um einen TriggerHandle zu erstellen, der manuell aufgerufen werden kann.

ParameterTypBeschreibung
type (erforderlich)benutzerdefiniert'.Eine Konstante zur Angabe der Art des Auslösers
timeout (erforderlich)NummerDie Anzahl der Millisekunden, die vor dem Übergang gewartet wird
const toOther = ecs.defineTrigger()
const example = {
triggers:
'other': [
{
type: 'custom',
trigger: toOther,
},
]
}
...
toOther.trigger()