Tutorial Canvas 2D – Cómo hacer un juego en javascript 5ª parte

Escrito por el 26 diciembre, 2011

Bienvenidos a la quinta parte de mi primer tutorial sobre cómo realizar un pequeño juego en JavaScript, más concretamente el tan viejo como popular Space Invaders. En la primera lección, hablábamos un poco sobre la estructura general que tienen los videojuegos, de forma muy sencilla, de cómo ibamos a diseñar nuestro juego, un par de consejos sobre cómo depurar, y finalmente, la parte más importante, enseñé una forma de adaptar la programación orientada a objetos a javascript. En la segunda parte vimos cómo crear las entidades que necesita el juego mediante herencia de los atributos y métodos en común, hablemos un poco sobre cómo simular el blucle principal del juego y sobre cómo empezar a dibujar el juego en la pantalla. En el tercer tutorial nos centramos en darle animaciones a las entidades para que se movieran por la pantalla, lo que nos introdujo el concepto de tiempo delta. También aprendimos una forma de detectar y utilizar los eventos de teclado, por medio de notificaciones al bucle principal del juego para mover al jugador. En el cuarto tutorial hicimos que el jugador pudiera disparar y destruir enemigos, utilizando un sencillo algoritmo de detección de colisiones. Hoy veremos cómo mejorar el aspecto general del juego, sustituyendo los feos rectángulos de colores por imágenes en movimiento. En concreto, usaré las del Space Invaders original, para que así, además de gastar mi tiempo escribiendo unos tutoriales que nadie lee , pueda también acabar en la cárcel por una denuncia de Atari por violación de copyrigth.

Aquí puedes probar la versión animada del juego, para entender mejor lo que explicaremos a continuación. Y aquí puedes tienes el ZIP con todos los archivos necesarios.

Si eres un vago y no quieres descargar nada, puedes ver el contenido de los archivos javascript aquí:

El almacén de imágenes

Como salta a la vista, el mayor cambio que le hemos hecho al juego es la inclusión de imágenes animadas que sustituyen a las horribles rectángulos de las otras versiones. Así que evidentemente necesitamos meter imágenes de alguna manera en las entidades del juego. En javascript, las imágenes, como cualquier otro elemento del HTML, son objetos; en particular, el objeto Image. Básicamente sólo tenemos que hacer un new Image(), asignándoselo a alguna variable, y especificarle una ruta en su atributo SRC. Pero ¿dónde metemos ese código? Probablemente, lo primero que se te ha ocurrido es cambiarle el nombre a el atributo color de las entidades, llamándolo ahora por ejemplo “imagen”. Y asignarle a él ese objeto Image del que hablamos antes.

Si hiciéramos éso, el juego funcionaría bien, pero estaríamos desperdiciando un montón de memoria (y tiempo de descarga) pues crearíamos y usaríamos varias veces la misma imagen, pero alojada en objetos Image diferentes. No, usaremos un patrón de la programación orientada a objetos llamado Singleton. El patrón Singleton se suele usar para una crear especie de superclase, una clase que no necesita ser instanciada antes de empezar a utilizarse. Uno de sus múltiples usos, es crear una especie de almacén de recursos (en nuestro caso, de archivos de imágenes) que serán accesibles desde todas las clases que componen nuestro programa. Así, si alguna clase necesita usar un recurso, en vez de crearlo ella, se lo pide a la clase almacén. La clase almacén mirará si lo tiene en su lista de recursos creados. Si lo encuentra, le devuelve un puntero con el recurso a la clase que lo necesita. Si no lo encuentra, lo crea, lo mete en su lista, y le manda el puntero a la clase que lo necesita. Así, cuando posteriormente alguien necesite ese mismo recurso, no se requiere volver a crearlo, con el conseguiente ahorro de memoria.

function SpritesClase(){
    this.lista=[];
    this.get=function(ruta){
        var img;
        for (var i=0;i<this.lista.lenght;i++)
        {
            img=this.lista[i];
            if (img.src==ruta)
            {
                return img;
            }
        }
        img=new Image();
        img.src=ruta;
        this.lista.push(img);
        return img;
    };
}
var Sprites=new SpritesClase();

Nuestro almacén de imágenes sera la variable Sprites, que es una instancia de la clase encargada de realizar todo lo que describí en el último párrafo. No es exactamente el patrón singleton, pero funciona igual. Podemos ver que sólo tiene un atributo, lista, donde se acumularán los objetos Imagen que se vayan creando, y su método get, para recuperarlos (o introducirlos si no existen) de la lista.

Las imágenes que vamos a usar en nuestro juego son:


alien1a.jpg

alien1b.jpg
Primer tipo de alien

alien2a.jpg

alien2b.jpg
Segundo tipo de alien

alien3a.jpg

alien3b.jpg
Tercer tipo de alien

disparoa.jpg

disparob.jpg
Disparo

nave.jpg

explosion.jpg
Nave y explosión

Mundo Sprite

Las imágenes tienen su propio nombre el el mundo del desarrolo de juegos, que seguramente ya hayas escuchado antes: Sprites. Un Sprite es básicamente una imagen que se pintará en la pantalla. Técnicamente, Javascript puede dibujar en un canvas un montón de formatos diferentes, como jpg, bmp, tff, gif, png, etc, pero básicamente sólo se usan tres: GIF, porque acepta transparencias y suele tener un peso (bytes) pequeño. Jpg, porque es el formato más comprimido, pero no permite transparencias. Y png, que es un formato comprimido, aunque no tanto como jpg, pero que sí acepta transparencias. Nota: Cuando hablo del formato GIF, hablo de una sola imagen sin animación, no de esos GIF’s animados que abundan en los foros de internet.

El tema de las transparencias es algo a tener en cuenta. Si tus sprites son propensos a dibujarse en el juego unos encima de otros, necesitarás usar un formato que acepte transparencias, y comunicarle de alguna manera a javascript que unas zonas de la imagen son transparentes, es decir, que no deben ser dibujadas. Por ejemplo, GIF y PNG necesitan que al crealos les indiques un color. Ese color es el que se utilizará como transparente. Sin embargo, yo he elegido el formato JPG, porque la única colisión que se va a producir es entre un alien y un disparo. Pero como los aliens van al principio de la lista de entidades se dibujarán primero. Los disparos están en las últimas posiciones de la lista, por lo que al dibujarse, lo harán, si es el caso, encima de los aliens. Asi que no necesito transparencias. Sin embargo, sí que necesito colorear el fondo de las imágenes de negro, para que se “mezclen” con el color negro de fondo y no se aprecie los rectángulos que en realidad forma cada imagen. Si mi fondo en vez de negro, hubiera sido una imagen con muchos detalles, hubiera tenido que recurrir obligatoriamente a algún formato que acepte transparencias. No obstante, cuando un alien choque contra la nave, pasará lo que expliqué con el ejemplo de la moto en la primera lección: En realidad colisionarán las esquinas de la imagen, de color negro, y no las figuras contenidas en la imagen. Pero bueno, es un defecto que puedo pasar por alto, ya que solucionarlo requeriría aquella aproximación por rectángulos que comenté en varias ocasiones, y que no merece la pena programar para un juego tan básico.

Otra característica de los sprites, es que también sirven para generar animaciones. Si muestras varias imágenes de forma consecutiva, con ligeros cambios entre cada una de ellas, darán sensación de animación. Por ejemplo, en la imagen de la izquierda, tenemos los sprites de Mario, a lo largo de sus primeros juegos. Los sprites suelen ir todos en un mismo archivo. Luego, en el código, le tenemos que indicar qué trozito de imagen, de forma rectangular, tenemos que coger para dibujar la entidad en un momento dado. Unas milésimas de segundo después, habrá que dibujar el siguiente sprite, y así se da la sensación de que la imagen está animada. Los sprites se suelen utilizar sobre todo para hacer las animaciones de los personajes (el protagonista y sus enemigos) cuando caminan, saltan, disparan, se agachan, etc. También se usan para los elementos móviles del escenario, como las antorchas que arden, puertas que pueden abrirse, elementos que el personaje puede activar, etc.

Como en nuestro juego sólo hay un par de sprites para algunas de las entidades, formando una animación muy simple, he optado por meterlos en archivos separados. Así no tengo que comprobar con un editor de imágenes en qué coordenadas y longitudes tengo que trocear el archivo para extraerle los diferentes sprites que contiene. Pero sin duda, cuando se tienen un montón de sprites para un sólo personaje, se ha de ensamblar todo en una sola imagen. El motivo principal es que por cada imagen a descargar, el navegador ha de crear y procesar una petición al servidor, y después descargar el archivo. Si lo metemos todo en una sola imagen, sólo tendrá que realizar una petición al servidor, con el ahorro de tiempo que eso supone.

Un detalle importante que he de comentar antes de entrar en materia, es que deberíamos precargar las imágenes a utilizar en el juego, aunque yo no lo he hecho. He preferido ignorar totalmente el tiempo de descarga de las imágenes, así que es posible que el juego empiece a funcionar, y no se vea nada en la pantalla, a causa de que la imagen a dibujar aún no se ha descargado del todo. Lo he hecho así porque he utilizado muy pocas imágenes, y que dichas imágenes son muy ligeras de peso, con lo que deberían descargarse muy rápidamente. Pero cuando tu juego requiera de muchas imágenes, o cuando las imágenes sean pesadas, es necesario cargarlas antes de ejecutar el juego.

Una imagen se empieza a descargar en cuanto le asignas una ruta, es decir, cuando le asignas a un objeto Image su atributo SRC. Uno de los métodos del objeto Image es onload, que se disparará cuando la imagen se haya descargado completamente, aunque no la muestres en ningún sitio. Aprovechando sus onload, llamando a alguna función global que controlara el numero de imágenes descargadas, podrías saber cuándo arrancar el juego. Es posible que en un futuro incierto haga un minitutorial sobre este sencillo recurso.

Cambios en las entidades

Bueno, llegó el momento de empezar a ver los cambios que le hemos tenido que hacer a la entidad. Sólo mostraré los atributos y métodos modificados o nuevos. Empezaremos por supuesto por la clase padre Entidad:

    this.sprites;
    this.spriteActual=0;
    this.tiempoSprite;
    this.tiempoTranscurrido=0;

    this.constructorBase=function(anchoIni, altoIni, spritesIni, tiempo,xIni,yIni,tipoIni){
        /*this.ancho=anchoIni;
        this.alto=altoIni;*/

        this.sprites=spritesIni;
        this.tiempoSprite=tiempo;
        /*this.x=xIni;
        this.y=yIni;
        this.tipo=tipoIni;*/

    };

Necesitamos 3 atributos nuevos. Sprites es una lista con las imágenes que necesitará la entidad para dibujarse. SpriteActual es el índice de esa lista que nos indica cuál es el sprite que se debe dibujar la próxima vez que se llame a su método dibujar. TiempoSprite contiene los milisegundos que tienen que transcurrir para cambiar al siguiente sprite. TiempoTranscurrido servirá para saber cuanto tiempo pasó desde que cambiamos de sprite, para saber cuándo se debe de volver a cambiar.

Su constructor, recibirá nuevos argumentos, para iniciar los nuevos atributos (a excepción del índice, el cual sabemos que comenzará con la primera posición de la lista, es decir, 0, y el del tiempo transcurrido.

    this.moverBase=function(delta){
        this.tiempoTranscurrido+=delta;
        if (this.tiempoTranscurrido>=this.tiempoSprite)
        {
            this.tiempoTranscurrido=0;
            this.spriteActual=(this.spriteActual+1)%this.sprites.length;
        }
       
        /*this.x+=(delta*this.dx)/1000;
        this.y+=(delta*this.dy)/1000;*/

    };

El método moverBase, además de desplazar la entidad por la pantalla, también se encargará de ir cambiando el sprite que se ha de pintar. Para ello usamos el tiempo que iniciamos antes a cero, sumándole el tiempo delta que nos llega como argumento. Cuando el tiempo transcurrido acumulado supere al tiempo de cambio de sprite, incrementamos una unidad al índice del sprite a utilizar, y ponemos el tiempo transcurrido otra vez a cero. Para evitar salirnos de la lista, después de sumarle uno al índice, le hacemos una operación MOD sobre el resultado. Así evitamos acceder luego a una posición de la lista que no existe, ya que la operación MOD hará que el índice se ponga a cero cuando supere el número de posiciones de la lista.

    this.dibujar=function(contexto){
        contexto.drawImage(this.sprites[this.spriteActual],this.x, this.y, this.ancho, this.alto); 
    };

Y por supuesto, hay que modificar su dibujar. Ahora utilizamos la función de javascript para dibujar imágenes en un contexto 2D. Ésta función tiene sobrecarga de parámetros, es decir, según el número de parámetros que le pasemos, pinta de una u otra forma la imagen. Aquí podemos ver todas las opciones que tiene.

Yo he elegido la que más me interesa, que es la que reescala la imagen pintandola completamente en el área especificada en sus parámetros. El primer parámetro es el objeto Image que debe pintar, que estará en la posición de la lista de sprites apuntada por el índice que usamos antes. Los siguientes 4 parámetros, son los mismos que necesitábamos para pintar un rectángulo, pero esta vez lo rellenará con la imagen, adaptándola a su tamaño. Los argumentos, para los despistados, eran las coordenadas X e Y del vértice superior izquierda, el ancho y el alto del rectángulo donde se inscribirá la imagen, deformándola si no se respetan sus proporciones originales.

En el juego final de la lección anterior, los aliens tenían todos el mismo tamaño. Sin embargo, las imágenes que estoy usando ahora para los aliens, tienen tamaños ligeramente diferentes. Para no complicaros la vida he optado por hacer que las imágenes ocupen exactamente el rectángulo azul de la versión antigua, así que alguna de las imágenes se estirará para llenar su superficie de forma casi imperceptible. La alternativa era crear 3 tipos de entidades alien diferentes (o un constructor en la clase hija que cree diferentes tipos de aliens según alguno de los argumentos recibidos), cada una con un ancho y alto acorde a la imagen original. La dificultad radica en que también habría que modificar el método de la clase Juego de iniciarEntidades, en particular, aquél doble bucle donde creábamos y posicionábamos los aliens en la pantalla, complicandolo al tener cada alien un tamaño diferente.

Aunque empecé a programar el código para no tener que tocar la clase Juego ni las clases hijas de Aliens, finalmente llegué a un callejón sin salida (fácil), por lo que tuve que hacer unos pequeños ajustes. Primero veremos los constructores de las clases Nave y Disparo, que no requieren casi explicación:

    //Constructor de la clase EntidadNave
    this.constructor=function(juego,xIni,yIni){
        this.constructorBase(40,25,[Sprites.get("img/space2/nave.jpg")],999999,xIni,yIni,"n");
        /*this.juego=juego;*/
    };



    //Constructor de la clase EntidadDisparo
    this.constructor=function(juego,xIni,yIni){
        this.constructorBase(4,10,[Sprites.get("img/space2/disparoa.jpg"),Sprites.get("img/space2/disparob.jpg")],100,xIni,yIni,"d");
        /*this.juego=juego;
        this.dy=this.velocidadMovimiento;*/

    };

Ahora necesitamos iniciar los nuevos argumentos, sustituyendo a los colores de la última versión de nuestro juego. Como dije, necesitamos enviarle una lista (por eso uso la notación de los corchetes []), usando para cargar el objeto Image el único método de nuestro almacén de imágenes. Como ya expliqué, sólo necesita que le digamos la ruta de la imagen a cargar. Si existe en el almacén, nos la devuelve. Si no existe, la crea, la mete en el almacén, y nos la devuelve. En el caso de la nave, no tiene animación, asi que la lista sólo tiene una posición. Como no tiene que cambiar de sprite nunca, le puse un tiempoSprite muy grande, para evitar que haga los cálculos aquellos para el incremento del índice del sprite a usar. Aunque si finalmente se cumple los 999999 milisegundos, y se ejecuta el cambio, funciona correctamente (el módulo 1 de cualquier valor siempre es cero).

El disparo tiene dos imágenes, asi para que se alternen los sprites rápidamente, le puse un valor bajo de 100 milisegundos.

En la clase EntidadNave no hay nada más que cambiar, pero EntidadDisparo sí necesita de otro cambio fundamental, en su método colisionadoCon.

    this.colosionadoCon=function(otro){
        /*if (this.usado)
        {
            return;
        }*/

       
        if (otro instanceof EntidadAlien)
        {
            /*this.usado=true;
            this.juego.eliminarEntidad(this);*/

            otro.destruir();
            /*this.juego.notificarAlienDestruido();*/
        }
    };

Antes, cuando el disparo chocaba con una entidad Alien, que venía en el argumento otro, le decía al juego que debe eliminar las dos entidades. Pero ahora queremos mostrar una pequeña explosión antes de cargárnosla, asi que no podemos borrarla de momento. En lugar de ello, lo que haremos será ejecutar un método nuevo de la clase EntidadAlien llamado destruir, para que el propio alien se encarge de mostrar su explosión antes de borrarse.

Veámos los cambios de EntidadAlien:

    this.destruido=0;

    this.constructor=function(juego,xIni,yIni,fila){
        switch(fila)
        {
            case 0:
                this.constructorBase(40,25,[Sprites.get("img/space2/alien1a.jpg"),Sprites.get("img/space2/alien1b.jpg")],200,xIni,yIni,"a");
                break;
            case 1:
            case 2:
                this.constructorBase(40,25,[Sprites.get("img/space2/alien2a.jpg"),Sprites.get("img/space2/alien2b.jpg")],250,xIni,yIni,"a");
                break;
            default:
                this.constructorBase(40,25,[Sprites.get("img/space2/alien3a.jpg"),Sprites.get("img/space2/alien3b.jpg")],300,xIni,yIni,"a");
                break;
        }
        /*this.juego=juego;
        this.dx=-this.velocidadMovimiento;*/

    };

Tenemos un atributo nuevo, destruido, que contendrá el número de milisegundos que tiene que transcurrir desde que se muestra el sprite de explosión hasta que se borra definitivamente la entidad. Su constructor recibe un nuevo argumento, fila, que nos servirá para saber qué sprites utilizar según la fila que ocupa el alien. La fila 1 mostrará el alien tipo 1. Las filas 2 y 3 mostrarán el tipo 2, y las filas 4 y 5 mostrarán el tipo 3, como en el juego original. Además, para evitar que todos los aliens cambien de sprite a la vez dándole un aspecto un poco feo al juego, hemos añadido diferentes tiempoSprite según también la fila que ocupan.

    this.destruir=function(){
        this.sprites=[Sprites.get("img/space2/explosion.jpg")];
        this.spriteActual=0;
        this.destruido=50;
    };

    this.mover=function(delta){
        /*if (this.dx<0 && this.x<10)
        {
            this.juego.actualizaLogica();
        }
        if (this.dx>0 && this.x>juego.getAnchuraCanvas()-(this.ancho+10))
        {
            this.juego.actualizaLogica();
        }
        this.moverBase(delta);*/

       
        if (this.destruido>0)
        {
            this.destruido-=delta;
            if (this.destruido<=0)
            {
                this.juego.eliminarEntidad(this);
            }
        }
    };

Cuando el disparo le notifica al alien que debe ser destruído, pone su atributo de tiempo destruido a 50 milisegundos, para que el método mover la destruya una vez transcurrido. Además, sustituye su lista de sprites por una nueva lista con sólo el sprite de explosión, y por supuesto le cambia su índice a la primera posición.

El método mover, comprueba si destruido ha sido iniciado con un valor diferente de cero. Como es una alarma, en vez de incrementar, resta el tiempo delta transcurrido, y cuando llegue a cero, ordena a la clase Juego que destruya esta entidad. Y con ésto, hemos terminado de modificar las entidades.

Antes de seguir, he de señalar un “fallo” aceptable que estoy cometiendo. Cuando un alien es destruído, mientras se muestra el sprite de esplosión sigue detectando colisiones. Si un disparo alcanza la “explosión”, el disparo se eliminará, y se volverá a poner el tiempo destruido a 50 milisegundos, alargando su existencia un poco más. Pero como el tiempo entre disparo y disparo es muy superior al tiempo que tardará el alien en desaparecer, podemos garantizar que jamás un disparo chocará con un alien “explotando”, al menos con los valores de velocidad del disparo y tasa de tiempo entre disparos que le puse por defecto. Pero es algo que debes tener en cuenta cuando te montes tu propio juego.

Una muy primitiva GUI para el juego

GUI significa Graphig User Interface, o lo que es lo mismo, los datos suministrados en la pantalla por medio de imágenes o texto que mantienen informado al jugador de la evolución del juego, como la puntuación que tiene, el número de vidas que le quedan, la madera que ha talado, el nivel que tiene su personaje, el inventario, etc. La GUI se suele dibujar en la pantalla en último lugar, para que quede siempre encima de lo que es el “motor” del juego.

En la versión anterior, utilizamos una GUI muy simple para mostrar el texto de fin de juego, mediante un elemento DIV situado en el HTML de la página, que con javascript escribíamos su contenido y lo mostrábamos u ocultábamos según el caso. Ahora, utilizaremos un sistema básico que lleve la cuenta de la puntuación y del número de vidas del jugador, utilizando las funciones gráficas del canvas para dibujarlos en la pantalla. Además, cuando nos maten, según el número de vidas que nos queden, mostraremos en el DIV diferentes mensajes.

Veámos qué hay que cambiar en la clase Juego:

    this.puntuacion=0;
    this.vidas=2;

Tenemos un par de atributos nuevos, para guardar la puntuación y las vidas que nos quedan. Inicialmente, a cero y dos, respectivamente.

    this.empezarJuego=function(){
        /*this.listaEntidades=[];
        this.listaEntidadesEliminar=[];
        this.iniciarEntidades();
       
        this.izquierdoPulsado=false;
        this.derechoPulsado=false;
        this.espacioPulsado=false;
       
        this.tiempoTranscurrido=new Date().getTime();
        this.esperandoTecla=false;*/

       
        if(this.vidas<=0)
        {
            this.vidas=2;
            this.puntuacion=0;
        }
    }; 


    this.iniciarEntidades=function(){
        /*this.nave=new EntidadNave();
        this.nave.constructor(this,370,550);
        this.listaEntidades.push(this.nave);
       
        this.contadorAliens=0;
        for (var fila=0;fila<5;fila++)
        {
            for (var i=0;i<12;i++)
            {
                var alien=new EntidadAlien();*/

                alien.constructor(this,100+i*50,50+fila*30,fila);
                /*this.listaEntidades.push(alien);
                this.contadorAliens++;
            }
        }*/

    };

El siguiente cambio viene en empezarJuego, el cual se encargaba de resetear la partida cuando el usuario presiona ENTER. Ahora tenemos que comprobar las vidas que nos quedan. Si no queda ninguna, entonces, para empezar de nuevo, ponemos la puntuación y el número de vidas a sus valores de inicio.

En iniciarEntidades, el único cambio es que ahora hay que enviarle el número de la fila donde estamos creando aliens para que su constructor sepa qué sprites utilizar.

    this.notificarMuerte=function(){
        this.vidas--;
        if (this.vidas>0)
        {
            this.panel.innerHTML="No te preocupes, aún quedan naves en el hangar para seguir luchando.<br/>Presiona ENTER para atacarlos.";
        }
        else
        {
            this.panel.innerHTML="La tierra ha sido destruida tras una invasion alienígena por tu culpa.<br/>Presiona ENTER para intentarlo de nuevo.";
        }
        this.esperandoTecla=true;
    };


    this.notificarAlienDestruido=function(){
        /*this.contadorAliens--;*/
        this.puntuacion+=10;
       
        /*if (this.contadorAliens==0)
        {
            this.notificarVictoria();
        } else
        {
            var n=this.listaEntidades.length;
            for (var i=0;i<n;i++)
            {
                if (this.listaEntidades[i] instanceof EntidadAlien)
                {
                    this.listaEntidades[i].setVelocidadHorizontal(this.listaEntidades[i].getVelocidadHorizontal()*1.02);
                }
            }
        }*/

    };

NotificarMuertes comprueba ahora el número de vidas que nos quedan, para mostrar uno u otro mensaje de texto acorde a la situación.

NotificarAlien sólo suma 10 al marcador de la puntuación por cada alien destruido.

    this.loop=function(){
        /*if (this.gameRunning)
        {
            //Calculamos el tiempo delta
            var delta=(new Date().getTime()) - this.tiempoTranscurrido;
            this.tiempoTranscurrido=new Date().getTime();
            this.contexto.clearRect(0,0,this.canvas.width,this.canvas.height);*/

           
            //Mover entidades
            //Dibujamos entidades
            //Detección de las colisiones
            //Eliminamos las entidades que estén en la lista a eliminar
            //Logica de entidades
            //Mostrar panel si Esperandotecla es true
           
            this.contexto.fillStyle="yellow";
            this.contexto.font = "bold 20px monospace";
            this.contexto.fillText(this.puntuacion+" Puntos",20,30);
            this.contexto.fillText(this.vidas+" Vidas",this.canvas.width-110,30);
           
            //Eventos de teclado
        }
    };

En el bucle principal del juego, casi al final del todo, tenemos las funciones que dibujan el GUI. Elegimos un color, por ejemplo el amarillo, y elegimos una fuente para escribir texto. Hay dos formatos: TAMAÑO-FUENTE o DECORACION-TAMAÑO-FUENTE. Decoración es bold o italic, para negrita o cursivas. Tamaño define el tamaño de la fuente, y puede ser en píxeles PX o puntos PT. FUENTE es el nombre de la fuente a utilizar, pero asegurate de escoger algunos de los más típicos, como arial, monospace, sans-serif, etc, o correrás el riesgo de que el navegador donde se ejecute el juego, si no dispone de la fuente, no escriba nada. A continuación escribimos el texto con 3 argumentos: El texto a escribir, y las coordenadas X e Y donde empezar a hacerlo.

Como comenté en el tutorial anterior, este método no partirá la línea si te sales del canvas, ni la alineará, ni nada de nada. Todos esos cálculos tendrás que hacerlos tú, si llegado el caso, necesitas escribir grandes textos en la pantalla. Aquí tienes más funciones para hacer cosas más complicadas.

Y con ésto, ya está todo dicho. Si teneis alguna clase de duda o sugerencia sobre el tutorial, o habeis programado vuestro propio juego, ¡no dudeis en dejar un comentario para hacérmelo saber!
En un futuro tutorial veremos cómo hacer un juego siguiendo el viejo pero útil sistema de Tiles, que tantos buenos ratos nos ha hecho pasar a los amantes de las 2D.

Etiquetas: , , , , , ,

Comentarios (15)

  • Geniales los tutoriales (algo largos quizá), espero ver más de éstos pronto!

  • muchas gracias por los tutoriales, claro que hay mucha gente que los lee pero muy pocos comentan, estan geniales y llevo un rato buscando tuttoriales de juegos con javascript y cnvas y el tuyo esta muy claro, de verdad se te agradece, ya me ayudaste a entrar en la programacion de juegos y de aqui para adelante…

  • Gracias por el tutorial, es uno de los pocos que me han servido sobre este tema. Muy bien explicado ademas junto con los ejemplos.

  • Simplemente genial, uno de los mejores tutoriales que he leido, y explicado de forma muy amena

  • Muy bueno, estoy de acuerdo que es uno de los mejores tutoriales que encontré y espero que sigas con estos tutoriales, gracias.

  • pero videos es lo que necesito para hacer un juego

  • Por si alguien le interesa una versión en Java simple sin gráficos, http://www.foro.lospillaos.es/viewtopic.php?p=27485#27485

  • Muy buenos los tutoriales.

    Ahora, como dato, nadie puede demandar por el uso de Space Invaders por que al autor nunca lo registro (por eso mismo tiene tantos clones XP )

    Gracias

  • Muy bueno el tutorial, realmente es muy didactico, te felicito.
    Ahora tengo una duda, estoy haciendo un juego y necesito asociar un sprite a una entidad, el siguiente tutorial (el del calvo) explica muchas otras cosas que me marean, lo que necesito es que los Alien llamen a una fila de un sprite segun algun parámetro, me puedes tirar un cable?
    tambien me gustaria saber si puedes/quieres formar parte de un desarrollo en donde hay que mostrar la animacion visual de un juego de basket. Bueno, lo dicho y gracias de antemano!

  • hey man muy bn tutorial cuando termine mi rpg (gracias a los q has posteado) pongo el link aca para q lo vean talves a muchos les sirva de algo…
    PDD: YO SI LEO TUS TOTURIALES DE PIES A CABEZA

  • Si que te leemos, una pasada tub blog.
    Felicidades.

    Saludos.

  • Muy bueno, justo lo que estaba buscando. Lo que no entiendo bien es por que la necesidad de usar canvas cuando este juego programado de la misma manera se puede hacer igual con simples divs.
    Saludos y gracias por el tuto.

  • Pintar en un canvas es mucho más rápido que mover elementos del DOM cambiando sus estilos CSS.

  • disculpa si quisiera que los aliens caigan solos desde arriba como aria para hacerlo me gustaria que me ayudaras ya que e visto los otros tutoriales y si les e entendido pero si me cuesta aun.
    espero tu respuesta y disculpa la molestia

  • Gracias hermano (y)

¿Tienes algo que decir?

Gestionado con Wordpress y Stripes Theme Entradas (RSS) | Comentarios (RSS)