En mis dos últimos tutoriales que creó un juego Fruit Ninja y ha añadido algo de contenido a la misma. En este último tutorial, vamos a añadir un nuevo modo de juego. Esto implicará, en la adición de una pantalla de título, refactorización parte de nuestro código y añadir algo de contenido adicional. Voy a cubrir el siguiente contenido en este tutorial: p>
- La adición de una fuente personalizada para todos los textos que se muestra li>
- Adición de un nuevo modo de juego llamado Time Attack. En este modo, el jugador debe cortar tantas frutas como sea posible antes de que acabe el tiempo li>
- Refactorizando el código de nuestros estados, a la reutilización entre los diferentes estados li>
- La adición de una pantalla de título, donde el jugador puede elegir entre los dos modos de juego li>
- La adición de dos cuttables casas prefabricadas para el modo Time Attack. El primero es un reloj, lo que aumenta el tiempo restante cuando se corta, y la segunda es una bomba de tiempo, lo que reduce el tiempo restante cuando se corta li>
Ul>
Para leer este tutorial, es importante que usted está familiarizado con los conceptos siguientes: p>
- Javascript y conceptos orientados a objetos. Li>
- conceptos básicos Phaser, tales como: estados, sprites, los grupos y la física de arcade li>
Ul>
Tabla de contenidos p>
Aprender Phaser mediante la construcción de 15 juegos h2>
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 strong> En línea En este supuesto The Complete el juego para móvil Curso de Desarrollo – Construir 15 juegos. p>
Activos de derechos de autor h2>
El activo bomba usada en este tutorial fue hecho por Alucard bajo Creative Commons License (http://opengameart.org/content/bomb-2d), que permite el uso comercial con atribución. P>
archivos de código fuente h2>
Puede descargar los archivos de código fuente tutorial aquí. P>
Juego Unidos h2>
utilizará los siguientes estados para ejecutar nuestro juego: p>
- Estado de arranque: Carga un archivo JSON con la información de nivel y comienza la carga del Estado li>
- Cargando Estado: carga todos los recursos del juego, y se inicia el siguiente estado li>
- Título del estado: muestra la pantalla de título, lo que permite al jugador elegir entre los dos modos de juego: Clásico y Time Attack li>
- Estado clásico: se ejecuta el modo de juego que estábamos trabajando en los dos últimos tutoriales, en el que los cortes jugador frutos hasta que se queda sin vidas li>
- Time Attack Estado: nuevo modo de juego, donde los cortes jugador frutos hasta que se queda sin tiempo li>
Ul>
Hay una gran cantidad de cambios en los códigos de estados, ya que vamos a refactorizar el código para permitir que varios modos de juego. Así que, para simplificar, voy a mostrar los cambios que son necesarios. P>
Refactorizando el código para añadir múltiples modos de juego h2>
Dado que vamos a tener múltiples estados, tenemos que identificar código común entre ellas y ponerla en otro estado, que se ampliará. Con el fin de hacer eso, vamos a crear los siguientes estados de padres a extenderse: p>
- JSONLevelState: carga un archivo JSON ya que estábamos usando en los últimos tutoriales, con los activos, los grupos y las casas prefabricadas li>
- LevelState: representa un nivel genérico. Es responsable de la detección de golpes, la comprobación de colisiones y que muestra la puntuación y el juego sobre la pantalla li>
Ul>
La jerarquía de estados se muestra en la figura siguiente. Tenga en cuenta que se extiende TitleState JSONLevelState, ya que vamos a iniciarlo desde un archivo JSON, pero no se extiende LevelState, ya que no es un estado del juego. Por otro lado, tanto ClassicState y TimeAttackState se extenderán LevelState, sobrescribir los métodos necesarios y añadiendo otros nuevos. P>
p>
El código para JSONLevelState y LevelState se muestran abajo. JSONLevelState tiene el método “create_prefab” y el código para crear desde el archivo JSON. LevelState tiene el “start_swipe”, “end_swipe”, “check_collision”, “init_hud”, los métodos de “restart_level” “game_over” y. Tenga en cuenta que la variable de mayor puntuación puede ser diferente según el modo de juego, por lo que tenemos que salir de su nombre como una variable que se encuentra en otras implementaciones del estado, como se puede ver en el método “game_over”. P>
var Fruit Ninja = Fruit Ninja || {}; FruitNinja.JSONLevelState = function () { «utilizar estricta»; Phaser.State.call (this); this.prefab_classes = {};}; FruitNinja.JSONLevelState.prototype = Object.create (Phaser.State.prototype); FruitNinja.JSONLevelState.prototype.constructor = FruitNinja.JSONLevelState; FruitNinja.JSONLevelState.prototype.init = function (level_data) { «utilizar estricta»; this.level_data = level_data; this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.scale.pageAlignHorizontally = true; this.scale.pageAlignVertically = true;}; FruitNinja.JSONLevelState.prototype.create = function () { «utilizar estricta»; GROUP_NAME var, prefab_name; // crear grupos this.groups = {}; this.level_data.groups.forEach (function (GROUP_NAME) {this.groups [GROUP_NAME] = this.game.add.group ();}, this); // crear prefabricadas this.prefabs = {}; para (prefab_name en this.level_data.prefabs) {if (this.level_data.prefabs.hasOwnProperty (prefab_name)) {// crear prefabricada this.create_prefab (prefab_name, this.level_data.prefabs [prefab_name]); }}}; FruitNinja.JSONLevelState.prototype.create_prefab = function (prefab_name, prefab_data) { «utilizar estricta»; prefab_position var, prefabricada; // crear el objeto de acuerdo con su tipo si (this.prefab_classes.hasOwnProperty (prefab_data.type)) {if (prefab_data.position.x> 0 && prefab_data.position.x <= 1) {// posición como porcentaje prefab_position = new Phaser.Point (prefab_data.position.x * this.game.world.width, prefab_data.position.y * this.game.world.height); } Else {// posición como absoluta número prefab_position = prefab_data.position; } = prefabricadas nuevas this.prefab_classes [prefab_data.type] (Esto, prefab_name, prefab_position, prefab_data.properties); }}; 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758varFruitNinja = Fruit Ninja || {}; FruitNinja.JSONLevelState = function () { "utilizar estricta"; Phaser.State.call (this); this.prefab_classes = {};}; FruitNinja.JSONLevelState.prototype = Object. crear (Phaser.State.prototype); FruitNinja.JSONLevelState.prototype.constructor = FruitNinja.JSONLevelState; FruitNinja.JSONLevelState.prototype.init = function (level_data) { "utilizar estricta"; this.level_data = level_data; this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.scale.pageAlignHorizontally = true; this.scale.pageAlignVertically = true;}; FruitNinja.JSONLevelState.prototype.create = function () { "utilizar estricta"; vargroup_name, prefab_name; // crear groupsthis .Grupos = {}; this.level_data.groups.forEach (function (GROUP_NAME) {this.groups [GROUP_NAME] = this.game.add.group ();}, this); // crear prefabsthis.prefabs = {} ; para (inthis.level_data.prefabs prefab_name) {if (this.level_data.prefabs.hasOwnProperty (prefab_name)) {// crear prefabthis.cre ate_prefab (prefab_name, this.level_data.prefabs [prefab_name]);}}}; FruitNinja.JSONLevelState.prototype.create_prefab = function (prefab_name, prefab_data) { "utilizar estricta"; varprefab_position, prefabricada; // crear el objeto de acuerdo con su typeif (this.prefab_classes.hasOwnProperty (prefab_data.type)) {if (prefab_data.position.x> 0 && prefab_data.position.x <= 1) {// posición como porcentaje prefab_position = new Phaser.Point (prefab_data.position.x * this.game.world.width, prefab_data.position.y * this.game.world.height);} else {// posición como numberprefab_position absoluta = prefab_data.position;} prefabricada = newthis.prefab_classes [prefab_data.type] (este , prefab_name, prefab_position, prefab_data.properties);}}; var Fruit Ninja = Fruit Ninja || {}; FruitNinja.LevelState = function () { "utilizar estricta"; FruitNinja.JSONLevelState.call (this); this.prefab_classes = {};}; FruitNinja.LevelState.prototype = Object.create (FruitNinja.JSONLevelState.prototype); FruitNinja.LevelState.prototype.constructor = FruitNinja.LevelState; FruitNinja.LevelState.prototype.init = function (level_data) { "utilizar estricta"; FruitNinja.JSONLevelState.prototype.init.call (esto, level_data); sistema de física this.game.physics.startSystem (Phaser.Physics.ARCADE) // empezar; this.game.physics.arcade.gravity.y = 1,000; this.MINIMUM_SWIPE_LENGTH = 50; this.score = 0;}; FruitNinja.LevelState.prototype.create = function () { "utilizar estricta"; FruitNinja.JSONLevelState.prototype.create.call (this); // añadir eventos para comprobar si hay golpe this.game.input.onDown.add (this.start_swipe, este); this.game.input.onUp.add (this.end_swipe, este); this.init_hud ();}; FruitNinja.LevelState.prototype.start_swipe = función (puntero) { "uso estricto"; this.start_swipe_point = new Phaser.Point (pointer.x, pointer.y);}; FruitNinja.LevelState.prototype.end_swipe = función (puntero) { "utilizar estricta"; var swipe_length, cut_style, corte; this.end_swipe_point = new Phaser.Point (pointer.x, pointer.y); swipe_length = Phaser.Point.distance (this.end_swipe_point, this.start_swipe_point); // si la longitud de golpe es mayor que el mínimo, se detecta un golpe si (swipe_length> = this.MINIMUM_SWIPE_LENGTH) {// crear una nueva línea que el golpe y comprobar las colisiones cut_style = {line_width: 5, color: 0xE82C0C, alpha: 1}; corte = nuevo FruitNinja.Cut (esto «corte», {x: 0, y: 0}, {grupo: «cortes», comenzar: this.start_swipe_point, final: this.end_swipe_point, duración: 0,3, el estilo: cut_style} ); this.swipe = new Phaser.Line (this.start_swipe_point.x, this.start_swipe_point.y, this.end_swipe_point.x, this.end_swipe_point.y); this.groups.fruits.forEachAlive (this.check_collision, este); this.groups.bombs.forEachAlive (this.check_collision, este); this.groups.special_fruits.forEachAlive (this.check_collision, este); this.groups.time_bombs.forEachAlive (this.check_collision, este); this.groups.clocks.forEachAlive (this.check_collision, este); }}; FruitNinja.LevelState.prototype.check_collision = función (objeto) { «utilizar estricta»; var object_rectangle, línea 1, línea 2, línea 3, line4, intersección; // crear un rectángulo para el cuerpo objeto object_rectangle = new Phaser.Rectangle (object.body.x, object.body.y, object.body.width, object.body.height); // cheque por intersecciones con cada línea 1 borde rectángulo = new Phaser.Line (object_rectangle.left, object_rectangle.bottom, object_rectangle.left, object_rectangle.top); line2 = new Phaser.Line (object_rectangle.left, object_rectangle.top, object_rectangle.right, object_rectangle.top); línea 3 = new Phaser.Line (object_rectangle.right, object_rectangle.top, object_rectangle.right, object_rectangle.bottom); line4 = new Phaser.Line (object_rectangle.right, object_rectangle.bottom, object_rectangle.left, object_rectangle.bottom); intersección = this.swipe.intersects (línea 1) || this.swipe.intersects (line2) || this.swipe.intersects (línea 3) || this.swipe.intersects (line4); si (intersección) {// si no se encuentra una intersección, cortar la object.cut objeto (); }}; FruitNinja.LevelState.prototype.init_hud = function () { «utilizar estricta»; var score_position, score_style, partitura; // crear anotar score_position prefabricada = new Phaser.Point (20, 20); score_style = {font: «48px Shojumaru», relleno: «#fff»}; puntuación = nueva FruitNinja.Score (esto, «puntuación», score_position, {texto: «Frutas», estilo: score_style, grupo: «HUD»});}; FruitNinja.LevelState.prototype.game_over = function () { » utilizar estricta «; var game_over_panel, game_over_position, game_over_bitmap, panel_text_style; // si la puntuación actual es superior a la puntuación más alta, que se actualizará si (localStorage [this.highest_score] || this.score> localStorage [this.highest_score]) {localStorage [this.highest_score] = this.score; } // crear un mapa de bits no mostrar el juego sobre game_over_position del panel = new Phaser.Point (0, this.game.world.height); game_over_bitmap = this.add.bitmapData (this.game.world.width, this.game.world.height); game_over_bitmap.ctx.fillStyle = «# 000»; game_over_bitmap.ctx.fillRect (0, 0, this.game.world.width, this.game.world.height); panel_text_style = {game_over: {font: «32px Shojumaru», relleno: «#FFF»}, current_score: {font: «20 píxeles Shojumaru», relleno: «#FFF»}, highest_score: {font: «18px Shojumaru», relleno : «#FFF»}}; // crear el juego sobre el panel game_over_panel = new FruitNinja.GameOverPanel (esto «game_over_panel», game_over_position, {textura: game_over_bitmap, grupo: «HUD», text_style: panel_text_style, animation_time: 500}); this.groups.hud.add (game_over_panel);}; FruitNinja.LevelState.prototype.restart_level = function () { «utilizar estricta»; this.game.state.restart (verdadero, falso, this.level_data);}; 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112varFruitNinja = Fruit Ninja || {}; FruitNinja.LevelState = function () { «utilizar estricta»; FruitNinja.JSONLevelState.call (this); esta .prefab_classes = {};}; FruitNinja.LevelState.prototype = Object.create (FruitNinja.JSONLevelState.prototype); FruitNinja.LevelState.prototype.constructor = FruitNinja.LevelState; FruitNinja.LevelState.prototype.init = function (level_data) { «use strict»; FruitNinja.JSONLevelState.prototype.init.call (esto, level_data); // iniciar la física systemthis.game.physics.startSystem (Phaser.Physics.ARCADE); this.game.physics.arcade.gravity.y = 1000; this.MINIMUM_SWIPE_LENGTH = 50; this.score = 0;}; FruitNinja.LevelState.prototype.create = function () { «utilizar estricta»; FruitNinja.JSONLevelState.prototype.create.call (this); // add eventos a CH eck para swipethis.game.input.onDown.add (this.start_swipe, este); this.game.input.onUp.add (this.end_swipe, este); this.init_hud ();}; FruitNinja.LevelState.prototype. start_swipe = función (puntero) { «utilizar estricta»; this.start_swipe_point = newPhaser.Point (pointer.x, pointer.y);}; FruitNinja.LevelState.prototype.end_swipe = función (puntero) { «utilizar estricta»; varswipe_length , cut_style, corte; this.end_swipe_point = newPhaser.Point (pointer.x, pointer.y); swipe_length = Phaser.Point.distance (this.end_swipe_point, this.start_swipe_point); // si la longitud de golpe es mayor que el mínimo , un golpe es detectedif (swipe_length> = this.MINIMUM_SWIPE_LENGTH) {// crear una nueva línea que el golpe y comprobar para collisionscut_style = {line_width: 5, color: 0xE82C0C, alfa: 1}; corte = newFruitNinja.Cut (esto, «corte», {x: 0, y: 0}, {grupo: «cortes», inicio: this.start_swipe_point, final: this.end_swipe_point, duración: 0,3, el estilo: cut_style}); this.swipe = newPhaser.Line (this.start_swipe_point.x, this.start_swipe_point.y, this.end_swipe_point.x, this.end_swipe_point.y); this.grou ps.fruits.forEachAlive (this.check_collision, este); this.groups.bombs.forEachAlive (this.check_collision, este); this.groups.special_fruits.forEachAlive (this.check_collision, este); this.groups.time_bombs.forEachAlive (this.check_collision, este); this.groups.clocks.forEachAlive (this.check_collision, este);}}; FruitNinja.LevelState.prototype.check_collision = función (objeto) { «utilizar estricta»; varobject_rectangle, línea 1, línea 2, línea 3, line4, intersección; // crear un rectángulo para el objeto bodyobject_rectangle = newPhaser.Rectangle (object.body.x, object.body.y, object.body.width, object.body.height); // cheque por intersecciones con cada rectángulo edgeline1 = newPhaser.Line (object_rectangle.left, object_rectangle.bottom, object_rectangle.left, object_rectangle.top); line2 = newPhaser.Line (object_rectangle.left, object_rectangle.top, object_rectangle.right, object_rectangle.top); línea3 = newPhaser.Line (object_rectangle.right, object_rectangle.top, object_rectangle.right, object_rectangle.bottom); line4 = newPhaser.Line (object_rectangle .RIGHT, object_rectangle.bottom, object_rectangle.left, object_rectangle.bottom); intersección = this.swipe.intersects (línea 1) || this.swipe.intersects (line2) || this.swipe.intersects (línea 3) || esto. swipe.intersects (4 en Línea), si (intersección) {// si no se encuentra una intersección, cortar la objectobject.cut ();}}; FruitNinja.LevelState.prototype.init_hud = función () { «utilizar estricta»; varscore_position, score_style, la puntuación; // crear prefabscore_position puntuación = newPhaser.Point (20,20); score_style = {font: «48px Shojumaru», relleno: «# fff»}; puntuación = newFruitNinja.Score (esto, «puntuación», score_position , {text: «Fruits:», el estilo: score_style, grupo: «HUD»});}; FruitNinja.LevelState.prototype.game_over = function () { «utilizar estricta»; vargame_over_panel, game_over_position, game_over_bitmap, panel_text_style; // si la puntuación actual es superior a la puntuación más alta, actualizar ITIF (localStorage [this.highest_score] || this.score> localStorage [this.highest_score]) {localStorage [this.highest_score] = this.score;} // crear un mapa de bits no mostrar el juego sobre panelgame_over_position = newPhas er.Point (0, this.game.world.height); game_over_bitmap = this.add.bitmapData (this.game.world.width, this.game.world.height); game_over_bitmap.ctx.fillStyle = «# 000» ; game_over_bitmap.ctx.fillRect (0,0, this.game.world.width, this.game.world.height); panel_text_style = {game_over: {font: «32px Shojumaru», relleno: «# FFF»}, current_score : {font: «20px Shojumaru», relleno: «# FFF»}, highest_score: {font: «18px Shojumaru», relleno: «# FFF»}}; // crear el juego más panelgame_over_panel = newFruitNinja.GameOverPanel (esto, «game_over_panel», game_over_position, {textura: game_over_bitmap, grupo: «HUD», text_style: panel_text_style, animation_time: 500}); this.groups.hud.add (game_over_panel);}; FruitNinja.LevelState.prototype.restart_level = function ( ) { «uso estricto»; this.game.state.restart (verdadero, falso, this.level_data);};Ahora, podemos escribir el código para ClassicState, lo que representa el modo de juego que ya teníamos. Dado que la mayor parte de su lógica ya está en LevelState y JSONLevelState, sólo tenemos que configurar el nombre de más alto puntaje y sobrescribir el método “init_hud” para incluir vidas jugador, como se muestra a continuación. P>
var Fruit Ninja = Fruit Ninja || {}; FruitNinja.ClassicState = function () { «utilizar estricta»; FruitNinja.LevelState.call (this); this.prefab_classes = { «fruit_spawner»: FruitNinja.FruitSpawner.prototype.constructor, «bomb_spawner»: FruitNinja.BombSpawner.prototype.constructor, «special_fruit_spawner»: FruitNinja.SpecialFruitSpawner.prototype.constructor, «fondo»: FruitNinja.Prefab.prototype .constructor};}; FruitNinja.ClassicState.prototype = Object.create (FruitNinja.LevelState.prototype); FruitNinja.ClassicState.prototype.constructor = FruitNinja.ClassicState; FruitNinja.ClassicState.prototype.init = function (level_data) { «uso estricto»; FruitNinja.LevelState.prototype.init.call (esto, level_data); this.lives = 3; this.highest_score = «classic_score»;}; FruitNinja.ClassicState.prototype.init_hud = function () { «utilizar estricta»; FruitNinja.LevelState.prototype.init_hud.call (this); lives_position var, vidas; // crear vidas prefabricada lives_position = new Phaser.Point (0,75 * this.game.world.width, 20); vidas = new FruitNinja.Lives (Esto, «vidas», lives_position, {textura: «sword_image», grupo: «HUD», «vida»: 3, «lives_spacing»: 50});}; 12345678910111213141516171819202122232425262728293031323334varFruitNinja = Fruit Ninja || { }; FruitNinja.ClassicState = function () { «utilizar estricta»; FruitNinja.LevelState.call (this); this.prefab_classes = { «fruit_spawner»: FruitNinja.FruitSpawner.prototype.constructor, «bomb_spawner»: FruitNinja.BombSpawner.prototype .constructor, «special_fruit_spawner»: FruitNinja.SpecialFruitSpawner.prototype.constructor, «fondo»: FruitNinja.Prefab.prototype.constructor};}; FruitNinja.ClassicState.prototype = Object.create (FruitNinja.LevelState.prototype); FruitNinja.ClassicState .prototype.constructor = FruitNinja.ClassicState; FruitNinja.ClassicState.prototype.init = function (level_data) { «utilizar estricta»; FruitNinja.LevelState.prototype.init.call (esto, level_data); this.lives = 3; esto. highest_score = «classic_score»;}; FruitNinja.ClassicState.prototype.init_hud = function () { «utilizar estricta»; FruitNinja.LevelStat e.prototype.init_hud.call (this); varlives_position, vidas; // crear vidas prefablives_position = newPhaser.Point (0,75 * this.game.world.width, 20); vive = newFruitNinja.Lives (esto, «vidas», lives_position, {textura: «sword_image», grupo: «HUD», «vida»: 3, «lives_spacing»: 50});};te dejaré el código TimeAttackState para cuando vamos a utilizarlo . p>
Custom fuente h2>
Vamos a añadir una fuente personalizada para mostrar todo el contenido de texto en nuestro juego, como el título, los elementos de menú, y el HUD. Puede descargar la fuente que vamos a utilizar aquí o con el código fuente. Para añadirlo al juego, sólo tiene que añadir una fuente cara en su archivo index.html. Sin embargo, podría no funcionar para todos los navegadores, de acuerdo con algunos informes en los foros de Phaser. Para evitar esto, se puede añadir un estilo para cargar la fuente personalizada y un objeto div en su archivo index.html para forzar al navegador a cargar la fuente. P>
El index.html con ambos métodos se muestra a continuación. Observe que hemos cambiado la propiedad de “izquierda” del estilo .fontPreload ser -100px, por lo que el objeto div no será visible. Después de hacer eso, sólo tiene que utilizar el nombre de familia de fuentes (Shojumaru) en la propiedad de estilo de nuestras casas prefabricadas de texto. P>
Aprender el desarrollo del juego en ZENVA.com title>
- JSONLevelState: carga un archivo JSON ya que estábamos usando en los últimos tutoriales, con los activos, los grupos y las casas prefabricadas li>
- Estado de arranque: Carga un archivo JSON con la información de nivel y comienza la carga del Estado li>
- Javascript y conceptos orientados a objetos. Li>