Aprende a crear juegos en HTML5 Canvas

lunes, 3 de septiembre de 2012

Dispara al objetivo

¿Conseguiste terminar el reto de la semana pasada? Si tuviste dificultades, aquí tenemos la respuesta. Comencemos con la implementar los 15 segundos al reloj para hacer de nuestro juego, un reto dentro de un tiempo predeterminado.

Primero, agregaremos las tres variables que vimos en la parte pasada, donde aprendimos a hacer un contador:
    var pause=true,gameover=true;
    var counter=0;
Todo lo que va dentro de la función act, lo envolveremos dentro de un if(!pause), para que nuestro juego corra sólo mientras este no se encuentre detenido. Agregraremos también el contador al final, para el tiempo regresivo:
        counter-=deltaTime;
        if(counter<=0){
            gameover=true;
            pause=true;
        }
Por último, se analizará si el botón izquierdo del ratón ha sido presionado, y si el tiempo ha acabado, se reiniciará el contador:
    if(lastPress==1)
        if(gameover){
            gameover=false;
            counter=15;
        }
        else{
            pause=false;
        }
    }
Al probar el juego, este funcionará perfectamente de acuerdo a lo deseado. Sin embargo, descubriremos un pequeño problema que puede resultar incómodo, pues al acaba el juego, si se vuelve a presionar un clic del ratón, el juego vuelve a comenzar inmediatamente, sin dejarnos ver el resultado. Arreglaremos esto mediante un pequeño truco, el cual permitirá un margen de un segundo antes de poder volver a iniciar un nuevo juego.

Comenzamos por arreglar la función act, dejando afuera del if(!pause) el movimiento del mouse, y el contador regresivo, quedando adentro lo siguiente:
        counter-=deltaTime;
        if(!pause){
            if(lastPress==1){
                bgColor='#333';
                if(player.distance(target)<0){
                    score++;
                    target.x=random(canvas.width/10-1)*10+target.radius;
                    target.y=random(canvas.height/10-1)*10+target.radius;
                }
            }
            else
                bgColor='#000';

            if(counter<1){
                pause=true;
            }
        }
    }
Y modificaremos también ligeramente el reset del contador, para que no se active antes de -1 (el segundo extra de margen):
        else if(lastPress==1&&counter<-1){
            pause=false;
            counter=15;
            score=0;
        }
Por último, agregaremos la información a la función paint:
        ctx.fillStyle='#fff';
        //ctx.fillText('Distance: '+player.distance(target).toFixed(1),0,10);
        ctx.fillText('Score: '+score,0,10);
        if(counter>0)
            ctx.fillText('Time: '+counter.toFixed(1),250,10);
        else
            ctx.fillText('Time: 0.0',250,10);
        if(pause){
            ctx.fillText('Score: '+score,120,100);
            if(counter<-1)
                ctx.fillText('CLICK TO START',100,120);
        }
Notaremos que el mensaje "CLICK TO START" se mostrará solo cuando el segundo de margen haya pasado. También, mostraremos el tiempo solo cuando sea mayor de 0, para que no muestre números negativos después de bajar de 0.

Ahora que ya está arreglado nuestro juego, agregaremos las imágenes. Primero declaramos las variables:
    var iSight=new Image();
    var iTarget=new Image();
    iSight.src='assets/sight.png';
    iTarget.src='assets/target.png';
Y posteriormente, las dibujamos en lugar de los círculos actuales:
        ctx.strokeStyle='#f00';
        target.drawImage(ctx,iTarget);
        ctx.strokeStyle='#0f0';
        player.drawImage(ctx,iSight);
La función drawImage de nuestra función círculo, quedaría de la siguiente forma:
    Circle.prototype.drawImage=function(ctx,img){
        if(img.width)
            ctx.drawImage(img,this.x-this.radius,this.y-this.radius);
        else
            this.stroke(ctx);
    }
Noten que hay que restar el radio de los círculos a la posición, para que el centro de la imagen quede en el centro del círculo. Con esto, haremos concluido nuestro juego "Dispara al objetivo". Si tienen más dudas, tengan seguridad que responderé sus comentarios.
¡Felices códigos!

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;
    var lastPress=null;
    var mousex=0,mousey=0;
    var score=0,counter=0;
    var bgColor='#000';
    var player=new Circle(0,0,5);
    var target=new Circle(100,100,10);
    var iSight=new Image();
    iSight.src='assets/sight.png';
    var iTarget=new Image();
    iTarget.src='assets/target.png';

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

        enableInputs();
        run();
    }

    function random(max){
        return ~~(Math.random()*max);
    }

    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){
        player.x=mousex;
        player.y=mousey;

        if(player.x<0)
            player.x=0;
        if(player.x>canvas.width)
            player.x=canvas.width;
        if(player.y<0)
            player.y=0;
        if(player.y>canvas.height)
            player.y=canvas.height;

        counter-=deltaTime;
        if(!pause){
            if(lastPress==1){
                bgColor='#333';
                if(player.distance(target)<0){
                    score++;
                    target.x=random(canvas.width/10-1)*10+target.radius;
                    target.y=random(canvas.height/10-1)*10+target.radius;
                }
            }
            else
                bgColor='#000';

            if(counter<=0){
                pause=true;
            }
        }
        else if(lastPress==1&&counter<-1){
            pause=false;
            counter=15;
            score=0;
        }
        lastPress=null;
    }

    function paint(ctx){
        ctx.fillStyle=bgColor;
        ctx.fillRect(0,0,canvas.width,canvas.height);
        
        ctx.strokeStyle='#f00';
        //target.stroke(ctx);
        target.drawImage(ctx,iTarget);
        ctx.strokeStyle='#0f0';
        //player.stroke(ctx);
        player.drawImage(ctx,iSight);

        ctx.fillStyle='#fff';
        //ctx.fillText('Distance: '+player.distance(target).toFixed(1),0,10);
        ctx.fillText('Score: '+score,0,10);
        if(counter>0)
            ctx.fillText('Time: '+counter.toFixed(1),250,10);
        else
            ctx.fillText('Time: 0.0',250,10);
        if(pause){
            ctx.fillText('Score: '+score,120,100);
            if(counter<-1)
                ctx.fillText('CLICK TO START',100,120);
        }
    }

    function enableInputs(){
        document.addEventListener('mousemove',function(evt){
            mousex=evt.pageX-canvas.offsetLeft;
            mousey=evt.pageY-canvas.offsetTop;
        },false);
        canvas.addEventListener('mousedown',function(evt){
            lastPress=evt.which;
        },false);
    }

    function Circle(x,y,radius){
        this.x=(x==null)?0:x;
        this.y=(y==null)?0:y;
        this.radius=(radius==null)?0:radius;
    }

    Circle.prototype.distance=function(circle){
        if(circle!=null){
            var dx=this.x-circle.x;
            var dy=this.y-circle.y;
            return (Math.sqrt(dx*dx+dy*dy)-(this.radius+circle.radius));
        }
    }
    
    Circle.prototype.stroke=function(ctx){
        ctx.beginPath();
        ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,true);
        ctx.stroke();
    }
    
    Circle.prototype.drawImage=function(ctx,img){
        if(img.width)
            ctx.drawImage(img,this.x-this.radius,this.y-this.radius);
        else
            this.stroke(ctx);
    }

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

9 comentarios:

  1. Por desgracia en mi trabajo es pura programacion para mercadeo, me encanta crear juegos, y al ver lo que crean me emociono y me dan ganas de enviar el trabajo al carajo, excelente!!!

    ResponderEliminar
    Respuestas
    1. Siempre puedes crear juegos por hobbie, y con algo de suerte, ¡Incluso sacar algo de provecho de ellos! Esto claro, si aun te quedan ganas de programar :P...

      Si te das la oportunidad, nunca sabes hasta dónde puedes llegar ;)

      Eliminar
    2. Concuerdo 100% contigo rah sun, realmente trabajar programando para mercadeo o creando sistemas de gestión hace sentir que estamos siendo desperdiciados. Hace 3 meses no tengo trabajo (solo un proyecto como freelance consultant) y me he dedicado a investigar y aprender las cosas que siempre quice, no saben lo felíz que soy, estoy emocionado al ver que mis neuronas pueden llegar a tanto, como tu dices, al carajo con el trabajo!!

      Eliminar
  2. Hola gracias por los cursos muy bien explicado..

    ResponderEliminar
  3. HOLA NO SE SI AUN ME PUEDAN RESPONDER , pero tengo una duda, cómo podria hacerle para que tubiera balas, osea que tenga un limite de disparos, tanto a los que le doy en el blanco, como a los que no le doy, espero me puedan ayuda aun

    ResponderEliminar
  4. HOLA NO SE SI AUN ME PUEDAN RESPONDER , pero tengo una duda, cómo podria hacerle para que tubiera balas, osea que tenga un limite de disparos, tanto a los que le doy en el blanco, como a los que no le doy, espero me puedan ayuda aun

    ResponderEliminar
    Respuestas
    1. Agrega una variable que cuente la cantidad de municiones que tienes. Cada que disparas una, la restas en uno, y si esta es menor a 1, entonces no ejecutas la acción de disparo.

      Si vas a querer agregar un ítem para recargar municiones, sólo debes agregar a esta variable la cantidad extra de municiones adquiridas.

      Eliminar
  5. e men me puedes pasar este ejemplo a un alert que diga has clickeado este circulo?

    ResponderEliminar
    Respuestas
    1. Sigue estos pasos para detectar cuando presionas en un círculo: http://juegos.canvas.ninja/2012/08/presionando-el-boton-del-raton.html

      Tan sólo debes cambiar la lógica para que muestre el mensaje en su lugar, a la forma en que deseas mostrarlo.

      Eliminar