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
Eigenschaft | Typ | Beschreibung |
---|---|---|
Welt | Welt | Hinweis auf die Welt. |
eid | eid | Die Entitäts-ID der aktuellen Komponente |
schemaAttribute | WorldAttribute | Verweis auf das Schema der aktuellen Komponente im World Scope. |
dataAttribute | WorldAttribute | Verweis 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
Eigenschaft | Typ | Beschreibung |
---|---|---|
initialState (erforderlich) | String | Name des Ausgangszustands des Zustandsautomaten |
Staaten (erforderlich) | Record<string, State> | Eine Karte, die die Namen der Staaten und ihre Definition speichert |
Gruppen | StateGroup[] | 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
Eigenschaft | Typ | Beschreibung |
---|---|---|
Auslöser (erforderlich) | Record<string, Trigger[]> | Ausgehende Übergänge, indiziert durch ihren Zielzustand |
onEnter | () => void | Funktion, die beim Eintritt in den Zustand aufgerufen wird |
onTick | () => void | Funktion, die bei jedem Frame aufgerufen wird, während sie sich im Zustand |
onExit | () => void | Funktion, die beim Verlassen des Zustands aufgerufen wird |
Hörer | ListenerParams[] | 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)
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
Parameter | Typ | Beschreibung |
---|---|---|
event (erforderlich) | String | Der Name des Ereignisses, nach dem gesucht werden soll |
nextState (erforderlich) | stateId | Der Zustand, in den bei Eintreten des Ereignisses übergegangen werden soll |
args | Objekt | Argumente zur Bestimmung der Übergangsbedingungen |
Args
Parameter | Typ | Beschreibung |
---|---|---|
Ziel | eid | Die Entität, die das Ereignis empfangen soll (standardmäßig der Besitzer des Zustandsautomaten) |
wobei | (QueuedEvent) => boolean | Eine 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.
Parameter | Typ | Beschreibung |
---|---|---|
Timeout | Nummer | Die Dauer in Millisekunden vor dem Übergang |
nextState | StateId | Der 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.
Parameter | Typ | Beschreibung |
---|---|---|
Griff | TriggerHandle | Der Griff, der bei manueller Betätigung einen Übergang auslöst |
nextState | StateId | Der 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.
Parameter | Typ | Beschreibung |
---|---|---|
Ziel | eid oder () => eid | Die Entität, von der erwartet wird, dass sie ein Ereignis erhält |
name | String | Das Ereignis, auf das Sie achten sollten |
Hörer | (QueuedEvent) => void | Die 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.
Parameter | Typ | Beschreibung |
---|---|---|
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'])
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
Parameter | Typ | Beschreibung |
---|---|---|
event (erforderlich) | String | Der Name des Ereignisses, nach dem gesucht werden soll |
nextState (erforderlich) | stateId | Der Zustand, in den bei Eintreten des Ereignisses übergegangen werden soll |
args | Objekt | Argumente zur Bestimmung der Übergangsbedingungen |
Args
Parameter | Typ | Beschreibung |
---|---|---|
Ziel | eid | Die Entität, die das Ereignis empfangen soll (standardmäßig der Besitzer des Zustandsautomaten) |
wobei | (QueuedEvent) => boolean | Eine 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.
Parameter | Typ | Beschreibung |
---|---|---|
Timeout | Nummer | Die Dauer in Millisekunden vor dem Übergang |
nextState | StateId | Der 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.
Parameter | Typ | Beschreibung |
---|---|---|
Griff | TriggerHandle | Der Griff, der bei manueller Betätigung einen Übergang auslöst |
nextState | StateId | Der 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.
Parameter | Typ | Beschreibung |
---|---|---|
Ziel | eid oder () => eid | Die Entität, von der erwartet wird, dass sie ein Ereignis erhält |
name | String | Das Ereignis, auf das Sie achten sollten |
Hörer | (QueuedEvent) => void | Die 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.
Parameter | Typ | Beschreibung |
---|---|---|
auslösen() | () => void | Rufen 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.
Parameter | Typ | Beschreibung |
---|---|---|
type (erforderlich) | Ereignis | Eine Konstante zur Angabe der Art des Auslösers |
event (erforderlich) | String | Der Name des Ereignisses, nach dem gesucht werden soll |
Ziel | eid | Die Entität, von der erwartet wird, dass sie ein Ereignis erhält |
wobei | (QueuedEvent) => boolean | Ein 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.
Parameter | Typ | Beschreibung |
---|---|---|
type (erforderlich) | Zeitüberschreitung | Eine Konstante zur Angabe der Art des Auslösers |
timeout (erforderlich) | Nummer | Die 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.
Parameter | Typ | Beschreibung |
---|---|---|
type (erforderlich) | benutzerdefiniert'. | Eine Konstante zur Angabe der Art des Auslösers |
timeout (erforderlich) | Nummer | Die Anzahl der Millisekunden, die vor dem Übergang gewartet wird |
const toOther = ecs.defineTrigger()
const example = {
triggers:
'other': [
{
type: 'custom',
trigger: toOther,
},
]
}
...
toOther.trigger()