Supongamos que estamos construyendo un juego de plataformas, donde el héroe puede caminar, saltar y atacar. El héroe puede saltar al estar de pie o caminar, pero él no puede atacar al saltar o caminar. Además, el jugador no puede saltar de nuevo mientras se está en el aire (sin doble salto).

Podemos empezar a aplicar el código héroe de la siguiente manera. Dependiendo de su experiencia de codificación, se habrán dado cuenta de que este código es problemático. Para saber si el jugador está saltando (y por lo tanto, evitando que atacar), tenemos que guardar su estado en una variable, que dice si el héroe es saltar o no. Ahora, supongamos que queremos que el jugador sea capaz de bloquear ataques, pero sólo cuando se está de pie sin caminar, saltar o atacar. Habría que añadir otra variable para realizar un seguimiento de ello. Además, supongamos que queremos cambiar la animación héroe cuando es saltar, bloquear o atacar. Es posible que ya haya notado que será extremadamente difícil de manejar este código a medida que aumenta el tamaño de nuestro juego.
Héroe = function (game_state, walking_speed, jumping_speed) {Phaser.Sprite.call (this); this.game_state = game_state; this.walking_speed = walking_speed; this.jumping_speed = jumping_speed; this.is_jumping = false; this.game_state.physics.arcade.enable (this); this.cursors = this.game_state.input.keyboard.createCursorKeys ();}; Hero.prototype = Object.create (Phaser.Sprite.prototype); Hero.prototype.constructor = héroe; Hero.prototype.update = function () {if (this.cursors.left.isDown) {this.body.velocity.x = -this.walking_speed; } Else if (this.cursors.right.isDown) {this.body.velocity.x = this.walking_speed; } Else {this.body.velocity.x = 0; } If (this.cursors.up.isDown && this.is_jumping!) {This.body.velocity.y = -this.jumping_speed; this.is_jumping = true; } If (this.game_state.input.keyboard.isDown (Phaser.Keyboard.SPACEBAR) && this.is_jumping!) {This.attack (); }}; 12345678910111213141516171819202122232425262728293031323334Hero = function (game_state, walking_speed, jumping_speed) {Phaser.Sprite.call (this); this.game_state = game_state; this.walking_speed = walking_speed; this.jumping_speed = jumping_speed; this.is_jumping = false; this.game_state .physics.arcade.enable (este); this.cursors = this.game_state.input.keyboard.createCursorKeys ();}; Hero.prototype = Object.create (Phaser.Sprite.prototype); Hero.prototype.constructor = héroe ; Hero.prototype.update = function () {if (this.cursors.left.isDown) {this.body.velocity.x = -this.walking_speed;} elseif (this.cursors.right.isDown) {this.body .velocity.x = this.walking_speed;} else {this.body.velocity.x = 0;} if (! this.cursors.up.isDown && this.is_jumping) {this.body.velocity.y = -this.jumping_speed ; this.is_jumping = true;} if (this.game_state.input.keyboard.isDown (Phaser.Keyboard.SPACEBAR) && this.is_jumping!) {this.attack ();}};

Para manejar este tipo de problema, hay una estructura llamada máquina de estados, que puede modelar de manera eficiente lo que queremos en nuestro juego : Un objeto que puede asumir diferentes estados durante su vida. En este tutorial, voy a explicar cómo usar una máquina de estados para administrar un comportamiento de objetos y animaciones en sus juegos.

En primer lugar, voy a explicar los fundamentos de las máquinas de estado, para aquellos que no están familiarizados con este concepto. A continuación, voy a mostrar una posible implementación del código, que será utilizado en una demostración de plataformas.

Para leer este tutorial, es importante que usted está familiarizado con los conceptos siguientes:


  • Javascript y conceptos orientados a objetos.
  • conceptos básicos Phaser, tales como: estados, sprites, los grupos y la física de arcade
  • Creación de mapas con baldosa

    Tabla de contenidos

    Aprender Phaser mediante la construcción de 15 juegos

    Si usted quiere dominar Phaser y aprender cómo publicar juegos Phaser como juegos nativas para iOS y Android se sienten libres para comprobar Zenva En línea En este supuesto The Complete el juego para móvil Curso de Desarrollo – Construir 15 juegos.

    archivos de código fuente

    Puede descargar los archivos de código fuente tutorial aquí.

    ¿Qué es una máquina de estado?

    máquina de estado A es un modelo matemático utilizado para representar los estados y las transiciones entre estos estados. Además del nombre de fantasía, las máquinas de estados son las cosas simples, y voy a explicarlos utilizando nuestro ejemplo héroe. La siguiente figura muestra la máquina de estados que controla nuestro héroe. En esta figura, los círculos son los estados posibles que nuestro héroe puede ser, mientras que las flechas representan transiciones entre ellos. El texto sobre las flechas son los insumos necesarios (en nuestras acciones de caso) para ejecutar esa transición de estado. Y eso es todo, el héroe debe empezar en un estado inicial (por ejemplo, de pie), y verifica constantemente la entrada para ejecutar cualquier transición necesarios (como caminar y saltar).

    hero_state_machine

    Por ejemplo, si nuestro héroe está de pie, y se recibe una entrada de pie, se debe cambiar al estado caminando. Por otro lado, si se encuentra en los estados de pie o caminando y recibe una entrada de salto, debe ir al estado de salto. Usted puede haber notado que las variables que hemos utilizado en el código anterior para realizar un seguimiento de lo que el héroe estaba haciendo (como saltar o de pie) estaban jugando el papel de los estados. Mediante el uso de una máquina de estados que podemos encapsular todo el comportamiento héroe en sus respectivos estados, manteniendo el código más limpio y más fácil de manejar.

    El código de máquina de estado

    Ahora que ya sabe lo que es una máquina de estados, es el momento de escribir el código para el nuestro. Recuerde que este es mi sugerencia de código, y se puede aplicar el suyo la forma que considera que mejor se adapte a su juego.

    Empecemos por escribir el código para la clase StateMachine, que se muestra a continuación. Una máquina de estado tiene un conjunto de estados, que puede ser añadido por el método de “add_state”. Cada estado se identifica por su nombre (por ejemplo: de pie, caminar, saltar y atacar), y que puede establecer el estado inicial por el método de “set_initial_state”. El método “handle_input” se debe llamar cada vez que una nueva entrada está disponible (representado por la variable “comando”). El estado actual se encargará de esta entrada y volverá al estado siguiente. Si el siguiente estado es diferente de la actual, hay que salir del estado actual, y entramos en el nuevo.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.StateMachine = function () { «utilizar estricta»; this.states = {};}; StateMachineExample.StateMachine.prototype.add_state = function (state_name, estado) { «utilizar estricta»; this.states [state_name] = estado;}; StateMachineExample.StateMachine.prototype.set_initial_state = function (state_name) { «utilizar estricta»; this.current_state = this.states [state_name]; this.current_state.enter ();}; StateMachineExample.StateMachine.prototype.handle_input = function (comando) { «utilizar estricta»; next_state var; next_state = this.current_state.handle_input (comando); if (! next_state && next_state == this.current_state.name) {this.current_state.exit (); this.current_state = this.states [next_state]; this.current_state.enter (); }}; 12345678910111213141516171819202122232425262728varStateMachineExample = StateMachineExample || {}; StateMachineExample.StateMachine = function () { «utilizar estricta»; this.states = {};}; StateMachineExample.StateMachine.prototype.add_state = function (state_name, estado) { «uso estrictas «; this.states [state_name] = estado;}; StateMachineExample.StateMachine.prototype.set_initial_state = function (state_name) {» utilizar estricta «; this.current_state = this.states [state_name]; this.current_state.enter () ;}; StateMachineExample.StateMachine.prototype.handle_input = function (comandos) { «use strict»; varnext_state; next_state = this.current_state.handle_input (comando), si (next_state && next_state == this.current_state.name!) {esto. current_state.exit (); this.current_state = this.states [next_state]; this.current_state.enter ();}};

    El código de clase Estado se muestra a continuación. Por defecto, cualquier estado contiene su nombre y no hace nada en las, “Salir” y “Enter” métodos “handle_input”. Esto es sólo la clase base que se extenderá por los estados de nuestro héroe.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.State = función (nombre, prefabricada) { «uso estricto»; this.name = Nombre; this.prefab = prefabricada;}; StateMachineExample.State.prototype.enter = function () { «utilizar estricta»;}; StateMachineExample.State.prototype.exit = function () { «utilizar estricta»;}; StateMachineExample.State. prototype.handle_input = function (comando) { «utilizar estricta»; volver this.name;}; 1234567891011121314151617181920varStateMachineExample = StateMachineExample || {}; StateMachineExample.State = función (nombre, prefabricada) { «utilizar estricta»; this.name = nombre; this.prefab = prefabricada;}; StateMachineExample.State.prototype .enter = function () { «utilizar estricta»;}; StateMachineExample.State.prototype.exit = function () { «utilizar estricta»;}; StateMachineExample.State.prototype.handle_input = function (comando) { «uso estricto» ; returnthis.name;};

    La clase de comandos se muestra a continuación. Será simplemente un objeto con un nombre para identificarlo y un conjunto de propiedades. Estas propiedades se pueden utilizar para manejar adecuadamente las transiciones. Por ejemplo, nuestro comando “paseo” puede tener la dirección a donde el héroe es caminar, para que podamos actualizar adecuadamente su velocidad.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.Command = función (nombre, propiedades) { «uso estricto»; var propiedad; this.name = Nombre; para (propiedad en las propiedades) {if (properties.hasOwnProperty (propiedad)) {esta [propiedad] = propiedades [propiedad]; }}}; 123456789101112varStateMachineExample = StateMachineExample || {}; StateMachineExample.Command = función (nombre, propiedades) { «utilizar estricta»; varproperty; this.name = nombre; para (propertyinproperties) {if (properties.hasOwnProperty (propiedad)) {esta [propiedad] = [propiedades de propiedad];}}};

    Phaser estados de nuestra demo

    Vamos a guardar los datos de nivel de nuestra demo en un archivo JSON, que será leído cuando se inicia. El archivo JSON que voy a la práctica se muestra a continuación. Tenga en cuenta que hay que definir los activos, grupos y mapa de información.
    { «activos»: { «map_tileset»: { «type»: «imagen», «fuente»: «assets / images / tiles_spritesheet.png»}, «hero_spritesheet»: { «type»: «spritesheet», «fuente» : «assets / images / player_spritesheet.png», «frame_width»: 28, «frame_height»: 30, «marcos»: 5, «margen»: 1, «espaciamiento»: 1}, «level_tilemap»: { «tipo» : «tilemap», «fuente»: «activos / mapas / demo_map.json»}}, «grupos»: [] «héroes», «mapa»: { «clave»: «level_tilemap», «baldosas»: [» map_tileset «]}} {123456789101112131415» activos «: {» map_tileset «: {» type «:» imagen » «fuente»: «assets / images / tiles_spritesheet.png»}, «hero_spritesheet»: { «type»:» spritesheet » «fuente»: «assets / images / player_spritesheet.png», «frame_width»: 28, «frame_height»: 30, «marcos»: 5, «margen»: 1, «espaciamiento»: 1},» level_tilemap «: {» type «:» tilemap», «fuente»: «activos / mapas / demo_map.json»}}, «grupos»: [] «héroes», «mapa»: { «clave»: «level_tilemap», «baldosas»: [ «map_tileset»]}}

    vamos a utilizar un mapa creado usando el editor de niveles de baldosa. Si no está familiarizado con la baldosa, se puede comprobar una de mis tutoriales anteriores, en el que se cubre con más detalles. Este es el mapa que voy a utilizar. Puede utilizar éste, proporcionado en el código fuente o crear el suyo propio. Sin embargo, si crea su propio mapa, hay que tener cuidado de dos cosas: 1) Debe configurar una propiedad denominada “colisión” para ser verdad a cualquiera de las capas que son Collidable; 2) debe definir las propiedades del objeto héroe como se muestra a continuación.

    mapa collision_layer hero_properties

    Nuestra demostración tendrá tres estados: BootState, LoadingState y DemoState. El código para BootState y LoadingState se muestra a continuación. Ambos son responsables de leer el archivo JSON y nivel de carga de todos los activos, antes de llamar a DemoState.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.BootState = function () { «utilizar estricta»; Phaser.State.call (this);}; StateMachineExample.BootState.prototype = Object.create (Phaser.State.prototype); StateMachineExample.BootState.prototype.constructor = StateMachineExample.BootState; StateMachineExample.BootState.prototype.init = function ( level_file, next_state) { «utilizar estricta»; this.level_file = level_file; this.next_state = next_state;}; StateMachineExample.BootState.prototype.preload = function () { «utilizar estricta»; this.load.text ( «level1», this.level_file);}; StateMachineExample.BootState.prototype.create = function () { «utilizar estricta»; level_text var, level_data; level_text = this.game.cache.getText ( «level1»); level_data = JSON.parse (level_text); this.game.state.start ( «LoadingState», verdadero, falso, level_data, this.next_state);}; 12345678910111213141516171819202122232425262728varStateMachineExample = StateMachineExample || {}; StateMachineExample.BootState = function () { «uso estricto»; Phaser.State. llamada (esto);}; StateMachineExample.BootState.prototype = Object.create (Phaser.State.prototype); StateMachineExample.BootState.prototype.constructor = StateMachineExample.BootState; StateMachineExample.BootState.prototype.init = function (level_file, next_state) { «utilizar estricta»; this.level_file = level_file; this.next_state = next_state;}; StateMachineExample.BootState.prototype.preload = function () { «utilizar estricta»; this.load.text ( «level1», this.level_file );}; StateMachineExample.BootState.prototype.create = function () { «utilizar estricta»; varlevel_text, level_data; level_text = this.game.cache.getText ( «level1»); level_data = JSON.parse (level_text); esta .game.state.start ( «LoadingState», verdadero, falso, level_data, this.next_state);}; var StateMachineExample = StateMachineExample || {}; StateMachineExample.LoadingState = function () { «utilizar estricta»; Phaser.State.call (this);}; StateMachineExample.LoadingState.prototype = Object.create (Phaser.State.prototype); StateMachineExample.LoadingState.prototype.constructor = StateMachineExample.LoadingState; StateMachineExample.LoadingState.prototype.init = function ( level_data, next_state) { «utilizar estricta»; this.level_data = level_data; this.next_state = next_state;}; StateMachineExample.LoadingState.prototype.preload = function () { «utilizar estricta»; var activos, asset_loader, asset_key, activo; = activos this.level_data.assets; para (asset_key en activos) {// activos de carga de acuerdo con la clave activo si (assets.hasOwnProperty (asset_key)) {activos = activos [asset_key]; interruptor (asset.type) {case «imagen»: this.load.image (asset_key, asset.source); descanso; caso «spritesheet»: this.load.spritesheet (asset_key, asset.source, asset.frame_width, asset.frame_height, asset.frames, asset.margin, asset.spacing); descanso; caso «tilemap»: this.load.tilemap (asset_key, asset.source, null, Phaser.Tilemap.TILED_JSON); descanso; }}}}; StateMachineExample.LoadingState.prototype.create = function () { «utilizar estricta»; this.game.state.start (this.next_state, verdadero, falso, this.level_data);}; 123456789101112131415161718192021222324252627282930313233343536373839404142varStateMachineExample = StateMachineExample || {}; StateMachineExample.LoadingState = function () { «utilizar estricta»; Phaser.State.call ( este);}; StateMachineExample.LoadingState.prototype = Object.create (Phaser.State.prototype); StateMachineExample.LoadingState.prototype.constructor = StateMachineExample.LoadingState; StateMachineExample.LoadingState.prototype.init = function (level_data, next_state) {» utilizar estricta «; this.level_data = level_data; this.next_state = next_state;}; StateMachineExample.LoadingState.prototype.preload = function () {» utilizar estricta «; varassets, asset_loader, asset_key, activo; activos = this.level_data.assets ; para (inassets asset_key) {// activos de carga de acuerdo con keyif activo (assets.hasOwnProperty (asset_key)) {activos = activos [asset_key]; conmutador (asset.type) {case «imagen»: this.load.image (asset_key , asset.source); break; caso «spritesheet»: this.load.spritesheet (asset_key, asset.source, asset.frame_width, asset.frame_height, asset.frames, asset.margin, asset.spacing); break; caso «tilemap»: this.load.tilemap (asset_key, asset.source, null, Phaser.Tilemap.TILED_JSON); descanso ;}}}}; StateMachineExample.LoadingState.prototype.create = function () { «utilizar estricta»; this.game.state.start (this.next_state, verdadero, falso, this.level_data);};

    DemoState debe cargar el mapa con todas sus casas prefabricadas. Para ello, primero se inicializará el mapa en el método “init”. Luego, en los “crear” método que creará todos los grupos, asignar capas y casas prefabricadas. Tenga en cuenta que usamos la propiedad “colisión” en las capas del mapa para comprobar si son Collidable. El “create_object” se utiliza para crear cada uno de acuerdo prefabricada para su tipo. Los tipos se almacenan en la propiedad “prefab_classes”, que se utiliza para llamar al constructor correcta. Tenga en cuenta que esto es posible porque todas las casas prefabricadas tienen el mismo constructor, como se muestra en la clase base prefabricada a continuación.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.DemoState = function () { «utilizar estricta»; Phaser.State.call (this); this.prefab_classes = { «héroe»: StateMachineExample.Hero.prototype.constructor};}; StateMachineExample.DemoState.prototype = Object.create (Phaser.State.prototype); StateMachineExample.DemoState.prototype.constructor = StateMachineExample.DemoState; StateMachineExample .DemoState.prototype.init = function (level_data) { «utilizar estricta»; tileset_index var, tile_dimensions; this.level_data = this.level_data || level_data; this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.scale.pageAlignHorizontally = true; this.scale.pageAlignVertically = true; sistema de física this.game.physics.startSystem (Phaser.Physics.ARCADE) // empezar; this.game.physics.arcade.gravity.y = 1,000; // crear el mapa y establecer conjunto de baldosas this.map = this.game.add.tilemap (this.level_data.map.key); tileset_index = 0; this.map.tilesets.forEach (función (conjunto de baldosas) {this.map.addTilesetImage (tileset.name, this.level_data.map.tilesets [tileset_index]); tileset_index + = 1;}, this);}; StateMachineExample.DemoState .prototype.create = function () { «utilizar estricta»; GROUP_NAME var, object_layer, collision_tiles; // crear capas de mapa this.layers = {}; this.map.layers.forEach (función (capa) {this.layers [layer.name] = this.map.createLayer (layer.name), si (layer.properties.collision) {// collision_tiles capa colisión = [] ; layer.data.forEach (function (data_row) {// encontrar azulejos usados ​​en la capa de data_row.forEach (function (teja) {// Comprobar si se trata de un índice de baldosas válido y no está ya en la lista si (teja. índice> 0 && collision_tiles.indexOf (tile.index) === -1) {collision_tiles.push (tile.index);}}, esto);}, this); this.map.setCollision (collision_tiles, es cierto, capa .name);}}, this); // cambiar el tamaño del mundo para ser el tamaño de las this.layers capa actuales [this.map.layer.name] .resizeWorld (); // crear grupos this.groups = {}; this.level_data.groups.forEach (function (GROUP_NAME) {this.groups [GROUP_NAME] = this.game.add.group ();}, this); this.prefabs = {}; para (object_layer en this.map.objects) {if (this.map.objects.hasOwnProperty (object_layer)) {// crear capa de objetos this.map.objects [object_layer] .forEach (this.create_object, este); }}}; StateMachineExample.DemoState.prototype.create_object = función (objeto) { «utilizar estricta»; var object_y, la posición, prefabricada; // azulejos coordenadas se inicia en la esquina inferior izquierda object_y = (object.gid)? object.y – (this.map.tileHeight / 2): object.y + (object.height / 2); posición = { «x»: object.x + (this.map.tileHeight / 2), «y»: object_y}; // crear el objeto de acuerdo con su tipo si (this.prefab_classes.hasOwnProperty (object.type)) {= prefabricadas nuevas this.prefab_classes [object.type] (esto, object.name, la posición, object.properties); } this.prefabs [object.name] = prefabricada;}; 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788varStateMachineExample = StateMachineExample || {}; StateMachineExample.DemoState = function () { «utilizar estricta»; Phaser.State.call (this); this.prefab_classes = {» héroe «: StateMachineExample.Hero.prototype.constructor};}; StateMachineExample.DemoState.prototype = Object.create (Phaser.State.prototype); StateMachineExample.DemoState.prototype.constructor = StateMachineExample.DemoState; StateMachineExample.DemoState.prototype.init = function (level_data) { «utilizar estricta»; vartileset_index, tile_dimensions; this.level_data = this.level_data || level_data; this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.scale.pageAlignHorizontally = true; this.scale. pageAlignVertically = true; // iniciar la física systemthis.game.physics.startSystem (Phaser.Physics.ARCADE); this.game.physics.arcade.gravity.y = 1000; // crear y conjunto del tilesetthis.m ap = this.game.add.tilemap (this.level_data.map.key); tileset_index = 0; this.map.tilesets.forEach (función (conjunto de baldosas) {this.map.addTilesetImage (tileset.name, this.level_data. map.tilesets [tileset_index]); tileset_index + = 1;}, this);}; StateMachineExample.DemoState.prototype.create = function () { «utilizar estricta»; vargroup_name, object_layer, collision_tiles; // crear layersthis.layers mapa = {}; this.map.layers.forEach (función (capa) {this.layers [layer.name] = this.map.createLayer (layer.name), si (layer.properties.collision) {// layercollision_tiles colisión = []; layer.data.forEach (function (data_row) {// encontrar azulejos utilizados en la layerdata_row.forEach (function (teja) {// Comprobar si se trata de un índice de baldosas válido y no está ya en la listif (teja. índice> 0 && collision_tiles.indexOf (tile.index) === -1) {collision_tiles.push (tile.index);}}, esto);}, this); this.map.setCollision (collision_tiles, es cierto, capa. nombre);}}, this); // cambiar el tamaño del mundo para ser el tamaño de los actuales layerthis.layers [this.map.layer.name] .resizeWorld (); // crear groupsthis .Grupos = {}; this.level_data.groups.forEach (function (GROUP_NAME) {this.groups [GROUP_NAME] = this.game.add.group ();}, this); this.prefabs = {}; para ( object_layer inthis.map.objects) {if (this.map.objects.hasOwnProperty (object_layer)) {// crear objectsthis.map.objects capa [object_layer] .forEach (this.create_object, este);}}}; StateMachineExample. DemoState.prototype.create_object = función (objeto) { «utilizar estricta»; varobject_y, la posición, prefabricada;? // coordenadas azulejos comienza en el cornerobject_y inferior izquierda = (object.gid) object.y- (this.map.tileHeight / 2): object.y + (object.height / 2); posición = { «x»: object.x + (this.map.tileHeight / 2), «y»: object_y}; // crear el objeto de acuerdo con su typeif ( this.prefab_classes.hasOwnProperty (object.type)) {prefabricada = newthis.prefab_classes (esto, object.name, posición [object.type], object.properties);} this.prefabs [object.name] = prefabricada;}; var StateMachineExample = StateMachineExample || {}; StateMachineExample.Prefab = function (game_state, nombre, posición, propiedades) { «utilizar estricta»; Phaser.Sprite.call (esto, game_state.game, position.x, position.y, properties.texture); this.game_state = game_state; this.name = Nombre; this.game_state.groups [properties.group] .add (this); this.frame = + properties.frame; si (properties.scale) {this.scale.setTo (properties.scale.x, properties.scale.y); } This.game_state.prefabs [nombre] = esta;}; StateMachineExample.Prefab.prototype = Object.create (Phaser.Sprite.prototype); StateMachineExample.Prefab.prototype.constructor = StateMachineExample.Prefab; 12345678910111213141516171819202122varStateMachineExample = StateMachineExample || {} ; StateMachineExample.Prefab = function (game_state, nombre, posición, propiedades) { «utilizar estricta»; Phaser.Sprite.call (esto, game_state.game, position.x, position.y, properties.texture); this.game_state = game_state; This.Name = nombre; this.game_state.groups [properties.group] .add (this); this.frame = + properties.frame; si (properties.scale) {this.scale.setTo (properties.scale. x, properties.scale.y);} this.game_state.prefabs [nombre] = esta;}; StateMachineExample.Prefab.prototype = Object.create (Phaser.Sprite.prototype); StateMachineExample.Prefab.prototype.constructor = StateMachineExample. prefabricada;

    héroe Unidos

    Ahora que tenemos nuestra máquina de estados implementada, vamos a crear los estados de nuestro héroe. En este tutorial voy a mostrar los estados de pie, caminar y saltar. Voy a dejar el estado atacando (y cualquier otro que pueda pensar) como un ejercicio, ya que es similar a lo que estamos haciendo por los demás estados. Todos los estados extender la clase base de Estado, y pondrán en práctica los métodos necesarios.

    Por ejemplo, el código siguiente muestra la StandingState. En su introduzca método debe establecer el marco héroe en el bastidor de pie y la velocidad a 0. En el método “handle_input” comprueba para los comandos de “saltar” “pie” y. Tenga en cuenta que el comando “caminar” tiene una propiedad “dirección” para que podamos saber si el jugador se mueve hacia la izquierda o hacia la derecha.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.StandingState = función (nombre, prefabricada, marco) { «utilizar estricta»; StateMachineExample.State.call (esto, nombre, prefabricada); this.frame = marco;}; StateMachineExample.StandingState.prototype = Object.create (StateMachineExample.State.prototype); StateMachineExample.StandingState.prototype.constructor = StateMachineExample.StandingState; StateMachineExample.StandingState.prototype.enter = function () { » utilizar estricta «; // conjunto de marco y la velocidad de pie a 0 this.prefab.frame = this.frame; this.prefab.body.velocity.x = 0;}; StateMachineExample.StandingState.prototype.handle_input = function (comando) { «utilizar estricta»; interruptor (command.name) {case «paseo»: si (command.direction === «izquierda») {return «walking_left»; } Else {return «walking_right»; } Caso de «salto»: return «salto»; } StateMachineExample.State.prototype.handle_input.call (esto, comandos);}; 1234567891011121314151617181920212223242526272829303132varStateMachineExample = StateMachineExample || {}; StateMachineExample.StandingState = función (nombre, prefabricada, marco) { «utilizar estricta»; StateMachineExample.State.call ( esto, nombre, prefabricada); this.frame = marco;}; StateMachineExample.StandingState.prototype = Object.create (StateMachineExample.State.prototype); StateMachineExample.StandingState.prototype.constructor = StateMachineExample.StandingState; StateMachineExample.StandingState.prototype. introducir = function () { «uso estricto»; // conjunto de marco y la velocidad de pie a 0this.prefab.frame = this.frame; this.prefab.body.velocity.x = 0;}; StateMachineExample.StandingState.prototype.handle_input = function (comandos) { «use strict»; conmutador (command.name) {case «paseo»: si (command.direction === «izquierda») {return «walking_left»;} else {return «walking_right»;} caso «salto»: return «salto»;} StateMachineExample.State.prototype.handle_input.call (esto, comandos);};

    El cont WalkingState AINS la velocidad de la animación a pie y caminar, como se muestra a continuación. En su método de “entrar” se reproducirá la animación caminar y ajustar la velocidad del héroe. En el método de “salida” que sólo se detendrá la animación. No vamos a establecer la velocidad a 0 en el método de “salida” por lo que el jugador puede mantenerse en movimiento cuando se pasa del estado caminando al estado de salto. Por último, en el método “handle_input” comprueba la “parada” y comandos “salto”.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.WalkingState = función (nombre, prefabricada, dirección, walking_speed) { «utilizar estricta»; StateMachineExample.State.call (esto, nombre, prefabricada); this.walking_animation = this.prefab.animations.add ( «pie», [0, 1, 2, 1], 6, true); this.direction = dirección; this.walking_speed = walking_speed;}; StateMachineExample.WalkingState.prototype = Object.create (StateMachineExample.State.prototype); StateMachineExample.WalkingState.prototype.constructor = StateMachineExample.WalkingState; StateMachineExample.WalkingState.prototype.enter = function () { » utilizar estricta «; // iniciar la animación y el juego de velocidad this.walking_animation.play (); this.prefab.body.velocity.x = this.direction * this.walking_speed; si (this.direction === 1) {this.prefab.scale.setTo (-1, 1); } Else {this.prefab.scale.setTo (1, 1); }}; StateMachineExample.WalkingState.prototype.exit = function () { «utilizar estricta»; // animación stop y el conjunto de velocidad a cero this.walking_animation.stop ();}; StateMachineExample.WalkingState.prototype.handle_input = function (comando) { «utilizar estricta»; interruptor (command.name) {case «parada»: retorno «de pie»; caso «salto»: return «salto»; } StateMachineExample.State.prototype.handle_input.call (esto, comandos);}; 1234567891011121314151617181920212223242526272829303132333435363738394041424344varStateMachineExample = StateMachineExample || {}; StateMachineExample.WalkingState = función (nombre, prefabricada, dirección, walking_speed) { «uso estricto»; StateMachineExample.State. llamada (esto, nombre, prefabricada); this.walking_animation = this.prefab.animations.add ( «pie», [0,1,2,1], 6, true); this.direction = dirección; this.walking_speed = walking_speed;}; StateMachineExample.WalkingState.prototype = Object.create (StateMachineExample.State.prototype); StateMachineExample.WalkingState.prototype.constructor = StateMachineExample.WalkingState; StateMachineExample.WalkingState.prototype.enter = function () { «utilizar estricta»; // iniciar la animación y velocitythis.walking_animation.play set (); this.prefab.body.velocity.x = this.direction * this.walking_speed; si (this.direction === 1) {this.prefab.scale.setTo (-1,1);} else {this.prefab.scale.setTo (1,1);}}; StateMachineExample.WalkingState.prototype.exit = f unción () { «uso estricto»; // animación stop y la velocidad conjunto a zerothis.walking_animation.stop ();}; StateMachineExample.WalkingState.prototype.handle_input = function (comando) { «utilizar estricta»; conmutador (command.name ) {case «parada»: retorno «de pie», y el caso «salto»: return «salto»;} StateMachineExample.State.prototype.handle_input.call (esto, comandos);};

    Finalmente, JumpingState tiene la velocidad de salto , que se aplica a la velocidad en su método “entrar”. El único comando comprueba en el método “handle_input” es el comando “caída”.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.JumpingState = función (nombre, prefabricada, jumping_speed) { «utilizar estricta»; StateMachineExample.State.call (esto, nombre, prefabricada); this.jumping_speed = jumping_speed;}; StateMachineExample.JumpingState.prototype = Object.create (StateMachineExample.State.prototype); StateMachineExample.JumpingState.prototype.constructor = StateMachineExample.JumpingState; StateMachineExample.JumpingState.prototype.enter = function () { » utilizar estricta «; // set velocidad vertical this.prefab.body.velocity.y = -this.jumping_speed;}; StateMachineExample.JumpingState.prototype.handle_input = function (comando) { «utilizar estricta»; interruptor (command.name) {case «caída»: retorno «de pie»; } StateMachineExample.State.prototype.handle_input.call (esto, comandos);}; 12345678910111213141516171819202122232425varStateMachineExample = StateMachineExample || {}; StateMachineExample.JumpingState = función (nombre, prefabricada, jumping_speed) { «utilizar estricta»; StateMachineExample.State.call ( esto, nombre, prefabricada); this.jumping_speed = jumping_speed;}; StateMachineExample.JumpingState.prototype = Object.create (StateMachineExample.State.prototype); StateMachineExample.JumpingState.prototype.constructor = StateMachineExample.JumpingState; StateMachineExample.JumpingState.prototype. introducir = function () { «utilizar estricta»; // set velocitythis.prefab.body.velocity.y vertical = -this.jumping_speed;}; StateMachineExample.JumpingState.prototype.handle_input = function (comando) { «utilizar estricta»; interruptor (command.name) {case «caída»: retorno «de pie»;} StateMachineExample.State.prototype.handle_input.call (esto, comandos);};

    El héroe prefabricada

    Ahora que tenemos los estados héroe, podemos crear su prefabricada como se muestra a continuación. En el constructor, creamos la máquina de estados, sumando todos sus estados y establecer el estado inicial. También añadimos las devoluciones de llamada a los eventos de teclado “onDown” y “onUp”. Estas devoluciones de llamada se utilizan para comprobar la entrada del usuario y enviar comandos a la máquina de estado.
    var StateMachineExample = StateMachineExample || {}; StateMachineExample.Hero = function (game_state, nombre, posición, propiedades) { «utilizar estricta»; StateMachineExample.Prefab.call (esto, game_state, nombre, posición, propiedades); this.anchor.setTo (0,5); this.walking_speed = + properties.walking_speed; this.jumping_speed = + properties.jumping_speed; this.game_state.game.physics.arcade.enable (this); this.body.collideWorldBounds = true; // crear y añadir máquina de estados Unidos this.state_machine = new StateMachineExample.StateMachine (); this.state_machine.add_state ( «pie», nuevo StateMachineExample.StandingState ( «pie», esto, 3)); this.state_machine.add_state ( «walking_left», nuevo StateMachineExample.WalkingState ( «walking_left», esto, -1, this.walking_speed)); this.state_machine.add_state ( «walking_right», nuevo StateMachineExample.WalkingState ( «walking_left», esto, 1, this.walking_speed)); this.state_machine.add_state ( «salto», nuevo StateMachineExample.JumpingState ( «salto», esto, this.jumping_speed)); this.state_machine.set_initial_state ( «pie»); // Añadir devoluciones de llamada a eventos de teclado this.game_state.game.input.keyboard.addCallbacks (esto, this.process_on_down_input, this.process_on_up_input, null);}; StateMachineExample.Hero.prototype = Object.create (StateMachineExample.Prefab.prototype) ; StateMachineExample.Hero.prototype.constructor = StateMachineExample.Hero; StateMachineExample.Hero.prototype.update = function () { «utilizar estricta»; this.game_state.game.physics.arcade.collide (esto, this.game_state.layers.collision); // tocar baldosas de suelo si (this.body.blocked.down) {this.state_machine.handle_input (nueva StateMachineExample.Command ( «caída», {})); }}; StateMachineExample.Hero.prototype.process_on_down_input = function (evento) { «utilizar estricta»; interruptor (event.keyCode) {case Phaser.Keyboard.LEFT: // caminar a la izquierda this.state_machine.handle_input (nueva StateMachineExample.Command ( «pie», {direction: «izquierda»})); descanso; Phaser.Keyboard.RIGHT caso: // caminar this.state_machine.handle_input derecha (nueva StateMachineExample.Command ( «pie», {dirección «correcta»})); descanso; Phaser.Keyboard.UP caso: // saltar this.state_machine.handle_input (nueva StateMachineExample.Command ( «salto», {})); descanso; }}; StateMachineExample.Hero.prototype.process_on_up_input = function (evento) { «utilizar estricta»; interruptor (event.keyCode) {case Phaser.Keyboard.LEFT: this.state_machine.handle_input (nueva StateMachineExample.Command ( «stop», {})); descanso; Phaser.Keyboard.RIGHT caso: this.state_machine.handle_input (nueva StateMachineExample.Command ( «stop», {})); descanso; }}; 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768varStateMachineExample = StateMachineExample || {}; StateMachineExample.Hero = function (game_state, nombre, posición, propiedades) { «utilizar estricta»; StateMachineExample.Prefab.call (esto, game_state, nombre, posición, propiedades); esto. anchor.setTo (0,5); this.walking_speed = + properties.walking_speed; this.jumping_speed = + properties.jumping_speed; this.game_state.game.physics.arcade.enable (este); this.body.collideWorldBounds = true; // crear máquina de estado y añadir statesthis.state_machine = newStateMachineExample.StateMachine (); this.state_machine.add_state ( «pie», newStateMachineExample.StandingState ( «pie», esto, 3)); this.state_machine.add_state ( «walking_left», newStateMachineExample .WalkingState ( «walking_left», esto, -1, this.walking_speed)); this.state_machine.add_state ( «walking_right», newStateMachineExample.WalkingState ( «walking_left», esto, 1, this.walking_speed)); this.state_machine. add_state ( «Salto», newStateMachineExample.JumpingState ( «salto», esto, this.jumping_speed)); this.state_machine.set_initial_state ( «pie»); // añadir devoluciones de llamada a eventsthis.game_state.game.input.keyboard.addCallbacks teclado (esto ,this.process_on_down_input,this.process_on_up_input,null);};StateMachineExample.Hero.prototype=Object.create(StateMachineExample.Prefab.prototype);StateMachineExample.Hero.prototype.constructor=StateMachineExample.Hero;StateMachineExample.Hero.prototype.update =function(){«use strict»;this.game_state.game.physics.arcade.collide(this,this.game_state.layers.collision);// touching ground tileif(this.body.blocked.down){this. state_machine.handle_input(newStateMachineExample.Command(«fall»,{}));}};StateMachineExample.Hero.prototype.process_on_down_input=function(event){«use strict»;switch(event.keyCode){casePhaser.Keyboard.LEFT :// walk leftthis.state_machine.handle_input(newStateMachineExample.Command(«walk»,{direction:»left»}));break;casePhaser.Keyboard.RIGHT:// walk rightthi s.state_machine.handle_input(newStateMachineExample.Command(«walk»,{direction:»right»}));break;casePhaser.Keyboard.UP:// jumpthis.state_machine.handle_input(newStateMachineExample.Command(«jump»,{}));break;}};StateMachineExample.Hero.prototype.process_on_up_input=function(event){«use strict»;switch(event.keyCode){casePhaser.Keyboard.LEFT:this.state_machine.handle_input(newStateMachineExample.Command(«stop»,{}));break;casePhaser.Keyboard.RIGHT:this.state_machine.handle_input(newStateMachineExample.Command(«stop»,{}));break;}};

    The “process_on_down_input” and “process_on_up_input” show those callback functions. We use the keyCode to identify the user input and create the new command accordingly. Notice that when we issue the “walk” command we must specify the direction.

    Finally, in the “update” method we check if the player is touching a ground tile and issue the “fall” command accordingly.

    Now we can play our demo and move our hero using the state machine!

    demo

    Possible extensions

    Even though our demo is complete, there are several extensions you can make. First, you can improve our state machine to allow the hero to change direction while jumping or even double jump. You can also issue the commands in the “update” method by checking the keys that are pressed, to see the difference.

    Try adding new states, like an invincible state when the hero gets a powerup item. Also, suppose that you want to create an item that changes the hero attack. You don’t want to check if the hero has collected this item every time in the attack state. So, you can create another state machine only to handle this new attack when it is available, and issue commands simultaneously for the two state machines. Therefore, there are endless possibilities.

    Finally, the main limitation of state machines is that sometimes they are too simple, and may not be suitable for complex AI. In this case, you can try different models, such as pushdown automatas and behavior trees.

    And that concludes our state machines tutorial. Tell me your opinion and questions in the comments section!

    Mensajes relacionados
    Huge generated game world from Unity

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *