CSS3 HTML5

Los nuevos estándares para el diseño y desarrollo web y Móvil

Capacitación HTML5

Mi primer videojuego con HTML5 Canvas y JavaScript

| 13 Comentarios

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.

  1. Manejo medio o incluso avanzado de Javascript, ya que usaremos algunas funciones de este lenguaje y es preferible conocer bien la sintaxis.
  2. Programación Orientada a Objetos (en adelante POO). Plantearemos el ejemplo utilizando pseudo clases.
  3. Javascript es un lenguaje de scripts. No es un lenguaje orientado a objetos, por lo tanto utilizaremos estructuras para simular dicho paradigma.
  4. 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.

 

El archivo Javascript

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;
    };
};

Como  ya comentamos en la introducción, Javascript no es un lenguaje orientado a objetos, 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.

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. Para 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.

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

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á.

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)

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.

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.

 

Conclusiones

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.

 

 

Autor: Tury

Desarrollador apasionado por las tecnologías web, con experiencia en lenguajes (x)HTML, CSS, PHP, Java, Javascript, AJAX y MySQL. También en sistemas de publicación CMS como WordPress y frameworks como Code Igniter. Además se especializa en sistemas GNU/Linux siendo también un difusor del Software Libre. En el ámbito laboral se desempeñó como programador web, webmaster y como administrador de sistemas llevando adelante procesos de migración a Software Libre. Estudia la carrera de Licenciatura en Análisis de Sistemas en la Facultad de Ingeniería de la UBA y se desempeña como programador freelance.

13 Comentarios

  1. Excelente!!! Acabo de entrar a este sitio y me encuentro con algo muy gratificante!!! Desarrollo de Videojuegos. Miré el ejemplo y está muy interesante, te felicito por el avance y te agradezco por colocar el código también. Te cuento que me acabo de recibir de Licenciado en Informática y Desarrollo de Software con una Tesis que justamente se llama “Desarrollo de Videojuegos”, aunque no toqué el tema Web de videojuegos. Por eso me interesa mucho este tema y voy a seguir investigando.

    Muchas gracias nuevamente!

    Saludos desde Mendoza, Argentina!

    Jesús Arce

  2. Muchas gracias por tu comentario, Jesús.

    El desarrollo de videjuegos es un tema realmente apasionante. La incorporación del elemento canvas en HTML5 nos abre un mundo nuevo para los programadores web. Vamos a continuar incluyendo artículos al respecto y esperamos seguir leyendo tus comentarios.

    Saludos
    Alejandro De Luca

  3. Pingback: Aprende a crear juegos con HTML5

  4. Si uso el teclado y mantengo pulsada la tecla la nave se mueve al inicio a trompicones. Es decir se mueve una posicion y luego hay una espera de 1sg para continuar moviendose. Como mejorar la respuesta al teclado para que sea mas fluida?

    • Testeado en varios navegadores no nos ocurre eso. ¿Qué navegador, versión y sistema operativo estás utilizando? (aclaro que para Firefox hay que hacer unos pasos extras que por la extensión de la nota no están comentados)

      • Utilizo el explorer 9. Pero no creo que sea por el explorer sino por la manera de capturar el evento del teclado.

        El efecto a que me refiero es el mismo que cuando empezamos a escribir en el bloc de notas o en word. Prueba a pulsar por ejemplo la tecla a sin soltarla durante un tiempo. Al principio de la pulsacion se escribe una ‘a’ luego hay como una pausa de 1sg antes de que se vuelvan a escribir mas ‘aes’ despues dejamos de pulsar la tecla a.

        Seria asi: a (pausa 1sg) aaaaaaaaaaaaaaaaa

        ¿ Como haces para que el movimiento con el teclado de la nave sea fluido ? Esto tambien pasa si pruebas a hacer lo mismo en vez de con html5+javascript con un juego en Java.

        • Yo lo estoy probando en Internet Explorer (Windows 7) y eso funciona perfecto. ¿Probaste con otro navegador? Yo creo que puede ser tu configuración de teclado y como tenés configurada la repetición de teclas. Fijate este tutorial de Microsoft para configurar el teclado y repetición de teclas: http://windows.microsoft.com/es-ES/windows-vista/Change-keyboard-settings

          • Lo he probado en windows 7 con explorer 9.
            He probado otros juegos en html5 que he visto en la web y el movimiento es fluido.

            Tambien he probado lo que me has dicho de tocar la frecuencia de repeticion configurandola en windows7 y parece que va mejor. Pero no es una buena solucion porque tendria que tocarla cada vez y si hago un juego el usuario no tiene porque ir tocando esto.

            ¿ Puede ser que haya una instrucion en html5 que modifique la repeticion de teclas del navegador ?
            ¿ Alomejor hay un algoritmo u otro metodo que solucione este problema?

  5. Lo he probado en windows 7 con explorer 9.
    He probado otros juegos en html5 que he visto en la web y el movimiento es fluido.

    Tambien he probado lo que me has dicho de tocar la frecuencia de repeticion configurandola en windows7 y parece que va mejor. Pero no es una buena solucion porque tendria que tocarla cada vez y si hago un juego el usuario no tiene porque ir tocando esto.

    ¿ Puede ser que haya una instrucion en html5 que modifique la repeticion de teclas del navegador ?
    ¿ Alomejor hay un algoritmo u otro metodo que solucione este problema?

  6. hola estoy muy interesado en la programacion de video juegos y quisiera saber que lenguajes de programacion debo aprender para desarrollarlos html5+javascript?
    que recomiendas para diseño en 3d y para programacion?

    gracias

  7. hola….estoy encantada con tu pagina..se muy poco de vídeo juegos.. o de código.. estoy viendo una clase este semestre… y debo desarrollar una mini pagina…estoy decidida a hacer un vídeo juego básico.. pero me encuentro con un problema… deseo que cuando se mueva el personaje..(nave) el backaground se mueva…. como en Mario Bross…me puedes indicar como se hacer esto o en donde me puedo reverenciar… pq he buscado mucho y no he encontrado..
    muchas gracias por explicar tan bien tu código 😀

    • Monique

      Para que el fondo de un videojuego se mueva hay que considerar que todos los elementos que lo conforman lo hagan. Generalmente, el personaje principal (nave o personaje de plataformas) avanza solo hasta la mitad de la pantalla y cuando intenta avanzar más, en ese momento comienza a moverse el fondo en la dirección contraria. No sé qué clase de ejemplo tienes que realizar, pero quizás sea conveniente la utilización de una librería como Kinetic JS para poder manejar capas de elementos para poder hacer un fondo que se mueva.

      Espero haberte orientado.
      Saludos.

Deja un comentario

Es requirido que se compelte.*.