En el ejemplo que vamos a presentar a continuación vamos a mover por pantalla una nave espacial mediante el teclado como en un videojuego. Con la teclas w
, a
, s
, d
podremos cambiar la posición y desplazar la nave por un canvas, que es el elemento HTML5 donde desarrollaremos toda la acción.
Podrán ver el ejemplo terminado en este enlace.
Antes de meternos de lleno en el ejemplo debemos aclarar algunos conocimientos previos que son recomendables tener para entender al 100% el código y poder sacarle el mayor provecho.
- Manejo medio o incluso avanzado de Javascript, ya que usaremos algunas funciones de este lenguaje y es preferible conocer bien la sintaxis.
- Programación Orientada a Objetos (en adelante POO). Plantearemos el ejemplo utilizando pseudo clases.
- Javascript es un lenguaje de scripts. No es un lenguaje orientado a objetos, por lo tanto utilizaremos estructuras para simular dicho paradigma.
- Desarrollo de videojuegos. Explicaremos las bases en el ejemplo, pero si alguna vez el lector programó algún videojuego sencillo en cualquier lenguaje, estará más familiarizado.
El Documento HTML5
<!DOCTYPE html> <head> <title>HTML5 - Nave en movimiento</title> <script src="js/Naves.js" type="text/javascript" ></script> <style> canvas { background: #000; } </style> </head> <body> <canvas id="espacio" height="480" width="600" style="border: 1px solid #c3c3c3;"></canvas> </body> </html>
En la sección <head> notarán, además del título, una referencia a un archivo externo llamado Naves.js que será el que tendrá todo el código de Javascript. En el <body> definimos solamente un canvas al que llamamos espacio y le definimos un tamaño de 480 x 600 píxeles y le marcamos un borde. Para que nuestra nave espacial parezca estar flotando en el espacio, pintamos el canvas de negro, esto lo hacemos en la sección <style> del <head>, aunque también lo hubiéramos podido incluir inline en la definición del canvas.
No hay demasiado más para destacar, el HTML resulta corto y sencillo para este videojuego.
El archivo Javascript del videojuego
Ahora, veamos al archivo Naves.js A grandes rasgos, podremos ver al principio un bloque que define constantes y algunas variables globales. Luego aparece window.onload que es la función que se ejecutará automáticamente en cuanto el navegador termine de cargar la página. Más abajo aparecen dos pseudo clases, primero ManejadorDeEventos
y luego Nave
. A continuación aparece la función limpiar()
que utilizaremos para borrar nuestro canvas
y finalmente nos encontramos con otra pseudo clase: Juego
.
Las constantes que utilizaremos serán, entre otras, las dimensiones del canvas, la ruta al directorio donde estará la imagen de la nave y las coordenadas iniciales.
Para facilitar el ejemplo, se maneja el canvas y el contexto como variables globales. Esto significa que estarán visibles y podrán ser usadas desde cualquier parte del código en que estemos situados.
La pseudo clase Nave
Empecemos con la más sencilla de entender y que servirá también para explicar algunas nociones para simular objetos en JavaScript.
var Nave = function () { // atributos this.posx = new Number(POSX_INICIAL); this.posy = new Number(POSY_INICIAL); this.figura = new Image(); this.figura.src = DIR_IMG + "viper.png"; this.dibujar = function() { var figura = this.getFigura(); var x = this.getX(); var y = this.getY(); if (isNaN(x) || isNaN(y)) { x = Math.rint(LARGO/2); y = ALTO + 15; } contexto.drawImage(figura,x,y,60,60); }; this.getX = function() { return this.posx; }; this.getY = function() { return this.posy; }; this.getFigura = function() { return this.figura; }; this.moverArriba = function() { this.posy-=15; }; this.moverAbajo = function() { this.posy+=15; }; this.moverIzquierda = function() { this.posx-=15; }; this.moverDerecha = function() { this.posx+=15; }; };
La explicación del código para la nave
Como ya comentamos en la introducción, JavasSript no es un lenguaje orientado a objetos (hasta ES6 que incorpora Clases), así que es necesario simular esa estructura. En el paradigma de objetos nos basamos en clases que son entidades abstractas. Cuando deseamos materializar una de esas clases lo que hacemos es crear una instancia y así dar vida a un objeto. Pero para que un objeto exista, primero debe estar bien definida su clase. En otras palabras, si queremos tener una nave espacial, entonces debemos tener una clase que defina cómo es. Aquí a las clases las llamaremos pseudo clases.
La pseudo clase para la nave
Con var Nave = function ()
definimos el nombre de la pseudo clase. Al igual que una clase, una pseudo clase tiene dos tipos de componentes: atributos y métodos. Los atributos definen características del objeto y se definen al principio. Los métodos definen acciones que el objeto puede realizar y se escriben debajo de los atributos. Para nuestro ejemplo, los atributos están constituidos por la posición de la nave en el canvas
y los métodos por cada una de las acciones de movimiento que tendrá la nave (arriba, abajo, derecha e izquierda).
Para los que entiendan POO verán que no hay constructor, por eso lo que se ejecutará al instanciar la clase será el bloque previo a los métodos que coincide con los atributos. En este caso, definimos las coordenadas en x y en y de la nave en el canvas. Noten que a cada atributo se le antepone el this. El otro atributo que tenemos es la imagen que representará la nave, que por supuesto está en un archivo externo y en formato PNG.
Los métodos
Más abajo tenemos la definición de los métodos. También comienzan con this. El primer método, dibujar
se encargará de dibujar la nave en pantalla en las coordenadas que corresponden. El if
que encontramos dentro de esta función es para prevenir el caso en que las coordenadas aún no estén definidas. En ese caso, se les asigna un valor por defecto. Con contexto.drawImage(figura,x,y,60,60);
finalmente se dibuja en pantalla la nave. El parámetro figura contiene la ruta a la imagen, x e y son las coordenadas de posición dentro del canvas y los dos 60
que aparecen son el tamaño de la imagen, siempre en píxeles.
Luego, aparecen los métodos getX
y getY
, que son getters al igual que se acostumbran a usar en POO. La idea es que sean parte de la interfaz de la pseudo clase para que ciertos atributos puedan ser accedidos desde afuera de la misma. En este caso, incluso, accedemos a esos atributos desde dentro de la pseudo clase misma, como se puede ver en dibujar()
. getFigura
es otro getter.
A continuación, nos encontramos con las acciones de movimiento de la nave. moverArriba
, moverAbajo
, moverIzquierda
y moverDerecha
cuando son invocados, alteran las coordenadas de la nave.
El Manejador de Eventos para el videojuego
Pasemos ahora a la parte más complicada. La clase que se encarga de controlar lo que ocurre y hará de nexo entre las teclas que presionamos y la nave que se moverá.
A continuación veremos el código JavaScript:
var ManejadorDeEventos = function(nave) { this.nave = nave; this.ultima; this.tecla = function(e) { // se obtiene el evento var evento = e || window.event; this.ultima = evento.keyCode; switch (evento.keyCode) { case 97: nave.moverIzquierda(); break; case 100: nave.moverDerecha(); break; case 115: nave.moverAbajo(); break; case 119: nave.moverArriba(); break; } return 0; }; document.body.onkeypress = this.tecla; };
Empecemos por atrás. Cuando se cargue la página, la última instrucción se ejecutará y se encargará de decirle al navegador que cuando se presione una tecla, se ejecute la función tecla() de esta misma pseudo clase.
El manejador de eventos se encargará de controlar las acciones de movimiento de cualquier nave que instanciemos. Vamos a avanzar un poco hasta la pseudo clase Juego, allí vemos que creamos un objeto nave que llamamos viper y le asignamos un manejador de eventos pasándole la instancia por parámetro en la línea var manejadornave = new ManejadorDeEventos(viper);
Es el parámetro que se corresponde con el que aparece en la definición de ManejadorDeEventos: var ManejadorDeEventos = function(nave)
La pseudo clase y los métodos
Como se ve, esta pseudo clase tiene un único método y será el encargado de asignar la tecla con la acción correspondiente. Es importante entender que cada vez que se presione una tecla se ejecutará el código del métodos this.tecla de la instancia manejadornave que siempre estará asociada a una nave, en este caso, para la nave viper. Esto quiere decir que las teclas que presionemos tendrán influencia únicamente para esta instancia de la pseudo clase Nave, pero debe quedar claro que podríamos tener aún más instancias.
El método this.tecla
captura el evento del navegador. El parámetro e
debe ponerse por defecto, al igual que la línea var evento = e || window.event;
No vamos a profundizar al respecto, es una técnica para capturar eventos en Javascript.
La variable evento pasará a tener información acerca de lo que ocurrió, en este caso, la tecla presionada. Luego, con un switch determinaremos qué tecla se presionó. Los números 97, 100, 115 y 119 se corresponden con los de las teclas a, d, s y w respectivamente. En cada caso, se invocará al método que corresponde de Nave. Recordemos que en this.nave tenemos la instancia que llamamos viper, así que cada vez que invoquemos un método, afectaremos a esa nave en particular.
El bucle de juego
Existe una técnica para la programación de videojuegos. Generalmente, lo que se hace es dibujar por pantalla los objetos, luego leer la entrada del teclado, actualizar los atributos de los objetos (como por ejemplo las coordenadas de posición) limpiar la pantalla y luego redibujar los objetos ahora con sus datos ya actualizados. Repitiendo esta secuencia en forma continua damos vida a un juego. Aquí vamos a utilizar exactamente lo mismo.
function limpiar () { micanvas.width = micanvas.width; } var Juego = function() { var viper = new Nave(); var manejadornave = new ManejadorDeEventos(viper); this.correr = function() { limpiar(); viper.dibujar(); }; var intervalId = setInterval(this.correr, 1000 / JUEGO_FPS); };
Como se puede apreciar al principio del código, la función de carga window.onload = function()
tan solo obtiene el canvas
y su contexto por un lado y por el otro instancia Juego
y le da el control. Es decir que el que se encargará de manejar lo que ocurra será Juego
.
Veamos entonces cómo actúa. Como ya comentamos, al no existir constructor, las instrucciones que aparecen al principio se ejecutan directamente. Es así como se crea una nave nueva que se manejará con la variable viper y un manejador de eventos que se le asignará a esta nave.
Los FPS y el movimiento del videojuego
Para llevar adelante la secuencia o bucle de juego utilizamos la función de Javascript setInterval
, que sirve para ejecutar funciones a intervalos regulares. En este caso, el método correr de la pseudo clase Juego se ejecutará cada 1000 dividido JUEGO_FPS
milisegundos. JUEGO_FPS
es una constante que indica la cantidad de frames por segundo o cuadros por segundo y se utiliza para regular la velocidad con que se muestra todo en pantalla. Es necesario introducir esta variable porque si ejecutáramos el código en una PC muy antigua notaríamos una diferencia de velocidad que podríamos ajustar cambiando el valor de esta constante.
Alguno puede preguntarse por qué se utiliza setInterval
en vez de utilizar un bucle tradicional for
o while
. El caso es que si hacemos eso, el navegador se detiene de forma permanente en ese bloque de código y no puede atender nada más. El resultado sería el cuelgue de la aplicación. Es por eso que simulamos un bucle con setInterval
.
El método correr()
limpia la pantalla haciendo uso de limpiar()
que es una función común y corriente de Javascript, podría equivaler a un método estático de una clase de POO, aunque en este caso, sin estar encapsulada. Para quien no entienda POO, puede tomar esta función tan solo como una auxiliar. Luego se dibuja la nave, invocando al método que corresponde a la clase y al objeto instanciado.
Videojuego con HTML5: conclusión
La estructura de este código puede servir de base para el desarrollo de videjuegos. La introducción del elemento canvas de HTML5 abre un abanico inmenso de posibilidades. Pero está claro que la factibilidad de programar este tipo de aplicaciones estará ligada al dominio que el desarrollador tenga de Javascript y de algunas técnicas de programación complementarias.