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 consta de tres componentes principales:

  • Estados
  • Grupos estatales
  • Disparadores

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.

Ejemplo

ecs.registerComponent({
name: 'Jump On Touch',
stateMachine: ({world, entity, defineState}) => {
const idle = defineState('idle').initial().onEnter(() => {
console.log('Entrando en estado idle')
}).onEvent(ecs.input.SCREEN_TOUCH_START, 'jumping')

const jumping = defineState('jumping').onEnter(() => {
console.log('Entrando en estado de salto')
ecs.physics.applyImpulse(world, entity.eid, 0, 5, 0)
}).onTick(() => {
console.log('En estado de salto')
}).wait(2000, 'idle')
},
})

Definición de una máquina de estados

Al crear una máquina de estados dentro de un componente, se llama a su función con lo siguiente:

Propiedades

PropiedadTipoDescripción
mundoMundoReferencia al mundo.
eideidID de entidad del componente actual
entidadEntidadLa instancia de la entidad del componente actual
definirEstadofunciónUna función para definir estados en la máquina de estados
defineGrupoEstadofunciónUna función para definir grupos en la máquina de estados
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 estados vacía:

ecs.registerComponent({
...
stateMachine: ({world, entity, defineState}) => {
// Define estados aquí
},
})

Estado

Un estado es la unidad atómica fundamental de una máquina de estados. Después de definir los posibles estados de tu máquina de estados, puedes moverte entre ellos definiendo disparadores.

Definir un Estado

El siguiente código es un ejemplo de cómo definir un nuevo Estado dentro de una máquina de estados dentro de un componente.

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

Las funciones de estado son "fluidas", es decir, devuelven la misma instancia del estado, lo que permite encadenar varias llamadas a funciones en una única sentencia.

.inicial()

Marca este estado como el primer estado activo cuando se crea la máquina de estados.

defineEstado('miEstadoPersonalizado').inicial()

.onEnter()

Establece una llamada de retorno que se ejecutará al entrar en este estado.

defineState('miEstadoPersonalizado').onEnter(() => {
// Haz algo
})

.onTick()

Establece una llamada de retorno para que se ejecute en cada fotograma mientras este estado esté activo.

defineState('myCustomState').onTick(() => {
// Haz algo
})

.onExit()

Establece una llamada de retorno que se ejecutará al salir de este estado.

defineState('myCustomState').onExit(() => {
// Haz algo
})

.onEvent()

Transición a un nuevo estado cuando se recibe un evento específico.

ParámetroTipoDescripción
evento (Obligatorio)cadenaEl nombre del evento a escuchar
nextState (Obligatorio)cadena o EstadoEstado al que se pasa cuando se produce el suceso
opciones (Opcional)objetoOpciones adicionales

Opciones

ParámetroTipoDescripción
objetivoeidLa entidad que se espera que reciba el evento (por defecto es la entidad actual)
donde(evento) => booleanoCondición opcional que se comprobará antes de realizar la transición; si es falsa, la transición no se producirá.
defineState('myCustomState').onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)

.wait()

Transición a un nuevo estado tras un tiempo determinado.

ParámetroTipoDescripción
tiempo de esperanúmeroLa duración en milisegundos antes de la transición
nextStatecadena o EstadoSiguiente estado de transición
defineEstado('miEstadoPersonalizado').wait(1000, 'miOtroEstadoPersonalizado')

.onTrigger()

Transición a un nuevo estado cuando se activa un TriggerHandle (definido con ecs.defineTrigger()).

ParámetroTipoDescripción
asaTriggerHandleLa manilla que provocará una transición cuando se active manualmente
nextStatecadena o EstadoSiguiente estado de transición
const toOther = ecs.defineTrigger()
defineState('ejemplo').onTrigger(toOther, 'otro')
...
toOther.trigger()

.listen()

Registra un receptor de eventos que se añadirá automáticamente al entrar en 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(evento) => voidLa función a la que se llamará cuando se envíe el evento
const handleCollision = (event) => { 
console.log('Colisionó con', event.data.other)
}
defineState('ejemplo').listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

Grupos estatales

Un grupo de estados es una forma de definir comportamientos y disparadores que se aplican a una lista de estados. Los grupos estatales no son Estados propiamente dichos y no se puede pasar directamente a ellos. En cambio, cuando cualquier estado del grupo está activo, el comportamiento y los activadores del grupo también lo están.

Definición de un grupo de Estados

ParámetroTipoDescripción
subestados (Opcional)Matriz de cadena o EstadoLa lista de estados que componen este grupo; excluir este parámetro equivale a listar todos los estados
const fizz = defineEstado('fizz')
const buzz = defineEstado('buzz')

const fizzBuzz = defineEstadoGrupo([fizz, 'buzz'])
consejo

Las funciones de grupo de estados son "fluidas", es decir, devuelven la misma instancia del grupo de estados, lo que permite encadenar varias llamadas a funciones en una única sentencia.

.onEnter()

Establece una llamada de retorno para que se ejecute al entrar en este grupo.

defineStateGroup(['a', 'b']).onEnter(() => {
// Haz algo
})

.onTick()

Establece una llamada de retorno para que se ejecute en cada fotograma mientras este grupo esté activo.

defineStateGroup(['a', 'b']).onTick(() => {
// Haz algo
})

.onExit()

Establece una llamada de retorno que se ejecutará al salir de este grupo.

defineStateGroup(['a', 'b']).onTick(() => {
// Haz algo
})

.onEvent()

Transición a un nuevo estado cuando se recibe un evento específico.

ParámetroTipoDescripción
evento (Obligatorio)cadenaEl nombre del evento a escuchar
nextState (Obligatorio)cadena o EstadoEstado al que se pasa cuando se produce el suceso
opciones (Opcional)objetoOpciones adicionales

Opciones

ParámetroTipoDescripción
objetivoeidLa entidad que se espera que reciba el evento (por defecto es la entidad actual)
donde(evento) => booleanoCondición opcional que se comprobará antes de realizar la transición; si es falsa, la transición no se producirá.
defineStateGroup(['a', 'b']).onEvent(
ecs.input.SCREEN_TOUCH_START,
'other',
{
target: world.events.globalId,
where: (event) => event.data.position.y > 0.5
}
)

.wait()

Transición a un nuevo estado tras un tiempo determinado.

ParámetroTipoDescripción
tiempo de esperanúmeroLa duración en milisegundos antes de la transición
nextStatecadena o EstadoSiguiente estado de transición
defineEstadoGrupo(['a', 'b']).espera(1000, 'c')

.onTrigger()

Transición a un nuevo estado cuando se activa un TriggerHandle (definido con ecs.defineTrigger()).

ParámetroTipoDescripción
asaTriggerHandleLa manilla que provocará una transición cuando se active manualmente
nextStatecadena o EstadoSiguiente estado de transición
const toC = ecs.defineTrigger()
defineStateGroup(['a', 'b']).onTrigger(toC, 'c')
...
toC.trigger()

.listen()

Registra un receptor de eventos que se añadirá automáticamente al entrar en el grupo de estados 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(evento) => voidLa función a la que se llamará cuando se envíe el evento
const handleCollision = (event) => { 
console.log('colisionó con', event.data.other)
}
defineStateGroup(['a', 'b']).listen(eid, ecs.physics.COLLISION_START_EVENT, handleCollision)

Activadores personalizados

Puede definir un activador personalizado que pueda invocarse en cualquier momento para provocar una transición.

const go = ecs.defineTrigger()
const parado = defineState('parado').onTick(() => {
if (world.input.getAction('inicio-marcha')) {
go.trigger()
}
}).onTrigger(go, 'going')
const going = defineState('going')