Máquinas de estado
Introducción
Las Máquinas de Estado están diseñadas para simplificar la gestión de estados.Una máquina de estados siempre está en un solo estado a la vez y pasa de un estado a otro cuando se cumplen determinadas condiciones (definidas por los activadores). Los grupos de estados son una forma práctica de agrupar la lógica compartida entre varios estados, pero los grupos no son estados en sí mismos. Proporcionan gran parte de la misma API que un estado, pero distribuyen el comportamiento y los desencadenantes entre todos sus subestados.
Una máquina de estados consta de tres componentes principales:
- Estados
- Grupos
- Disparadores
Máquina de estados
Definición de una máquina de estados
StateMachineDefiner
Cuando se crea una Máquina de Estado dentro de un componente, se utiliza una instancia de StateMachineDefiner.
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
mundo | Mundo | Referencia al mundo. |
eid | eid | ID de entidad del componente actual |
esquemaAtributo | AtributoMundo | Referencia al esquema del componente actual en el ámbito mundial. |
dataAttribute | AtributoMundo | Referencia a los datos del componente actual en World Scope. |
El siguiente código es un ejemplo de cómo definir una Máquina de Estado vacía:
ecs.registerComponent({
...
stateMachine: ({world, eid}) => {
// Define estados aquí
},
})
StateMachineDefinition
También puede crear una Máquina de Estado independiente de un componente.
Cuando se crea una Máquina de Estado fuera de un componente, se utiliza una instancia de StateMachineDefinition.
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
initialState (Obligatorio) | cadena | Nombre del estado inicial de la máquina de estados |
estados (Obligatorio) | Registro<string, State> | Un mapa que almacena los nombres de los Estados y su definición |
grupos | EstadoGrupo[]` | Una lista opcional de grupos de estados. |
const stateMachine = {
initialState: 'a'
states: {
'a': {
onExit: () => console.log('exit a'),
triggers: {
'b': [{ type: 'timeout', timeout: 1000 }],
},
},
'b': { onEnter: () => console.log('entrar b') },
},
groups: [{
substates: ['a', 'b'],
listeners: [{
target: world.events.globalId,
name: ecs.input.SCREEN_TOUCH_START,
listener: (event) => console.log('touch'),
}]
}],
}
Estado
Un estado es la unidad atómica fundamental de una máquina de estados. Pueden definirse directamente o con la API fluida StateMachineDefiner descrita anteriormente. Una máquina de estados siempre se encuentra en un solo estado a la vez y realiza la transición según los disparadores definidos por el usuario asociados al estado actual.
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
disparadores (Obligatorio) | Registro<string, Trigger[]> | Transiciones salientes, indexadas por su estado de destino |
onEntrar | () => void | Función llamada al entrar en el estado |
onTick | () => void | Función llamada cada fotograma mientras se está en el estado |
onExit | () => void | Función llamada al salir del estado |
oyentes | ListenerParams[] | Parámetros del receptor de eventos, añadidos automáticamente al entrar y eliminados al salir |
Definir un Estado
El siguiente código es un ejemplo de cómo definir un nuevo Estado dentro de una Máquina de Estado dentro de un componente.
ecs.registerComponent({
...
stateMachine: ({world, eid}) => {
const foo = ecs.defineState('foo')
...
}
})
ID
Los StateIds se utilizan para especificar los destinos de transición. Puede ser un StateDefiner o el propio nombre del estado como cadena.
const a = ecs.definestate('a').wait(1000, 'b')
const b = ecs.defineState('b').wait(1000, a)
Las funciones de StateDefiner son "fluidas", es decir, devuelven la misma instancia, lo que permite encadenar varias llamadas a funciones en una única sentencia.
.inicial()
Marca este estado para que sea el estado actual de la máquina de estados cuando se cree.
ecs.defineState('miEstadoPersonalizado').initial()
.onEnter()
Establece una llamada de retorno que se ejecutará al entrar en este estado.
ecs.defineState('myCustomState').onEnter(() => {
// Haz algo
})
.onTick()
Establece una llamada de retorno para que se ejecute en cada fotograma.
ecs.defineState('myCustomState').onTick(() => {
// Haz algo
})
.onExit()
Establece una llamada de retorno que se ejecutará al salir de este estado.
ecs.defineState('myCustomState').onExit(() => {
// Haz algo
})
.onEvent()
Llamada para añadir un EventTrigger de este estado a otro que pueda transicionar cuando se invoque un evento específico.
Propiedades
Parámetro | Tipo | Descripción |
---|---|---|
evento (Obligatorio) | cadena | El nombre del evento a escuchar |
nextState (Obligatorio) | stateId | Estado al que se pasa cuando se produce el suceso |
args | objeto | Argumentos utilizados para determinar las condiciones de transición |
Args
Parámetro | Tipo | Descripción |
---|---|---|
objetivo | eid | La entidad que se espera que reciba el evento (por defecto es el propietario de la máquina de estado) |
donde | (QueuedEvent) => booleano | Condición opcional que se comprobará antes de realizar la transición; si es falsa, la transición no se producirá. |
ecs.defineState('myCustomState').onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)
.wait()
Llamada para añadir un TimeoutTrigger de este estado a otro que transiciona después de una cantidad de tiempo establecida.
Parámetro | Tipo | Descripción |
---|---|---|
tiempo de espera | número | La duración en milisegundos antes de la transición |
nextState | StateId | Siguiente estado de transición |
ecs.defineState('miEstadoPersonalizado').wait(1000, 'miOtroEstadoPersonalizado')
.onTrigger()
Llamada para añadir un CustomTrigger de este estado a otro que puede transicionar en cualquier momento inmediatamente por el usuario. Utilice ecs.defineTrigger() para crear un TriggerHandle que pueda ser invocado manualmente.
Parámetro | Tipo | Descripción |
---|---|---|
asa | TriggerHandle | La manilla que provocará una transición cuando se active manualmente |
nextState | StateId | Siguiente estado de transición |
const toOther = ecs.defineTrigger()
ecs.defineState('ejemplo').onTrigger(toOther, 'otro')
...
toOther.trigger()
.listen()
Llamada para añadir ListenerParams al conjunto de oyentes de este estado. Se añadirá automáticamente un receptor de eventos cuando se introduzca el estado, y se eliminará al salir.
Parámetro | Tipo | Descripción |
---|---|---|
objetivo | eid o () => eid | La entidad que se espera que reciba un evento |
nombre | cadena | El acontecimiento a tener en cuenta |
oyente | (QueuedEvent) => void | La función a la que se llamará cuando se envíe el evento |
const handleCollision = (evento) => { ... }
ecs.defineState('ejemplo').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)
Grupos estatales
Definición de un grupo de Estados
StateGroupDefiner
Cuando se crea un grupo de estados dentro de un componente, se utiliza una instancia de StateGroupDefiner.
Parámetro | Tipo | Descripción |
---|---|---|
subestados (Obligatorio) | StateId[] | La lista de estados que componen este grupo; excluir este parámetro equivale a listar todos los estados |
const fizz = ecs.defineState('fizz')
const buzz = ecs.defineState('buzz')
const fizzBuzz = ecs.defineStateGroup([fizz, 'buzz'])
Las funciones de StateGroupDefiner son "fluidas", es decir, devuelven la misma instancia, lo que permite encadenar varias llamadas a funciones en una sola sentencia.
.onEnter()
Establece una llamada de retorno para que se ejecute al entrar en este grupo.
ecs.defineStateGroup(['a', 'b']).onEnter(() => {
// Haz algo
})
.onTick()
Establece una llamada de retorno para que se ejecute en cada fotograma.
ecs.defineStateGroup(['a', 'b']).onTick(() => {
// Haz algo
})
.onExit()
Establece una llamada de retorno que se ejecutará al salir de este grupo.
ecs.defineStateGroup(['a', 'b']).onTick(() => {
// Haz algo
})
.onEvent()
Llamada para añadir un EventTrigger desde cualquier estado de este grupo a algún otro estado que pueda transicionar cuando se invoque un evento específico.
Propiedades
Parámetro | Tipo | Descripción |
---|---|---|
evento (Obligatorio) | cadena | El nombre del evento a escuchar |
nextState (Obligatorio) | stateId | Estado al que se pasa cuando se produce el suceso |
args | objeto | Argumentos utilizados para determinar las condiciones de transición |
Args
Parámetro | Tipo | Descripción |
---|---|---|
objetivo | eid | La entidad que se espera que reciba el evento (por defecto es el propietario de la máquina de estado) |
donde | (QueuedEvent) => booleano | Condición opcional que se comprobará antes de realizar la transición; si es falsa, la transición no se producirá. |
ecs.defineStateGroup(['a', 'b']).onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)
.wait()
Llamada para añadir un TimeoutTrigger desde cualquier estado en este grupo a algún otro estado que transiciona después de una cantidad de tiempo establecida.
Parámetro | Tipo | Descripción |
---|---|---|
tiempo de espera | número | La duración en milisegundos antes de la transición |
nextState | StateId | Siguiente estado de transición |
ecs.defineStateGroup(['a', 'b']).wait(1000, 'c')
.onTrigger()
Llamada para añadir un CustomTrigger desde cualquier estado de este grupo a algún otro estado que pueda transicionar en cualquier momento inmediatamente por el usuario. Utilice ecs.defineTrigger() para crear un TriggerHandle que pueda ser invocado manualmente.
Parámetro | Tipo | Descripción |
---|---|---|
asa | TriggerHandle | La manilla que provocará una transición cuando se active manualmente |
nextState | StateId | Siguiente estado de transición |
const toC = ecs.defineTrigger()
ecs.defineStateGroup(['a', 'b']).onTrigger(toC, 'c')
...
toC.trigger()
.listen()
Llamada para añadir ListenerParams al conjunto de oyentes de este estado. Se añadirá automáticamente un receptor de eventos cuando se introduzca el estado, y se eliminará al salir.
Parámetro | Tipo | Descripción |
---|---|---|
objetivo | eid o () => eid | La entidad que se espera que reciba un evento |
nombre | cadena | El acontecimiento a tener en cuenta |
oyente | (QueuedEvent) => void | La función a la que se llamará cuando se envíe el evento |
const handleCollision = (evento) => { ... }
ecs.defineState('ejemplo').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)
Disparadores
Existen múltiples tipos de desencadenantes de la transición en diversas circunstancias
Mango
Objeto que se utiliza para definir una transición arbitraria entre estados. Debe crearse mediante ecs.defineTrigger, y es utilizado por onTrigger o CustomTrigger.
Parámetro | Tipo | Descripción |
---|---|---|
desencadenar() | () => void | Llame a esta función para que se produzca cualquier transición CustomTrigger activa |
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')
Tipos
Activador de eventos
Los EventTriggers se utilizan para realizar una transición opcional cuando se invoca un evento especificado. Los datos del evento se pueden utilizar para tomar una decisión en tiempo de ejecución sobre la transición o no.
Parámetro | Tipo | Descripción |
---|---|---|
tipo (Obligatorio) | evento | Una constante para indicar el tipo de disparador |
evento (Obligatorio) | cadena | El nombre del evento a escuchar |
objetivo | eid | La entidad que se espera que reciba un evento |
donde | (QueuedEvent) => booleano | Predicado opcional que se comprobará antes de realizar la transición; si se devuelve false, se evitará que se produzca la transición. |
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
Los TimeoutTriggers se utilizan para hacer que se produzca una transición tras un tiempo determinado desde que se entra en un estado o grupo.
Parámetro | Tipo | Descripción |
---|---|---|
tipo (Obligatorio) | tiempo de espera | Una constante para indicar el tipo de disparador |
tiempo de espera (obligatorio) | número | El número de milisegundos a esperar antes de la transición |
const ejemplo = {
desencadenantes:
'otros': [
{
type: 'timeout',
timeout: 1000,
},
]
}
CustomTrigger
Los CustomTriggers son transiciones que pueden activarse en cualquier momento, provocando una transición inmediata. Utilice ecs.defineTrigger() para crear un TriggerHandle que pueda ser invocado manualmente.
Parámetro | Tipo | Descripción |
---|---|---|
tipo (Obligatorio) | personalizado | Una constante para indicar el tipo de disparador |
tiempo de espera (obligatorio) | número | El número de milisegundos a esperar antes de la transición |
const toOther = ecs.defineTrigger()
const example = {
triggers:
'other': [
{
type: 'custom',
trigger: toOther,
},
]
}
...
toOther.trigger()