Aprende a crear juegos en HTML5 Canvas

domingo, 8 de diciembre de 2013

Aceleración

En los juegos pasados hemos trabajado con movimientos constantes, es decir, que se mueven siempre a la misma velocidad. Hoy aprenderemos a mover objetos a una velocidad variable a través de la aceleración.

Para empezar, aprenderemos a adaptar lo que ya sabemos al método que usaremos para las aceleraciones. Para ello necesitamos tres partes importantes: La posición del personaje (Que ya está dada con su creación), la velocidad que tiene el personaje en cada momento, y la constante de aceleración de la velocidad:
    var K=10;
    
    var player=new Circle(40,40,5);
    var speed=0;
Cuando presionamos la tecla derecha, sumamos su constante. Cuando presionamos la tecla izquierda, se la restamos. La desaceleración, en el caso de un movimiento constante, es que cuando ninguna tecla se presiona, su movimiento simplemente es cero:
        // Move Rect
        speed=0;
        if(pressing[KEY_RIGHT]){
            speed+=K;
        }
        if(pressing[KEY_LEFT]){
            speed-=K;
        }
Posteriormente se suma la velocidad obtenida a la posición del jugador:
        player.x+=speed;
En la función "paint" imprimiremos la velocidad del jugador para conocerla en todo momento:
        ctx.fillText('Speed: '+speed,0,10);
Si pruebas este código, verás que su funcionamiento es exactamente el mismo que hemos usado hasta el día de hoy. Probemos ahora con la aceleración.

Un método muy popular que he encontrado en Internet para hacer un efecto de aceleración, es el de sumar una constante mientras se presiona una tecla, y multiplicarlo por un número decimal para provocar su desaceleración:
        // Move Rect
        if(pressing[KEY_RIGHT]){
            speed+=K;
        }
        if(pressing[KEY_LEFT]){
            speed-=K;
        }
        speed*=0.9;
Este tipo de desaceleración causa que nunca termina de llegar al límite superior de velocidad, y nunca termina realmente de frenar, pero casi lo hace. Al frenar hace un efecto que pareciera como si patinara sobre una superficie resbalosa. He notado que su velocidad máxima y el tiempo que tarda en frenar, se ven afectados tanto por su constante "K", como por el número con el que se multiplica al final la velocidad. Tras varias pruebas, concluí que para obtener una máxima velocidad de 10 pixeles por ciclo y una desaceleración agradable, tuve que poner la constante "K" a 1.1, y multiplicar la velocidad por 0.9.

Otra forma de manejar la aceleración, es sumar la constante cuando la tecla es presionada, y cuando esta deja de presionarse, causar la desaceleración hasta frenar con esa misma constante. Si bien es un poco más complejo de hacer, esta aceleración/desaceleración constante es mas precisa de manejar, así como su límite de velocidad:
        // Move Rect
        if(pressing[KEY_RIGHT]){
            if(speed<10)
                speed+=K;
        }
        else{
            if(speed>0)
                speed-=K;
        }
        if(pressing[KEY_LEFT]){
            if(speed>-10)
                speed-=K;
        }
        else{
            if(speed<0)
                speed+=K;
        }
Verás que el código es un poco más largo, pero preciso. Para que la aceleración no sea muy rápida en este ejemplo, he puesto la constante de aceleración "K" a 0.5 pixeles por ciclo.

Con esto cubrimos los tipos de aceleración y sus respectivas desaceleraciones. ¿Cual de los dos métodos usar? Eso depende completamente de ti, y lo que prefieras para tus juegos. Para ayudarte a decidir mejor, he condensado los tres métodos en un solo ejemplo, con lo que podrás analizar los tres métodos al mismo tiempo. Siéntete libre de modificarles y encontrar la aceleración que mejor se adapte a tu gusto.

Código final:

[Canvas not supported by your browser]
(function(){
    'use strict';
    window.addEventListener('load',init,false);
    var KEY_LEFT=37;
    var KEY_RIGHT=39;
    var K1=10;
    var K2=1.1;
    var K3=0.5;

    var canvas=null,ctx=null;
    var lastPress=null;
    var pressing=[];
    var player1=new Circle(40,40,5);
    var player2=new Circle(40,100,5);
    var player3=new Circle(40,160,5);
    var speed1=0;
    var speed2=0;
    var speed3=0;

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

    function run(){
        setTimeout(run,50);
        act(0.05);
    }

    function repaint(){
        requestAnimationFrame(repaint);
        paint(ctx);
    }

    function act(deltaTime){
        // Move Rect
        speed1=0;
        if(pressing[KEY_RIGHT]){
            speed1+=K1;
            speed2+=K2;
            if(speed3<10)
                speed3+=K3;
        }
        else{
            if(speed3>0)
                speed3-=K3;
        }
        if(pressing[KEY_LEFT]){
            speed1-=K1;
            speed2-=K2;
            if(speed3>-10)
                speed3-=K3;
        }
        else{
            if(speed3<0)
                speed3+=K3;
        }
        speed2*=0.9;
        
        player1.x+=speed1;
        player2.x+=speed2;
        player3.x+=speed3;
        
        // Out Screen
        if(player1.x>canvas.width)
            player1.x=0;
        if(player1.x<0)
            player1.x=canvas.width;
            
        if(player2.x>canvas.width)
            player2.x=0;
        if(player2.x<0)
            player2.x=canvas.width;
            
        if(player3.x>canvas.width)
            player3.x=0;
        if(player3.x<0)
            player3.x=canvas.width;
    }

    function paint(ctx){
        ctx.fillStyle='#000';
        ctx.fillRect(0,0,canvas.width,canvas.height);
        
        ctx.strokeStyle='#0f0';
        player1.stroke(ctx);
        player2.stroke(ctx);
        player3.stroke(ctx);
        
        ctx.fillStyle='#fff';
        //ctx.fillText('Last Press: '+lastPress,0,20);
        ctx.fillText('Speed1: '+speed1,0,10);
        ctx.fillText('Speed2: '+speed2,100,10);
        ctx.fillText('Speed3: '+speed3,0,20);
    }

    document.addEventListener('keydown',function(evt){
        lastPress=evt.keyCode;
        pressing[evt.keyCode]=true;
    },false);

    document.addEventListener('keyup',function(evt){
        pressing[evt.keyCode]=false;
    },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();
    }

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

3 comentarios:

  1. Excelente material! Esto de la aceleración es de lo más útil y necesario en cualquier juego y tú lo has explicado perfecto. Resumido y práctico :D
    Muchas gracias!!!!!!!!

    ResponderEliminar
  2. Hola perdón q moleste queria saber si hay una forma de salvar el juego y a iniciar nuevamente retomar el juego donde lo dejaste?

    ResponderEliminar
    Respuestas
    1. Seguro, revisa el tema Almacenamiento local y altos puntuajes (http://juegos.canvas.ninja/2013/07/almacenamiento-local-y-altos-puntuajes.html). El ejemplo indica como hacerlo para puntajes, pero funciona de igual forma para cualquier dato que desees almacenar.

      Eliminar