Aprende a crear juegos en HTML5 Canvas

lunes, 27 de agosto de 2012

Midiendo el tiempo

Hoy vamos a despegarnos un solo momento del juego que llevamos, para ver una herramienta que nos servirá en muchos de nuestros juegos, presentes y futuros: Como medir el tiempo.

En verdad, esta técnica es muy sencilla. Partiremos del código base visto en la Parte 2: Animando el Canvas de nuestro juego anterior Snake, y borraremos todo lo concerniente al cuadrado, quedando tan solo la base preparada para los ciclos del juego.

Agregaremos una variable counter, que será nuestro contador del tiempo, y una variable lastUpdate, cuyo propósito recordaremos en un momento:
    var counter=0;
    var lastUpdate=0;
Para contar el tiempo que pase en el ciclo de nuestro juego, usaremos la técnica que antiguamente utilizamos para contar los cuadros por segundo en pantalla, con la cual obteníamos la delta de tiempo, es decir, el tiempo que pasa entre ciclo y ciclo, y ese valor lo enviaremos a la función act:
        var now=Date.now();
        var deltaTime=(now-lastUpdate)/1000;
        if(deltaTime>1)deltaTime=0;
        lastUpdate=now;
        
        act(deltaTime);
Ya con esto, en la función act, aumentaremos la delta de tiempo a nuestro contador:
    function act(deltaTime){
        counter+=deltaTime;
    }
Vamos a mostrar esto en nuestra función paint. Aprovechando que este código se enfoca en mostrar el tiempo mediante texto, les mostraré como se hace para centrarlo y mostrarlo en un tamaño más grande:
    function paint(ctx){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.fillStyle='#fff';
        ctx.textAlign='center';
        ctx.font='20px arial';
        ctx.fillText(counter,150,100);
    }
La variable textAlign del contexto, indica hacia dónde se dibujará el texto desde las coordenadas dadas por fillText. Puede ser right (valor por defecto), center o left. Como le indicamos que dibuje el texto en el centro del lienzo (150,100), el texto se dibujará en el centrado en el centro mismo del lienzo.

La siguiente variable es font. Su valor por defecto es '10px arial', y como verán en el ejemplo, yo he indicado que se dibuje al doble del tamaño original. Los valores mínimos que requiere esta variable, es un tamaño de fuente, y la fuente a usar. Pero se pueden agregar antes otros valores, para modificar su aspecto de acuerdo a lo necesario. Ejemplo: "bold italic 16px verdana".

Hay que recalcar con mucha importancia, que para que el usuario vea la fuente selecta, debe él tener instalada dicha fuente, por lo que recomendamos mantenerse dentro de las fuentes del proyecto Core fonts for the Web. También es posible hacer uso de técnicas como @font-face, pero no detallaremos de ello en este curso.

Al probr el código, veremos que el contador funciona, pero lo hace mostrando demasiados decimales, lo que lo hace confuso. Podemos agregar un toFixed para que solo muestre el tiempo con décimas de segundo:
        ctx.fillText(counter.toFixed(1),150,100);
Ahora que ya comprendemos como funciona un contador, veremos como aplicar su lógica a un juego. Para esto, agregaremos dos variables que ya nos son familiares del juego de la serpiente:
    var pause=true,gameover=true;
Aprovecharemos el uso del ratón recién aprendido en la entrada anterior. La lógica será así:
  1. Iniciamos con ambas variables como verdadero.
  2. Al presionar el ratón, se hará un reset al contador.
  3. Al presionar de nuevo el ratón, comenzará un contador de 5 segundos en forma regresiva.
  4. Al llegar a 0, se detendrá todo, y se volverá al punto 1.
Comencemos agregando el escucha del MouseDown, como lo hicimos en la entrada anterior. Hemos decidido que el contador tendrá 5 segundos. Por lo tanto, agregaremos este código en la función act:
        if(lastPress==1){
            if(gameover){
                gameover=false;
                counter=5;
            }
            else
                pause=false;
            lastPress=null;
        }
Esto se encargará de los puntos 2 y 3. Ahora para el punto cuatro, agregaremos a la función act el contador para que lo haga de forma inversa, y dentro de las condicionales correspondientes:
        if(!pause){
            counter-=deltaTime;
            if(counter<=0){
                counter=0;
                gameover=true;
                pause=true;
            }
        }
Nota que dado que el restar la delta del tiempo puede darnos un valor negativo en el contador, asignamos que el contador sea específicamente igual a 0 cuando el tiempo se ha acabado.

Por último, mostraremos cuando el juego esté en pausa, instrucciones para que el usuario de clic con el ratón para iniciar. Recuerda devolver el tamaño de letra al original. Aprovecharemos que las fuentes están centradas también:
        if(pause){
            ctx.font='10px arial';
            if(gameover)
                ctx.fillText('click to reset',150,120);
            else
                ctx.fillText('click to start',150,120);
        }
Con esto, tendremos un contador de 5 segundos, como se muestra en el ejemplo de abajo. Puedes personalizarlo al tiempo que necesites.

El reto de la semana.

¿Cómo va el reto de la semana pasada? ¿Bien? ¿Más o menos? Pues antes de ver como solucionar este reto, agregaremos una nueva dificultad a la programación de este juego. El reto consiste en agregar un temporizador como el visto en este capítulo al juego, de tal forma, que el jugador tenga 15 segundos para destruir tantos objetivos como le sean posible. ¿Podrás con este reto? Toma en cuenta que al lograrlo, tendrás finalmente un segundo juego completo para tu colección de códigos. ¡Mucha suerte!

Código final:

[Canvas not supported by your browser]
(function(){
    'use strict';
    window.addEventListener('load',init,false);
    var canvas=null,ctx=null;
    var lastUpdate=0;
    var pause=true,gameover=true;
    var lastPress=null;
    var counter=0;

    function init(){
        canvas=document.getElementById('canvas');
        ctx=canvas.getContext('2d');
        canvas.width=300;
        canvas.height=200;

        enableInputs();
        run();
    }

    function run(){
        requestAnimationFrame(run);
            
        var now=Date.now();
        var deltaTime=(now-lastUpdate)/1000;
        if(deltaTime>1)deltaTime=0;
        lastUpdate=now;
        
        act(deltaTime);
        paint(ctx);
    }

    function act(deltaTime){
        if(!pause){
            counter-=deltaTime;
            if(counter<=0){
                counter=0;
                gameover=true;
                pause=true;
            }
        }
        else if(lastPress==1){
            if(gameover){
                gameover=false;
                counter=5;
            }
            else
                pause=false;
            lastPress=null;
        }
    }

    function paint(ctx){
        ctx.fillStyle='#000';
        ctx.fillRect(0,0,canvas.width,canvas.height);
        
        ctx.fillStyle='#fff';
        ctx.textAlign='center';
        ctx.font='20px arial';
        ctx.fillText(counter.toFixed(1),150,100);
        if(pause){
            ctx.font='10px arial';
            if(gameover)
                ctx.fillText('click to reset',150,120);
            else
                ctx.fillText('click to start',150,120);
        }
    }

    function enableInputs(){
        canvas.addEventListener('mousedown',function(evt){
            lastPress=evt.which;
        },false);
    }

    window.requestAnimationFrame=(function(){
        return window.requestAnimationFrame || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame || 
            function(callback){window.setTimeout(callback,17);};
    })();
})();

No hay comentarios.:

Publicar un comentario