Introducción
En la historia de los juegos de ordenador algunos juegos han creado y llevado a las empresas enteras sobre sus hombros. Uno de esos juegos es, sin duda Mario Bros. El personaje de Mario apareció por primera vez en el juego Donkey Kong , y se hizo muy famoso dentro de su propia serie de juegos a partir de la original de Mario Bros. en 1983. Hoy en día una gran cantidad de spin-offs y salto de carreras en 3D y se están produciendo centrar el personaje de Mario. En este artículo vamos a desarrollar un muy simple Super Mario clon, que es fácilmente extensible con nuevos elementos, enemigos, héroes y de los niveles de los cursos.
El código del juego en sí será escrito en JavaScript orientado a objetos. Ahora que suena como una trampa ya que JavaScript es un lenguaje de scripting prototipo basado, sin embargo, hay varios objetos orientados como posibles patrones. Vamos a investigar algo de código que nos dará algunas restricciones orientados a objetos. Esto será muy útil para permanecer en el mismo patrón a través de la codificación enteros.
Fondo
La versión original de esta aplicación fue desarrollada por dos estudiantes que tomaron mi conferencia sobre «Programación de aplicaciones web con HTML5, CSS3 y JavaScript». Les di un código básico para el motor y desarrollaron un juego que incluye un editor de niveles, sonidos y gráficos. El juego en sí no contiene un montón de errores, sin embargo, el rendimiento era bastante pobre y debido al uso poco frecuente de las propiedades de prototipos de la extensibilidad también fue limitado. El quemador principal actuación fue el uso del plug-in de jQuery vivaz (que se puede encontrar aquí ). En este caso yo soy el único culpable, ya que se recomienda su uso por la simplicidad. La cuestión aquí es que spritely sí hace un buen trabajo en hacer una animación, pero no cien o más.Cada nueva animación (a pesar de que generó en el mismo momento) tendrá su propio ciclo programado intervalo de visita.
Para este artículo he decidido centrarse en las cosas principales del juego. A través de este artículo vamos a reescribir todo el juego – con los beneficios, como se explica más arriba:
- El juego será fácilmente ampliable
- El rendimiento no sufrirá tanto por la animación de objetos
- El inicio o la acción de pausa del juego tendrá un impacto directo en todos los elementos
- El juego no se basan en elementos externos como sonidos, gráficos, …
La última declaración suena como un loco escribiendo este artículo. Sin embargo, esto es, en mi opinión muy importante. Tomemos el siguiente código para mostrar mi punto de vista:
$(document).ready(function() {
var sounds = new SoundManager();//**
var level = new Level('world');//world is the id of the corresponding DOM container
level.setSounds(sounds);//*
level.load(definedLevels[0]);
level.start();
keys.bind();
});
Ahora bien, esto no parece tan desagradable, pero esto es, de hecho, todo lo que se necesita para jugar a un juego de Mario con HTML5. Los detalles que nos quedan aquí todo se explicará más adelante. Volver a la declaración de arriba vemos la línea marcada con un comentario de dos estrellas ( / / **
): Aquí una nueva instancia de la clase de director de sonido es creado. Este también se carga los efectos de sonido. ¿Qué pasa si queremos pasar por alto esta línea? No tendríamos una instancia de trabajo del administrador de sonido. Ahora la siguiente cosa a notar es que la instancia del gestor de sonido no se guarda en un ámbito global, pero a nivel local solo. Podemos hacer esto porque no hay ningún objeto en todo el juego requiere una instancia determinada de esta clase. Lo que se hace en su lugar?Si un objeto se quiere reproducir un sonido que llama a un método que es proporcionado por el nivel (cada objeto tiene que pertenecer a un nivel para poder existir, ya que un nivel de clase es el único lugar donde los objetos del juego se crean).
Ahora aquí es donde la línea con el comentario estrella solitaria ( / / *
) entra en juego. Si no llamamos a lossetSounds ()
el método de una instancia de nivel de nivel no va a tener un gestor de sonido adecuada instancia de la clase unida. Por lo tanto todas las solicitudes para reproducir un sonido por cualquier objeto será colocado en la papelera. Esto hace que el gerente de la clase enchufable de sonido, ya que sólo tiene que quitar dos líneas de código para eliminar por completo el administrador de sonido. Por otro lado, sólo tenemos que añadir dos líneas de código. Por supuesto, esto sería algo que se puede lograr más elegante dentro de C # mediante la reflexión (como lo requiere la inyección de dependencias u otros patrones).
El resto de este pequeño código es sólo para cargar un nivel de partida (en este caso se utiliza el primero de una lista de niveles predefinidos) y ponerlo en marcha. El objeto global llamado el teclado las teclas
puede obligar ()
ounbind ()
todos los eventos clave en el documento.
El diseño básico
Vamos a omitir la implementación del administrador de sonido en este artículo. Habrá otro artículo acerca de un editor de buen nivel y varias otras cosas interesantes, una de ellas será la implementación del administrador de sonido. El esquema del documento de base para el juego de Super Mario se parece a lo siguiente:
<!doctype html>
<html>
<head>
<meta charset=utf-8 />
<title>Super Mario HTML5</title>
<link href="Content/style.css" rel="stylesheet" />
</head>
<body>
<div id="game">
<div id="world">
</div>
<div id="coinNumber" class="gauge">0</div>
<div id="coin" class="gaugeSprite"></div>
<div id="liveNumber" class="gauge">0</div>
<div id="live" class="gaugeSprite"></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="Scripts/testlevels.js"></script>
<script src="Scripts/oop.js"></script>
<script src="Scripts/keys.js"></script>
<script src="Scripts/sounds.js"></script>
<script src="Scripts/constants.js"></script>
<script src="Scripts/main.js"></script>
</body>
</html>
Así que después de todo, no tenemos margen de beneficio mucho aquí. Vemos que el mundo está contenido dentro de un área de juego. El área de juego incluye los indicadores (y algunos sprites para animar los indicadores). Este sencillo juego de Mario sólo contiene dos indicadores: uno para las monedas y otra para el número de vidas.
Una sección muy importante es la lista de JavaScript que se incluirán. Por razones de rendimiento que les coloca en la parte inferior de la página. Esta es también la razón por la cual se obtiene a partir de una jQuery CDN (que es Google en este caso). Los otros guiones deben ser empacados en una sola y reducir al mínimo (esto se llama la agrupación y es una de las características incluidas de ASP.NET MVC 4). No vamos a reunir todas estas secuencias de comandos para este artículo. Vamos a echar un vistazo al contenido de los archivos de script:
- El testlevels.js archivo es bastante grande y debe reducirse al mínimo. Contiene los niveles pre-hechas. Todos esos niveles en los que llevan a cabo los dos estudiantes de mi clase. El primer nivel es un clon del primer nivel de Super Mario Land para la Game Boy clásica (si no me equivoco aquí).
- El oop.js archivo contiene el código para simplificar JavaScript orientado a objetos. Vamos a discutir eso en un minuto.
- El keys.js archivo crea la
teclas
del objeto. Si queremos diseñar un modo multijugador u otras características que también deben modulize esta secuencia de comandos (como lo hicimos con la clase de director de sonido). - El administrador de sonido está escrito en el sounds.js archivo. La idea básica es que la web de audio API ( Sitio Oficial ) podría superar a la API actual. Ahora el problema es que la web de la API de audio se limita a Google Chrome y algunos nightly builds de Safari. Si la distribución es correcto que esto podría ser la manera de conseguir el audio que necesitamos para los juegos en el navegador.
- Los archivos constants.js contiene las enumeraciones y los métodos de ayuda muy básicas.
- Todos los demás objetos se encuentran agrupados en los archivos main.js .
Antes de entrar en detalles de la aplicación que debe tener una mirada en el archivo CSS:
@font-face {
font-family: 'SMB';
src: local('Super Mario Bros.'),
url('fonts/Super Mario Bros.ttf') format('truetype');
font-style: normal;
}
#game {
height: 480px; width: 640px; position: absolute; left: 50%; top: 50%;
margin-left: -321px; margin-top: -241px; border: 1px solid #ccc; overflow: hidden;
}
#world {
margin: 0; padding: 0; height: 100%; width: 100%; position: absolute;
bottom: 0; left: 0; z-index: 0;
}
.gauge {
margin: 0; padding: 0; height: 50px; width: 70px; text-align: right; font-size: 2em;
font-weight: bold; position: absolute; top: 17px; right: 52px; z-index: 1000;
position: absolute; font-family: 'SMB';
}
.gaugeSprite {
margin: 0; padding: 0; z-index: 1000; position: absolute;
}
#coinNumber {
left: 0;
}
#liveNumber {
right: 52px;
}
#coin {
height: 32px; width: 32px; background-image : url(mario-objects.png);
background-position: 0 0; top: 15px; left: 70px;
}
#live {
height: 40px; width: 40px; background-image : url(mario-sprites.png);
background-position : 0 -430px; top: 12px; right: 8px;
}
.figure {
margin: 0; padding: 0; z-index: 99; position: absolute;
}
.matter {
margin: 0; padding: 0; z-index: 95; position: absolute; width: 32px; height: 32px;
}
Ahora bien, no esta muy larga de nuevo (pero no tenía mucho margen de beneficio sea). En la parte superior se introduce alguna fuente de lujo con el fin de dar a nuestro juego de Mario como el aspecto (de tipo racional). Luego sólo hay que configurar todo por la característica de una resolución de 640 x 480 píxeles. Es muy importante que el juego hace ocultar cualquier desbordamiento. Por lo tanto, sólo puede mover el mundo en el juego. Esto significa que el juego actúa como una especie de punto de vista. El mismo nivel se coloca en el mundo. Los indicadores se colocan en las primeras filas de la vista. Cada figura tendrá una clase CSS llamada la figura
adjunta. Lo mismo ocurre con la materia como elementos de decoración, de tierra o elementos: los elementos tienen una clase CSS llamadamateria
. Muy importante es la propiedad z-index
. Siempre queremos que un objeto dinámico que estar delante de un ser estático (hay excepciones, pero llegaremos a eso más adelante).
Orientado a objetos JavaScript
Hacer la codificación orientada a objetos con JavaScript, no es difícil, pero un poco desordenado. Una de las razones de esto es que hay múltiples maneras de hacerlo. Cada forma tiene sus propias ventajas y desventajas. Para este juego queremos seguir un patrón estrictamente. Por lo tanto yo propongo la siguiente manera:
var reflection = {};
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /b_superb/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){ };
// Create a new Class that inherits from this class
Class.extend = function(prop, ref_name) {
if(ref_name)
reflection[ref_name] = Class;
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn) {
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
Este código está muy inspirado en prototipo y se ha construido por John Resig. Él escribió un artículo acerca de los problemas integrales de codificación en su blog ( el artículo de John Resig sobre el código de JavaScript orientado a objetos ). El código se envuelve en un método ejecutado directamente anónimo con el fin de las variables internas de alcance. La clase
objeto es una extensión de la ventana de
objeto (que debe ser el objeto subyacente, es decir,este
si este archivo script se ejecuta desde un navegador web).
Mi extensión de este código es la posibilidad de nombrar a la clase. JavaScript no tiene las propiedades de reflexión de gran alcance, razón por la cual estamos obligados a esforzarnos un poco más en el proceso de descripción de la clase.Al asignar el constructor de una variable (que veremos en un momento), tenemos la opción de pasar un nombre de la clase como segundo argumento. Si no hacemos esto, entonces una referencia al constructor se coloca en el objetode reflexión
con el segundo argumento como el nombre de la propiedad.
Una construcción sencilla clase es la siguiente:
var TopGrass = Ground.extend({
init: function(x, y, level) {
var blocking = ground_blocking.top;
this._super(x, y, blocking, level);
this.setImage(images.objects, 888, 404);
},
}, 'grass_top');
Aquí estamos creando una clase llamada TopGrass
, que hereda de la tierra
clase. El init ()
representa el método constructor de la clase. Con el fin de llamar al constructor base (que no es necesario) tenemos que llamar a más de la this._super ()
método. Este es un método especial que se puede llamar en cualquier método reemplazado.
Una observación importante en este caso : Es importante distinguir aquí entre el polimorfismo real (que se puede hacer en lenguajes orientados a objetos con tipos estáticos como C #) y el que aquí se presenta. Obviamente no es posible acceder a métodos de los padres desde el exterior (ya que no podemos cambiar nuestra forma de ver el objeto – que siempre es un objeto dinámico). Así que la única manera de acceder al método de la matriz es llamar a lathis._super ()
método en el método reemplazado correspondiente. También hay que señalar que esta afirmación no es absoluta, sin embargo, es cierto con el código presentado anteriormente.
La tierra
de clase es bastante interesante (sólo una capa intermedia). Así que vamos a echar un vistazo a la clase base de tierra
, llamada la Materia
:
var Matter = Base.extend({
init: function(x, y, blocking, level) {
this.blocking = blocking;
this.view = $(DIV).addClass(CLS_MATTER).appendTo(level.world);
this.level = level;
this._super(x, y);
this.setSize(32, 32);
this.addToGrid(level);
},
addToGrid: function(level) {
level.obstacles[this.x / 32][this.level.getGridHeight() - 1 - this.y / 32] = this;
},
setImage: function(img, x, y) {
this.view.css({
backgroundImage : img ? c2u(img) : 'none',
backgroundPosition : '-' + (x || 0) + 'px -' + (y || 0) + 'px',
});
this._super(img, x, y);
},
setPosition: function(x, y) {
this.view.css({
left: x,
bottom: y
});
this._super(x, y);
},
});
Este sentido, ampliar la base de
la clase (que es una de las clases superiores). Todas las clases que heredan de la materia
son estáticas 32 x 32 bloques de píxeles (que no se puede mover) y contienen una variable de bloqueo (a pesar de que se podría establecer que no hay bloqueo, que es el caso, por ejemplo para la decoración). Debido a que cada materia
, así como la figura
instancia representa un objeto visible que tenemos que crear un punto de vista adecuado para ello (usando jQuery). Esto también explica por qué el setImage ()
método se ha ampliado con el fin de establecer la imagen de la vista.
La misma razón se puede aplicar por razones imperiosas de la setPosition ()
método. El addToGrid
método ha sido añadido a fin de dar clases los niños la posibilidad de desactivar el comportamiento estándar de añadir la instancia creada para el obstáculos
de matriz del nivel dado.
Controlar el juego
El juego está básicamente controlado por el teclado. Por lo tanto tenemos que obligar a los controladores de eventos correspondientes al documento. Acabamos interesado en unas pocas teclas, que pueden ser prensadas o liberados.Puesto que necesitamos para controlar el estado de cada tecla que acaba de ampliar el objeto teclas
con las propiedades correspondientes (por ejemplo, a la izquierda
de la tecla izquierda, la derecha
de la tecla de la derecha y así sucesivamente). Todo lo que necesitamos para llamar son los métodos bind ()
para enlazar las claves para el documento o unbind ()
para ponerlos en libertad. Una vez más estamos usando jQuery para hacer el trabajo real en el navegador para nosotros – que nos da más tiempo para pasar en nuestros problemas en lugar de cualquier otro tema en cualquier navegador o el legado.
var keys = {
//Method to activate binding
bind : function() {
$(document).on('keydown', function(event) {
return keys.handler(event, true);
});
$(document).on('keyup', function(event) {
return keys.handler(event, false);
});
},
//Method to reset the current key states
reset : function() {
keys.left = false;
keys.right = false;
keys.accelerate = false;
keys.up = false;
keys.down = false;
},
//Method to delete the binding
unbind : function() {
$(document).off('keydown');
$(document).off('keyup');
},
//Actual handler - is called indirectly with some status
handler : function(event, status) {
switch(event.keyCode) {
case 57392://CTRL on MAC
case 17://CTRL
case 65://A
keys.accelerate = status;
break;
case 40://DOWN ARROW
keys.down = status;
break;
case 39://RIGHT ARROW
keys.right = status;
break;
case 37://LEFT ARROW
keys.left = status;
break;
case 38://UP ARROW
keys.up = status;
break;
default:
return true;
}
event.preventDefault();
return false;
},
//Here we have our interesting keys
accelerate : false,
left : false,
up : false,
right : false,
down : false,
};
Otra forma de hacer esto sería utilizar el poder de jQuery. Podemos asignar el mismo método para el evento y tecla con algún argumento personalizado. Este argumento sería entonces determinar el evento que fue el responsable de la llamada al método. Sin embargo, esta forma es un poco más limpio y más comprensible.
CSS Spritesheets
Todos los gráficos del juego se llevan a cabo mediante el uso de spritesheets CSS. Esta característica es muy sencillo de utilizar si conocemos las siguientes líneas. En primer lugar con el fin de utilizar una imagen como un spritesheet tenemos que asignar la imagen como imagen de fondo para el elemento correspondiente. Es importante no desactivar la repetición de fondo, ya que esto nos dará la ventaja de las condiciones de contorno periódicas. Por lo general, esto no daría lugar a consecuencias malas, sin embargo, con condiciones de frontera periódicas que podemos hacer mucho más.
El siguiente paso consiste en establecer una especie de compensación a la imagen de fondo que acabamos de asignar.Por lo general, el desplazamiento es (0, 0). Esto significa que las coordenadas superior izquierda de nuestro elemento es la coordenada superior izquierda de nuestra spritesheet. El desplazamiento que se introduce es relativo al elemento, es decir, mediante el establecimiento de un desplazamiento de (20, 10) que fijaría la parte superior izquierda (0, 0) coordinar de la spritesheet a 20 píxeles a la izquierda y los píxeles 10 a la parte superior lado del elemento. Si usamos (-20, -10) en su lugar, vamos a tener el efecto de tener la parte superior izquierda (0, 0) coordinar las del exterior spritesheet de nuestro elemento. Por lo tanto la parte visible de la imagen estará dentro de la imagen (y no en la frontera).
Esto ilustra cómo spritesheets trabajar en CSS. Todo lo que necesitamos son nuestras coordenadas y nosotros somos buenos para ir. En general podemos distinguir entre spritesheets homogéneos y heterogéneos spritesheets. Mientras que el primero tiene un una rejilla fija (por ejemplo, 32px 32px veces para cada elemento, lo que resulta en sencillos cálculos de compensación), este último no tiene una rejilla fija. La ilustración muestra un spritesheet heterogéneo. Si creamos un spritesheet de nuestra página web con el fin de aumentar el rendimiento por la disminución de las peticiones HTTP, por lo general va a terminar con un spritesheet heterogéneo.
Para animaciones que debe pegarse a spritesheets homogéneos con el fin de disminuir la cantidad de información necesaria para la propia animación. El juego utiliza el siguiente fragmento de código que se ejecutará la animación spritesheet:
var Base = Class.extend({
init: function(x, y) {
this.setPosition(x || 0, y || 0);
this.clearFrames();
},
/* more basic methods like setPosition(), ... */
setupFrames: function(fps, frames, rewind, id) {
if(id) {
if(this.frameID === id)
return true;
this.frameID = id;
}
this.frameCount = 0;
this.currentFrame = 0;
this.frameTick = frames ? (1000 / fps / constants.interval) : 0;
this.frames = frames;
this.rewindFrames = rewind;
return false;
},
clearFrames: function() {
this.frameID = undefined;
this.frames = 0;
this.currentFrame = 0;
this.frameTick = 0;
},
playFrame: function() {
if(this.frameTick && this.view) {
this.frameCount++;
if(this.frameCount >= this.frameTick) {
this.frameCount = 0;
if(this.currentFrame === this.frames)
this.currentFrame = 0;
var $el = this.view;
$el.css('background-position', '-' + (this.image.x + this.width *
((this.rewindFrames ? this.frames - 1 : 0) - this.currentFrame)) +
'px -' + this.image.y + 'px');
this.currentFrame++;
}
}
},
});
Se incluyeron la funcionalidad spritesheet en el más básico (juego) de clase, ya que todas las clases más especializadas como las figuras o elementos que heredan de esta clase. Esto garantiza la disponibilidad de la animación spritesheet. En principio, cada objeto tiene una función para configurar la animación spritesheet llamasetupFrames ()
. Los únicos parámetros que deben ser especificados son el número de fotogramas por segundo (fps
) y el número de fotogramas en la spritesheet ( marcos
). Por lo general, la animación se ejecuta de izquierda a derecha – por lo tanto, se incluyó el parámetro de rebobinar
para cambiar la animación de ser de derecha a izquierda.
Una cosa importante de este método es opcional Identificación del
parámetro. Aquí se puede asignar un valor que permita identificar a la animación actual. Esto puede ser utilizado con el fin de distinguir si la animación que está a punto de ser creado ya se está ejecutando. Si este es el caso de que no se restablecerá los frameCount
variables internas y otros. ¿Cómo puede este código se utilizará dentro de alguna clase? Veamos con un ejemplo de la Mario
de clase en sí:
var Mario = Hero.extend({
/*...*/
setVelocity: function(vx, vy) {
if(this.crouching) {
vx = 0;
this.crouch();
} else {
if(this.onground && vx > 0)
this.walkRight();
else if(this.onground && vx < 0)
this.walkLeft();
else
this.stand();
}
this._super(vx, vy);
},
walkRight: function() {
if(this.state === size_states.small) {
if(!this.setupFrames(8, 2, true, 'WalkRightSmall'))
this.setImage(images.sprites, 0, 0);
} else {
if(!this.setupFrames(9, 2, true, 'WalkRightBig'))
this.setImage(images.sprites, 0, 243);
}
},
walkLeft: function() {
if(this.state === size_states.small) {
if(!this.setupFrames(8, 2, false, 'WalkLeftSmall'))
this.setImage(images.sprites, 81, 81);
} else {
if(!this.setupFrames(9, 2, false, 'WalkLeftBig'))
this.setImage(images.sprites, 81, 162);
}
},
/* ... */
});
Aquí vamos a sobreescribir el setVelocity ()
método. Dependiendo del estado actual se ejecuta la función correspondiente como walkright ()
o walkleft ()
. La función de continuación, examina el estado actual con el fin de decidir cuál de animación para aplicar. Aquí traemos la opción Identificación
de parámetros en juego.Cambiamos la ubicación spritesheet actual sólo entonces cuando se podría aplicar una nueva animación. De lo contrario la animación actual parece todavía sea válida, resultando en una ubicación spritesheet válido también.
El diagrama de clases
Uno de los propósitos de la reescritura de todo el juego fue el incentivo para describir todo lo que de una manera orientada a objetos. Esto hará que la codificación más interesante, así como más simple. También el juego final contendrá menos errores. El siguiente diagrama de clases estaba previsto antes de crear el juego:
El juego ha sido estructurado para mostrar las relaciones y dependencias. Una de las ventajas de tal estructura es la capacidad de extender el juego. Vamos a investigar el proceso de extensión en la siguiente sección.
La herencia es uno de los factores que la escritura de JavaScript orientado a objetos nos trae. Otra es la capacidad de tener algo así como los tipos (las instancias de las clases). Por lo tanto podemos, por ejemplo, preguntar si un objeto es una instancia de una clase determinada. Vamos a echar un vistazo a el siguiente código de ejemplo:
var Item = Matter.extend({
/* Constructor and methods */
bounce: function() {
this.isBouncing = true;
for(var i = this.level.figures.length; i--; ) {
var fig = this.level.figures[i];
if(fig.y === this.y + 32 && fig.x >= this.x - 16 && fig.x <= this.x + 16) {
if(fig instanceof ItemFigure)
fig.setVelocity(fig.vx, constants.bounce);
else if(fig instanceof Enemy)
fig.die();
}
}
},
})
El fragmento de código muestra una parte del artículo
de clase. Esta clase contiene un nuevo método de rebote ()
, que nos dejó el cuadro de subir y bajar un poco al establecer la isBouncing
propiedad verdadera
. Al igual que en el original juego de Mario que puede matar a los enemigos que están desgraciadamente de pie en el punto de rebote. El caso de otro popular es dar a los casos de ItemFigure
(como los hongos) en un impulso adicional y la dirección (en caso de rebote por encima del elemento).
Ampliar el juego
Una cosa que siempre se pueden incluir son nuevos sprites (imágenes) y los movimientos. Un ejemplo sería un traje adecuado para el Mario en modo de fuego. La demo sólo utiliza las mismas imágenes como el Mario grande. La siguiente imagen muestra el fuego adecuado Mario obtener la victoria:
Hay varios puntos de extensión en el juego en sí. Un punto de extensión obvia es la construcción de una nueva clase y darle un nombre de una adecuada reflexión. Niveles continuación, puede utilizar este nombre, que se traduce en el nivel de la creación de una instancia de esta clase. Empezamos con un ejemplo sencillo de la aplicación de nuevo tipo de decoración: Una izquierda que cuelga de Bush!
var LeftBush = Decoration.extend({
init: function(x, y, level) {
this._super(x, y, level);
this.setImage(images.objects, 178, 928);
},
}, 'bush_left');
Esto fue bastante fácil. Sólo tenemos que heredar de la decoración de
la clase y establecer otra imagen sobre lasetImage ()
método. Desde las decoraciones son no-bloqueo que no puede especificar un nivel de bloqueo aquí (al igual que con las clases que heredan de tierra
). Llamamos a esta decoración nueva clase bush_left
.
Ahora consideremos el caso de extender el juego con un nuevo enemigo: el fantasma (no incluido en el código fuente)! Esto es un poco más difícil, pero no desde el principio. Los problemas que acabamos de venir con las normas que este tipo específico de enemigo tiene que seguir. La construcción básica es sencilla:
var Ghost = Enemy.extend(
init: function(x, y, level) {
this._super(x, y, level);
this.setSize(32, 32);
},
die: function() {
//Do nothing here!
},
});
Así que la primera cosa a notar aquí es que no llame a la _super ()
método de la matriz ()
método. Esto se traduce en el Espíritu
no ser capaz de morir (ya que él o ella ya está muerta). Esto es realmente una de las reglas.Las otras reglas son las siguientes:
- Los movimientos hacia fantasma Mario (una vez que es capaz de ver Mario)
- Si Mario mira directamente al fantasma (dirección de Mario es lo contrario de la dirección del fantasma) el fantasma no se mueve
- Incluso si Mario tiene una estrella o dispara el fantasma no puede morir
Mientras que otras rutinas simplemente abusan de la setVelocity ()
el método sería muy útil para anular lajugada ()
método en este caso. Hay dos razones para ello:
- La gravedad no tiene ningún efecto sobre el fantasma
- El fantasma sólo se mueve si ciertas reglas (ver arriba) se cumplen
Con este conocimiento, ahora podemos incluir al resto del enemigo fantasma, dando como resultado el siguiente código:
var Ghost = Enemy.extend({
init: function(x, y, level) {
this._super(x, y, level);
this.setSize(33, 32);
this.setMode(ghost_mode.sleep, directions.left);
},
die: function() {
//Do nothing here!
},
setMode: function(mode, direction) {
if(this.mode !== mode || this.direction !== direction) {
this.mode = mode;
this.direction = direction;
this.setImage(images.ghost, 33 * (mode + direction - 1), 0);
}
},
getMario: function() {
for(var i = this.level.figures.length; i--; )
if(this.level.figures[i] instanceof Mario)
return this.level.figures[i];
},
move: function() {
var mario = this.getMario();
if(mario && Math.abs(this.x - mario.x) <= 800) {
var dx = Math.sign(mario.x - this.x);
var dy = Math.sign(mario.y - this.y) * 0.5;
var direction = dx ? dx + 2 : this.direction;
var mode = mario.direction === direction ? ghost_mode.awake : ghost_mode.sleep;
this.setMode(mode, direction);
if(mode)
this.setPosition(this.x + dx, this.y + dy);
} else
this.setMode(ghost_mode.sleep, this.direction);
},
hit: function(opponent) {
if(opponent instanceof Mario) {
opponent.hurt(this);
}
},
}, 'ghost');
Aquí todas nuestras reglas se han aplicado. El fantasma, sólo lo hará un movimiento si Mario se encuentra dentro de un cierto rango (800 píxeles, en este caso). Con el fin de trabajar coherente se introduce un objeto de enumeración, llamado ghost_mode
:
var ghost_mode = {
sleep : 0,
awake : 1,
};
También tenemos que introducir algunos nuevos sprites. En este caso hemos añadido una nueva imagen que incluye todos los sprites. La ruta se guarda en images.ghost
y conduce a la siguiente imagen:
Puntos de interés
En este artículo se llama Super Mario para HTML5, sin embargo, no hay realmente mucho HTML5 en esta demostración. Las dos características que son proporcionados por el HTML5 son <canvas>
elemento y el <audio>
elemento. Ambos no se han utilizado para esta demostración. Mientras que el primero de ellos es interesante para el editor de niveles (vamos a echar un vistazo a que en el próximo artículo), este último es, por supuesto, ya interesante para el juego también. Decidí excluir en esta demostración con el fin de mantener el tamaño de la fuente tan pequeña como sea posible.
A pesar de que JavaScript es un lenguaje dinámico que todavía puede utilizar la enumeración bandera como variables.Por ejemplo las variables de bloqueo se definen en una enumeración bandera como forma, por ejemplo:
//e.g. check for top-blocking
function checkTopBlocking(blocking) {
if((ground_blocking.top & blocking) === ground_blocking.top)
return true;
return false;
}
Las variables que contienen valores de ground_blocking
mediante el uso de var bloqueo = ground_blocking.left + ground_blocking.top;
(También se podría lograr mediante una operación de poco, sin embargo, debido a la declaración por escrito en JavaScript que no tendría ningún beneficio) o algo similar puede ser leer con facilidad. El proceso de la lectura de los valores atómicos se puede hacer mediante:
/ / por ejemplo, control de la parte superior de bloqueo de
la función checkTopBlocking (bloqueo) { si ((ground_blocking.top y bloqueo) === ground_blocking.top) devuelva cierto ;
volver falsa ; }
La versión original (incluidos los sonidos y el código) se puede encontrar en https://www.florian-rappl.de/html5/projects/SuperMario/ . Esta versión también estará disponible muy pronto.
Historia
- v1.0.0 | Versión inicial | 01.06.2012.
- v1.1.0 | Se han solucionado algunos errores tipográficos, incluyendo vídeo de YouTube, extendió el código fantasma | 05.06.2012.
Licencia
Este artículo, junto con el código fuente y los archivos asociados, está licenciado bajo Licencia El Proyecto de Código Abierto (CPOL)