Passer au contenu principal

Machines à états

Introduction

Les machines à états sont conçues pour simplifier la gestion des états.

Une machine à états est toujours dans un seul état à la fois et passe d'un état à l'autre lorsque certaines conditions (définies par les déclencheurs) sont remplies. Les groupes d'États sont un moyen pratique de regrouper la logique partagée entre plusieurs États, mais les groupes ne sont pas des États à proprement parler. Ils fournissent en grande partie la même API qu'un état, mais répartissent le comportement et les déclencheurs sur l'ensemble de leurs sous-états.

Une machine à états est composée de trois éléments principaux :

  • États
  • Groupes
  • Déclencheurs

Machine à états

Définition d'une machine à états

Définisseur de machines d'État

Lors de la création d'une machine d'État dans un composant, une instance de StateMachineDefiner est utilisée.

Propriétés
PropriétéTypeDescription
mondeLe mondeRéférence au monde.
eideidL'ID de l'entité du composant actuel
schemaAttributeAttribut du mondeRéférence au schéma du composant actuel dans World Scope.
dataAttributeAttribut du mondeRéférence aux données de la composante actuelle dans World Scope.

Le code suivant est un exemple de définition d'une machine à états vide :

ecs.registerComponent({
...
stateMachine : ({world, eid}) => {
// Définir les états ici
},
})

StateMachineDefinition

Vous pouvez également créer une machine d'état indépendante d'un composant.

Lors de la création d'une machine d'État en dehors d'un composant, une instance de StateMachineDefinition est utilisée.

Propriétés
PropriétéTypeDescription
initialState (Obligatoire)chaîne de caractèresNom de l'état de départ de la machine à états
États (obligatoire)Record<string, State>Une carte qui stocke les noms des États et leur définition
groupesStateGroup[]Une liste facultative de groupes d'États.
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'),
}]
}],
}

État

Un état est l'unité atomique fondamentale d'une machine à états. Ils peuvent être définis directement ou à l'aide de l'API fluente StateMachineDefiner décrite ci-dessus. Une machine à états est toujours dans un seul état à la fois et passe d'un état à l'autre en fonction de déclencheurs définis par l'utilisateur et associés à l'état actuel.

Propriétés

PropriétéTypeDescription
déclencheurs (obligatoire)Record<string, Trigger[]>Transitions sortantes, indexées par leur état cible
onEnter() => voidFonction appelée lors de l'entrée dans l'état
onTick() => voidFonction appelée chaque fois que l'on se trouve dans l'état
onExit() => voidFonction appelée lors de la sortie de l'état
auditeursListenerParams[]Paramètres de l'écouteur d'événements, automatiquement ajoutés à l'entrée et supprimés à la sortie

Définir un État

Le code suivant est un exemple de définition d'un nouvel état à l'intérieur d'une machine d'état dans un composant.

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

identifiant

Les StateIds sont utilisés pour spécifier les destinations des transitions. Il peut s'agir d'un StateDefiner ou du nom de l'État lui-même sous forme de chaîne de caractères.

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

Les fonctions de StateDefiner sont "fluentes", c'est-à-dire qu'elles renvoient la même instance, ce qui vous permet d'enchaîner plusieurs appels de fonctions dans une seule déclaration.

.initial()

Marquer cet état comme étant l'état actuel de l'automate à états lors de sa création.

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

.onEnter()

Définir une procédure de rappel à exécuter lors de l'entrée dans cet état.

ecs.defineState('myCustomState').onEnter(() => {
// Faire quelque chose
})

.onTick()

Définir un rappel à exécuter à chaque image.

ecs.defineState('myCustomState').onTick(() => {
// Faire quelque chose
})

.onExit()

Définit un rappel à exécuter lorsque l'on quitte cet état.

ecs.defineState('myCustomState').onExit(() => {
// Faire quelque chose
})

.onEvent()

Appel pour ajouter un EventTrigger de cet état à un autre qui peut faire la transition lorsqu'un événement spécifique est invoqué.

Propriétés
ParamètresTypeDescription
événement (obligatoire)chaîne de caractèresLe nom de l'événement à écouter
nextState (Obligatoire)stateIdL'état vers lequel il faut passer lorsque l'événement se produit
argsobjetArguments utilisés pour déterminer les conditions de transition
Args
ParamètresTypeDescription
cibleeidL'entité censée recevoir l'événement (par défaut, le propriétaire de la machine d'état)
(QueuedEvent) => booléenUne condition facultative à vérifier avant la transition ; si elle est fausse, la transition n'a pas lieu.
ecs.defineState('myCustomState').onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target : world.events.globalId,
where : (event) => event.data.position.y > 0.5
}
)

.wait()

Appel pour ajouter un TimeoutTrigger de cet état à un autre qui transite après un certain temps.

ParamètresTypeDescription
délai d'attentenombreDurée en millisecondes avant la transition
nextStateStateIdL'état suivant à atteindre
ecs.defineState('myCustomState').wait(1000, 'myOtherCustomState')

.onTrigger()

Appel pour ajouter un CustomTrigger de cet état à un autre qui peut être transposé à tout moment par l'utilisateur. Utilisez ecs.defineTrigger() pour créer un TriggerHandle qui peut être invoqué manuellement.

ParamètresTypeDescription
poignéeTriggerHandleLa poignée qui provoque une transition lorsqu'elle est activée manuellement
nextStateStateIdL'état suivant à atteindre
const toOther = ecs.defineTrigger()
ecs.defineState('example').onTrigger(toOther, 'other')
...
toOther.trigger()

.listen()

Appel pour ajouter des ListenerParams à l'ensemble des listeners de cet état. Un récepteur d'événements sera automatiquement ajouté lors de l'entrée dans l'état et supprimé lors de la sortie.

ParamètresTypeDescription
cibleeid ou () => eidL'entité qui est censée recevoir un événement
nomchaîne de caractèresL'événement à suivre
auditeur(QueuedEvent) => voidLa fonction à appeler lorsque l'événement est déclenché
const handleCollision = (event) => { ... }
ecs.defineState('example').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

Groupes d'États

Définition d'un groupe d'États

Définisseur de groupes d'États

Lors de la création d'un groupe d'États à l'intérieur d'un composant, une instance de StateGroupDefiner est utilisée.

ParamètresTypeDescription
substates (Obligatoire)StateId[]La liste des États qui composent ce groupe ; l'exclusion de ce paramètre équivaut à lister tous les États.
const fizz = ecs.defineState('fizz')
const buzz = ecs.defineState('buzz')

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

Les fonctions de StateGroupDefiner sont "fluentes", c'est-à-dire qu'elles renvoient la même instance, ce qui vous permet d'enchaîner plusieurs appels de fonctions dans une seule déclaration.

.onEnter()

Définir un rappel à exécuter lors de l'entrée dans ce groupe.

ecs.defineStateGroup(['a', 'b']).onEnter(() => {
// Faire quelque chose
})

.onTick()

Définir un rappel à exécuter à chaque image.

ecs.defineStateGroup(['a', 'b']).onTick(() => {
// Faire quelque chose
})

.onExit()

Définir un rappel à exécuter lors de la sortie de ce groupe.

ecs.defineStateGroup(['a', 'b']).onTick(() => {
// Faire quelque chose
})

.onEvent()

Appel pour ajouter un EventTrigger de n'importe quel état de ce groupe vers un autre état qui peut effectuer une transition lorsqu'un événement spécifique est invoqué.

Propriétés
ParamètresTypeDescription
événement (obligatoire)chaîne de caractèresLe nom de l'événement à écouter
nextState (Obligatoire)étatIdL'état vers lequel il faut passer lorsque l'événement se produit
argsobjetArguments utilisés pour déterminer les conditions de transition
Args
ParamètresTypeDescription
cibleeidL'entité censée recevoir l'événement (par défaut, le propriétaire de la machine d'état)
(QueuedEvent) => booléenUne condition facultative à vérifier avant la transition ; si elle est fausse, la transition n'a pas lieu.
ecs.defineStateGroup(['a', 'b']).onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target : world.events.globalId,
where : (event) => event.data.position.y > 0.5
}
)

.wait()

Appel pour ajouter un TimeoutTrigger depuis n'importe quel état de ce groupe vers un autre état qui transite après un certain temps.

ParamètresTypeDescription
délai d'attentenombreDurée en millisecondes avant la transition
nextStateStateIdL'état suivant à atteindre
ecs.defineStateGroup(['a', 'b']).wait(1000, 'c')

.onTrigger()

Appel pour ajouter un CustomTrigger de n'importe quel état de ce groupe vers un autre état qui peut être transposé à tout moment par l'utilisateur. Utilisez ecs.defineTrigger() pour créer un TriggerHandle qui peut être invoqué manuellement.

ParamètresTypeDescription
poignéeTriggerHandleLa poignée qui provoque une transition lorsqu'elle est activée manuellement
nextStateStateIdL'état suivant à atteindre
const toC = ecs.defineTrigger()
ecs.defineStateGroup(['a', 'b']).onTrigger(toC, 'c')
...
toC.trigger()

.listen()

Appel pour ajouter des ListenerParams à l'ensemble des listeners de cet état. Un récepteur d'événements sera automatiquement ajouté lors de l'entrée dans l'état et supprimé lors de la sortie.

ParamètresTypeDescription
cibleeid ou () => eidL'entité qui est censée recevoir un événement
nomchaîne de caractèresL'événement à suivre
auditeur(QueuedEvent) => voidLa fonction à appeler lorsque l'événement est déclenché
const handleCollision = (event) => { ... }
ecs.defineState('example').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

Déclencheurs

Il existe plusieurs types de déclencheurs de la transition dans différentes circonstances

Poignée

Objet utilisé pour définir une transition arbitraire entre deux états. Il doit être créé via ecs.defineTrigger et est utilisé par onTrigger ou CustomTrigger.

ParamètresTypeDescription
déclencher()() => voidAppeler cette fonction pour déclencher toutes les transitions CustomTrigger actives.
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')

Les types

Déclencheur d'événements

Les déclencheurs d'événements sont utilisés pour assurer une transition facultative lorsqu'un événement spécifique est invoqué. Les données relatives à l'événement peuvent être utilisées pour prendre la décision d'effectuer ou non une transition au cours de l'exécution.

ParamètresTypeDescription
type (obligatoire)événementUne constante pour indiquer le type de déclencheur
événement (obligatoire)chaîne de caractèresLe nom de l'événement à écouter
cibleeidL'entité qui est censée recevoir un événement
(QueuedEvent) => booléenUn prédicat optionnel à vérifier avant la transition ; retourner false empêchera la transition de se produire
const example = {
triggers :
'other' : [
{
type : 'event',
event : ecs.input.SCREEN_TOUCH_START,
target : world.events.globalId
where : (event) => event.data.position.y > 0.5
},
]
}

Déclencheur de délai d'attente

Les TimeoutTriggers sont utilisés pour provoquer une transition après un délai fixe à partir de l'entrée dans un état ou un groupe.

ParamètresTypeDescription
type (obligatoire)délai d'attenteUne constante pour indiquer le type de déclencheur
timeout (Obligatoire)nombreNombre de millisecondes à attendre avant d'effectuer la transition
const example = {
triggers :
'other' : [
{
type: 'timeout',
timeout: 1000,
},
]
}

Déclencheur personnalisé

Les CustomTriggers sont des transitions qui peuvent être déclenchées à tout moment, provoquant une transition immédiate. Utilisez ecs.defineTrigger() pour créer un TriggerHandle qui peut être invoqué manuellement.

ParamètresTypeDescription
type (obligatoire)'custom' (personnalisé)Une constante pour indiquer le type de déclencheur
timeout (Obligatoire)nombreNombre de millisecondes à attendre avant d'effectuer la transition
const toOther = ecs.defineTrigger()
const example = {
triggers :
'other' : [
{
type: 'custom',
trigger: toOther,
},
]
}
...
toOther.trigger()