Ir al contenido principal

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
PropiedadTipoDescripción
mundoMundoReferencia al mundo.
eideidID de entidad del componente actual
esquemaAtributoAtributoMundoReferencia al esquema del componente actual en el ámbito mundial.
dataAttributeAtributoMundoReferencia 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
PropiedadTipoDescripción
initialState (Obligatorio)cadenaNombre 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
gruposEstadoGrupo[]`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

PropiedadTipoDescripción
disparadores (Obligatorio)Registro<string, Trigger[]>Transiciones salientes, indexadas por su estado de destino
onEntrar() => voidFunción llamada al entrar en el estado
onTick() => voidFunción llamada cada fotograma mientras se está en el estado
onExit() => voidFunción llamada al salir del estado
oyentesListenerParams[]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)
consejo

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ámetroTipoDescripción
evento (Obligatorio)cadenaEl nombre del evento a escuchar
nextState (Obligatorio)stateIdEstado al que se pasa cuando se produce el suceso
argsobjetoArgumentos utilizados para determinar las condiciones de transición
Args
ParámetroTipoDescripción
objetivoeidLa entidad que se espera que reciba el evento (por defecto es el propietario de la máquina de estado)
donde(QueuedEvent) => booleanoCondició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ámetroTipoDescripción
tiempo de esperanúmeroLa duración en milisegundos antes de la transición
nextStateStateIdSiguiente 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ámetroTipoDescripción
asaTriggerHandleLa manilla que provocará una transición cuando se active manualmente
nextStateStateIdSiguiente 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ámetroTipoDescripción
objetivoeid o () => eidLa entidad que se espera que reciba un evento
nombrecadenaEl acontecimiento a tener en cuenta
oyente(QueuedEvent) => voidLa 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ámetroTipoDescripció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'])
consejo

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ámetroTipoDescripción
evento (Obligatorio)cadenaEl nombre del evento a escuchar
nextState (Obligatorio)stateIdEstado al que se pasa cuando se produce el suceso
argsobjetoArgumentos utilizados para determinar las condiciones de transición
Args
ParámetroTipoDescripción
objetivoeidLa entidad que se espera que reciba el evento (por defecto es el propietario de la máquina de estado)
donde(QueuedEvent) => booleanoCondició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ámetroTipoDescripción
tiempo de esperanúmeroLa duración en milisegundos antes de la transición
nextStateStateIdSiguiente 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ámetroTipoDescripción
asaTriggerHandleLa manilla que provocará una transición cuando se active manualmente
nextStateStateIdSiguiente 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ámetroTipoDescripción
objetivoeid o () => eidLa entidad que se espera que reciba un evento
nombrecadenaEl acontecimiento a tener en cuenta
oyente(QueuedEvent) => voidLa 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ámetroTipoDescripción
desencadenar()() => voidLlame 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ámetroTipoDescripción
tipo (Obligatorio)eventoUna constante para indicar el tipo de disparador
evento (Obligatorio)cadenaEl nombre del evento a escuchar
objetivoeidLa entidad que se espera que reciba un evento
donde(QueuedEvent) => booleanoPredicado 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ámetroTipoDescripción
tipo (Obligatorio)tiempo de esperaUna constante para indicar el tipo de disparador
tiempo de espera (obligatorio)númeroEl 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ámetroTipoDescripción
tipo (Obligatorio)personalizadoUna constante para indicar el tipo de disparador
tiempo de espera (obligatorio)númeroEl 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()