Aprende a crear juegos en HTML5 Canvas

lunes, 30 de enero de 2012

Parte 3. Usando el teclado.

Nuestro rectángulo ya se mueve por el lienzo, pero para verdaderamente interactuar con él, necesitamos indicarle a dónde queremos que vaya. Para eso, necesitamos primero una variable dónde guardar la tecla presionada:
var lastPress = null;
Y agregar al final de nuestro código un escucha del teclado que almacene la tecla presionada:
document.addEventListener('keydown', function (evt) {
    lastPress = evt.which;
}, false);
Mediante este método, podremos tomar decisiones en el juego sabiendo la última tecla presionada. Cada tecla tiene un valor numérico, el cual tendremos que comparar para realizar la acción deseada dependiendo la tecla presionada. Una buena forma de saber cuál ha sido la última tecla presionada, sería agregando esta línea en nuestra función “paint”:
    ctx.fillText('Last Press: ' + lastPress, 0, 20);
Por ahora no debes preocuparte de eso. Usaremos las teclas izquierda, arriba, derecha y abajo, cuyos valores numéricos son 37, 38, 39 y 40 respectivamente. Para usarlas más facilmente, las guardaremos en valores constantes:
var KEY_LEFT = 37,
    KEY_UP = 38,
    KEY_RIGHT = 39,
    KEY_DOWN = 40;
A diferencia de otros lenguajes de programación, JavaScript no tiene constantes, pero las variables se encargarán del trabajo sin problema.

Vayamos ahora al movimiento del rectángulo. Primero, necesitaremos una nueva variable que almacene la dirección de nuestro rectángulo:
var dir = 0;
Esta variable “dir” tendrá un valor del 0 al 3, siendo 0 hacia arriba, y rotando en dirección de las manecillas del reloj para demás valores cada cuarto de hora.

Ahora utilizaremos un método simple para tener un tiempo consistente entre dispositivos (Puedes leer más en el Apéndice 2: Tiempo consistente entre dispositivos). La forma más fácil para hacer esto por ahora, es dividir las funciones act y paint en dos llamadas distintas, una optimizada para el re-pintar, y una regulada para las acciones:
function repaint() {
    window.requestAnimationFrame(repaint);
    paint(ctx);
}

function run() {
    setTimeout(run, 50);
    act();
}
Como puedes ver dentro de la función "run", llamamos a un temporizador "setTimeout", que llama a la función "run" de nuevo cada 50 milisegundos. Esta es una forma simple de tener el juego a 20 ciclos por segundo.

Nota que, dado que este hace el ciclo de la función "act" asíncrono de la función "repaint", no puedes confiar que lo que realices en el primero, será dibujado en el segundo; pero esto no suele ser un problema cuando el re-pintado es más rápido que el ciclo de acciones. ¡No olvides llamar a ambas funciones en la función init!

Ahora comencemos por detectar la dirección que tomará nuestro rectángulo dependiendo la última tecla presionada, dentro de la función "act":
    // Change Direction
    if (lastPress == KEY_UP) {
        dir = 0;
    }
    if (lastPress == KEY_RIGHT) {
        dir = 1;
    }
    if (lastPress == KEY_DOWN) {
        dir = 2;
    }
    if (lastPress == KEY_LEFT) {
        dir = 3;
    }
Después, moveremos nuestro rectángulo dependiendo la dirección que se haya tomado:
    // Move Rect
    if (dir == 0) {
        y -= 10;
    }
    if (dir == 1) {
        x += 10;
    }
    if (dir == 2) {
        y += 10;
    }
    if (dir == 3) {
        x -= 10;
    }
Por último, buscaremos si el rectángulo ha salido de la pantalla, y en dado caso, lo regresaremos a la misma:
    // Out Screen
    if (x > canvas.width) {
        x = 0;
    }
    if (y > canvas.height) {
        y = 0;
    }
    if (x < 0) {
        x = canvas.width;
    }
    if (y < 0) {
        y = canvas.height;
    }
Te habrás dado cuenta que antes de cada bloque de código, agregué una referencia precedida de dos diagonales (//). Esto es un comentario, y son bastante funcionales para describir qué ocurre en cada sección del código, así, si es necesario modificarlo después, podremos identificar con facilidad sus componentes.

Los comentarios también sirven para “eliminar” líneas de código, pero que podríamos querer utilizar después, por ejemplo, la que dibuja en pantalla cuál fue la última tecla presionada (No querremos que la gente que juegue nuestro juego la vea, ¿O si?).

Guarda el juego y abre “index.html”. Si todo está de forma correcta, ahora podrás controlar al pequeño rectángulo usando las flechas del teclado. ¡Felicidades!

Pausa


Podría dar por concluida la lección de hoy, pero aprovechando que estamos viendo como usar el teclado, te contaré un pequeño truco que se usa para poner “Pausa” a un juego. Comenzaremos por supuesto, creando una variable que indicará si el juego está en pausa:
var pause = true;
Ahora, encerraremos todo el contenido de nuestra función “act” en una condicional “if (!pause)”, o sea, si el juego no está en pausa. Hasta el momento hemos hecho condicionales de una sola línea, por lo que no hemos usado llaves. Pero en caso de ser utilizada más de una línea en la condicional, se deben usar llaves igual que en las funciones, tal como hacemos en el caso actual.

Al final de la condicional “if (!pause)”, agregaremos estas líneas para que al presionar “KEY_ENTER” (13), se cambie el juego entre pausado y sin pausa:
    // Pause/Unpause
    if (lastPress == KEY_ENTER) {
        pause = !pause;
        lastPress = null;
    }
Es muy importante que estas líneas no las encierres dentro del “if (!pause)”, o de lo contrario, jamás podrás quitar la pausa (Por que jamás entrarás a esa parte del código). La asignación “pause = !pause” indica que cambio su valor por el opuesto (falso si es verdadero o verdadero si es falso), y después nulificamos “lastPress”, o de lo contrario, el juego estaría poniendo y quitando pausa sin fin hasta que se presione otra tecla.

Por último, dibujaremos en nuestra función “paint” el texto “PAUSE” de forma centrada, si la pausa está activada:
    // Draw pause
    if (pause) {
        ctx.textAlign = 'center';
        ctx.fillText('PAUSE', 150, 75);
        ctx.textAlign = 'left';
    }
Actualicemos el juego. Ahora cada vez que presionemos la tecla Enter, el juego entrará o saldrá de la pausa.

Actualización: Revisen el comentario dejado por Miguel Ángel, quien da algunos tips y buenas prácticas para mejorar este código y futuros. ¡Gracias! :)

Código final:

[Canvas not supported by your browser]
var KEY_ENTER = 13,
    KEY_LEFT = 37,
    KEY_UP = 38,
    KEY_RIGHT = 39,
    KEY_DOWN = 40,
    
    canvas = null,
    ctx = null,
    lastPress = null,
    pause = true,
    x = 50,
    y = 50,
    dir = 0;

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

document.addEventListener('keydown', function (evt) {
    lastPress = evt.which;
}, false);

function paint(ctx) {
    // Clean canvas
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // Draw square
    ctx.fillStyle = '#0f0';
    ctx.fillRect(x, y, 10, 10);

    // Debug last key pressed
    ctx.fillStyle = '#fff';
    //ctx.fillText('Last Press: ' + lastPress, 0, 20);
    
    // Draw pause
    if (pause) {
        ctx.textAlign = 'center';
        ctx.fillText('PAUSE', 150, 75);
        ctx.textAlign = 'left';
    }
}

function act() {
    if (!pause) {
        // Change Direction
        if (lastPress == KEY_UP) {
            dir = 0;
        }
        if (lastPress == KEY_RIGHT) {
            dir = 1;
        }
        if (lastPress == KEY_DOWN) {
            dir = 2;
        }
        if (lastPress == KEY_LEFT) {
            dir = 3;
        }

        // Move Rect
        if (dir == 0) {
            y -= 10;
        }
        if (dir == 1) {
            x += 10;
        }
        if (dir == 2) {
            y += 10;
        }
        if (dir == 3) {
            x -= 10;
        }

        // Out Screen
        if (x > canvas.width) {
            x = 0;
        }
        if (y > canvas.height) {
            y = 0;
        }
        if (x < 0) {
            x = canvas.width;
        }
        if (y < 0) {
            y = canvas.height;
        }
    }
    
    // Pause/Unpause
    if (lastPress == KEY_ENTER) {
        pause = !pause;
        lastPress = null;
    }
}

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

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

function init() {
    // Get canvas and context
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    
    // Start game
    run();
    repaint();
}

window.addEventListener('load', init, false);

65 comentarios:

  1. Muy buenas, me está encantando tu forma de explicar las cosas. Mira he estado implementando tu código y a la vez aumentando la funcionalidad o corrigiendo cosas, por ejemplo en las lineas
    if(x<0)
    x=canvas.width;
    if(y<0)
    y=canvas.height;

    Creo que hay que asignarle canvas.width - 10 y en la segunda canvas.height-10.

    De esta forma no se perderá el cuadrado fuera del lienzo.

    Yo personalmente he optado por sustituir ese 10 por una variable llamada desplazamiento en la cabecera del script. De esa forma podemos cuadrados del tamaño de esa variable.

    Espero te sirva de ayuda ;)

    Y gracias por el tutorial que está fantástico :D

    ResponderEliminar
    Respuestas
    1. ¡Excelentes recomendaciones! Yo personalmente ya uso esos consejos, pero cuando hice el tutorial, quise dejar las cosas simples, y permitir que la gente se percatara de esos detalles como tú y los corrigiera como mejor creyeran :)

      Sin embargo, editaré la entrada para redirigir a los usuarios y vean tu comentario, así sepan como mejorar y detallar este código.

      Gracias por los consejos ;)

      Eliminar
    2. Se ve mas fluido, pero ocurre que se te puede quedar el 'cuadrito' en ese (-10) y desaparecer del canvas

      Eliminar
    3. Este comentario ha sido eliminado por el autor.

      Eliminar
    4. Mejor que eso sólo servirá poner un menor igual o mayor igual, debido que al ponerle -10 estas indicando que tu bloque en movimiento es de 10, pero si decides aumentarlo a 20 o más, tendrás que modificar nuevamente esa parte del código. Para que sea más "limpio", intentar con:

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

      Gracias por todo Karl :D

      Eliminar
  2. Ey muy buena pagina es perfectamente la que andaba buscando, soy novato en esto de la programacion y estoy tratamdo de hacer un juego simple como in block breaker creo. Que esta patina's me ayudara mmucho. Dios te bendiga.

    ResponderEliminar
    Respuestas
    1. ¡Muchas gracias y mucha suerte!

      Si necesitas ayuda, estamos aquí con gusto para ayudarte ;)

      Eliminar
    2. ey amigo soy yo otra vez el que comento el 24 de diciembre 2012 18:06
      mira este codigo: y si puedes dime porque se ve lento http://breakingbricks.site90.net/

      Eliminar
  3. el perdon ya se por que se ve lento no te preocupes

    ResponderEliminar
  4. una pregunta si solo quisiera q avanzara cuando muevo con flechas como podria hacerlo

    ResponderEliminar
    Respuestas
    1. Eso se ve en un tema más adelante: http://juegoscanvas.blogspot.com/2012/04/mover-mientras-se-presiona-tecla.html

      Eliminar
  5. Estoy siguiendo tus tutos y son geniales compañero, fáciles y rápidos, no se como lo haces para explicarlo tan claro, eres de los pocos que consigue transmitir los conocimientos sin que me aburra al leer.

    Mira que llevaba tiempo queriendo hacer un juego para el navegador para poder llegar a más gente, mis antiguos proyectos eran en GDI+ pero Canvas da muchísima más facilidad de desarrollo.

    Pues nada, a seguir avanzando con el siguiente tuto hasta acabarlos todos ^^

    Gracias Karl y enhorabuena por tus tutoriales. Saludos compañero! :D

    ResponderEliminar
    Respuestas
    1. ¡Muchas gracias por tu comentario!

      Al momento de crear este blog, mi propósito fue desde el comienzo, asegurar el poder transmitir este conocimiento de forma sencilla y completa, sin detallar quizá en la siempre importante teoría, pero transmitiendo también lo importante de ella.

      Saber que estoy logrando mi propósito a través de comentarios como el tuyo, son la alegría que me permiten continuar con emoción el desarrollo de este sitio. Espero siga siendo de utilidad para ti.

      ¡Muchas gracias!

      Eliminar
    2. Todo lo contrario, yo soy quién te está agradecido!!

      No conozco mucha gente que se esfuerce en compartir sus conocimientos y gracias a ti voy a estar este verano entretenido con Canvas, al menos estos 2 próximos meses de calor.

      Bueno pues como buen user que soy te animo a seguir, así que intenta no dejarlo, jaja aunque es imposible ya que a todos nos ha pasado con alguna web que con el tiempo nos cansamos de publicar siempre lo mismo...aunque con todo el contenido de Canvas que hay en el blog ya no creo que haya mucho más que ver.

      En fin que me enrollo jaja, pues ya te iré comentando por otras entradas tuyas a medida que vaya mejorando mi futuro juego con tus tutos.

      Saludos socio! ^^

      Eliminar
  6. Estudie el código 20 veces repitiendo y lo logre entender.

    ResponderEliminar
  7. gracias!!!!!!!!!!!!!!!!!!!! totales

    ResponderEliminar
  8. Una pregunta, si en lugar de apritar enter para pausar deseo que con un button onclick ¿como modifico el archivo js?

    ResponderEliminar
    Respuestas
    1. Eso se ve más adelante, en el tema "Presionando el botón del ratón" ( http://juegoscanvas.blogspot.com/2012/08/presionando-el-boton-del-raton.html )

      Eliminar
  9. Hola! Quería compartir una modificación que le hice al mostrar el LastPress, para que muestre que tecla en concreto estamos tocando y no el numero soso:

    Definí la función (comente con el condicional if por si alguno no conoce el switch):

    function transform(x) {
    /*if (x===37) {
    return "KEY_LEFT"
    } else if (x===38) {
    return "KEY_UP"
    } else if (x===39) {
    return "KEY_RIGHT"
    } else if (x===40) {
    return "KEY_DOWN"
    }
    else {
    return "PRESS A KEY"
    }*/

    switch (x) {
    case 37 : return "KEY_LEFT";
    break;
    case 38 : return "KEY_UP";
    break;
    case 39 : return "KEY_RIGHT";
    break;
    case 40 : return "KEY_DOWN";
    break;
    default: return "PRESS A KEY";
    }
    }

    Y en paint:

    ctx.fillText('Last Press: '+transform(lastPress),0,20);

    Saludos!

    ResponderEliminar
    Respuestas
    1. Gracias por tu sugerencia. El proposito de imprimir lastKey es para averiguar en código de cada tecla en caso de querer usarla luego (Teclas como Control, Shift, ASDW, entre otras populares en algunos juegos).

      Pero dado el caso de querer imprimir las teclas usadas, creo que tu técnica es de bastante ayuda.

      Eliminar
    2. No, es verdad. No consideré el propósito original y por eso, ahora lo veo, no tiene sentido el switch jaja

      Eliminar
    3. Sin embargo, para el propósito de mostrar la tecla presionada, la función es bastante funcional. Seguro a alguien le servirá :)

      Eliminar
  10. Karl tengo una duda..cuando es un cuadrado y solo hay una coordenada se aplica de esta forma pero si por ejemplo tengo una figura mas compleja con multiples coordenadas como lo mueves...es decir ¿hay una forma de "agrupar" la figura? he creado una figura vector con AI y mediante un plugin me lo ha exportado a canvas. pero no puedo imaginarme como moverlo .... te dejo la figura que tengo. es un ovni.
    // capa1/Forma compuesta/Trazado compuesto
    ctx.beginPath();

    // capa1/Forma compuesta/Trazado compuesto/Trazado
    ctx.moveTo(91.1, 8.8);
    ctx.bezierCurveTo(88.8, 6.5, 86.1, 4.4, 82.7, 3.3);
    ctx.bezierCurveTo(72.3, -0.3, 62.4, -0.9, 51.6, 1.4);
    ctx.bezierCurveTo(48.0, 2.1, 45.1, 3.9, 42.4, 6.1);
    ctx.bezierCurveTo(64.6, 2.4, 81.5, 5.7, 91.1, 8.8);
    ctx.strokeStyle = 'red';
    ctx.stroke();

    ctx.closePath();
    ctx.lineWidth = 0.2;
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.strokeStyle = 'black';
    ctx.stroke();
    // capa1/Forma compuesta/Trazado compuesto/Trazado
    ctx.moveTo(130.8, 32.1);
    ctx.bezierCurveTo(128.5, 28.9, 117.7, 25.6, 115.9, 25.0);
    ctx.bezierCurveTo(109.5, 23.1, 102.8, 21.2, 98.0, 16.3);
    ctx.bezierCurveTo(96.7, 14.9, 95.4, 13.4, 94.1, 12.0);
    ctx.bezierCurveTo(85.3, 8.6, 66.1, 3.4, 39.3, 8.7);
    ctx.bezierCurveTo(37.8, 10.0, 36.4, 11.3, 34.9, 12.5);
    ctx.bezierCurveTo(29.5, 16.7, 22.6, 17.8, 16.1, 19.0);
    ctx.bezierCurveTo(14.3, 19.3, 3.3, 21.3, 0.5, 24.2);
    ctx.bezierCurveTo(15.6, 21.1, 76.6, 11.1, 130.8, 32.1);
    ctx.closePath();
    ctx.closePath();
    ctx.lineWidth = 0.2;
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.strokeStyle = 'black';
    ctx.stroke();

    // capa1/Forma compuesta/Trazado compuesto/Trazado
    ctx.moveTo(0.0, 26.3);
    ctx.bezierCurveTo(0.1, 26.4, 0.2, 26.6, 0.3, 26.7);
    ctx.bezierCurveTo(1.8, 28.5, 7.0, 28.7, 9.2, 29.1);
    ctx.bezierCurveTo(28.1, 32.7, 44.9, 44.0, 64.3, 45.4);
    ctx.bezierCurveTo(64.5, 45.4, 64.6, 45.4, 64.7, 45.4);
    ctx.bezierCurveTo(64.7, 45.5, 64.6, 45.5, 64.6, 45.5);
    ctx.bezierCurveTo(84.1, 46.5, 102.3, 37.2, 121.6, 35.8);
    ctx.bezierCurveTo(123.7, 35.7, 129.0, 36.2, 130.7, 34.6);
    ctx.bezierCurveTo(130.8, 34.5, 130.8, 34.4, 130.9, 34.3);
    ctx.bezierCurveTo(75.9, 12.6, 13.3, 23.6, 0.0, 26.3);
    ctx.closePath();
    ctx.closePath();
    ctx.lineWidth = 0.2;
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.strokeStyle = 'black';
    ctx.stroke();

    ResponderEliminar
    Respuestas
    1. ¡Vaya! Una imagen vectorial... Esto definitivamente sería muy difícil de agregar una variable a cada coordenada...

      Supongo que la solución más sencilla seria usar ctx.translate, el cual vemos mas adelante en el tema de rotación de imágenes. Si no me equivoco, sería de la siguiente forma:

      ctx.save();
      ctx.translate(x,y);
      // Incluye aquí el dibujado vectorial
      ctx.restore();

      Sería quizá mas sencillo si pones el dibujado de cada imagen vectorial en una función propia. Espero esto resuelva tu problema. ¡Suerte!

      Eliminar
    2. ok gracias lo probaré, aunque ahora que dices lo de la funcion es mas logico no?...como seria con la funcion?

      Eliminar
    3. ya lo he probado y va perfecto. gracias de nuevo karl!

      Eliminar
  11. Lo he creado de esta forma y me funciona muy bien :D
    function nave (x,y) {
    ctx.save();
    ctx.translate(x,y);
    // aquí el dibujado vectorial
    ctx.restore();
    }
    nave(20,20);

    ResponderEliminar
    Respuestas
    1. Precisamente así es como se haría con una función.

      Ahora podrás dibujar cada gráfico vectorial fácilmente cuantas veces sea necesario desde tu función paint.

      Eliminar
    2. el rendimiento del ordenador es aumentado con los dibujos vectoriales con respecto a los mapa de bit? porque un dibujo de ese tamaño a dos colores ocupa muy poco en png por ejemplo. Un saludo y gracias

      Eliminar
    3. Vectores y easter tienen ambos ventajas y desventajas. La principal diferencia en cuanto a rendimiento, es que raster ocupa mas RAM y espacio físico, pero vector requiere más CPU para ser procesado. En general con computadoras modernas, si no son tareas intensivas, repercute poco en rendimiento, pero cuando tienes muchas imágenes en proceso, puedes comenzar a ver la diferencia entre ambos.

      Eliminar
  12. Hola como puedo hacer que el cuadrado se detenga en la parte inferior del rectángulo y a su vez al quedarse ahi se genere otro cuadro y asi sucesivamente, pero que los cuadros se acumulen

    ResponderEliminar
    Respuestas
    1. Lo más sencillo para hacer esto, es que una vez que el cuadro actual "se bloquee", sea al llegar abajo o tocar otro cuadro, este se agregue a un arreglo que contenga todos tus cuadros bloqueados, y recicles tu cuadro modificable agregándolo de nuevo a la parte superior, algo así como esto:

      player.y += 10;
      if(player.y > canvas.height){
      rectArray.push(new Rectangle(player.x, player.y-10, 10, 10));
      player.y = 0;
      }

      Esta misma verificación se haría para cada uno de los elementos de rectArray. Es posible que necesites leer al menos los dos temas siguientes para comprender el uso de Rectángulos y Arreglos, pero una vez que sepas como usarles, comprenderás como implementar este código.

      ¡Mucha suerte!

      Eliminar
    2. Muchas Gracias Excelentes Tutoriales!!!!!

      Eliminar
  13. No sería mejor usar "¡El método más popular y efectivo! Usar la delta de tiempo"

    En mi navegador el demo se ve lento pero con la técnica de "Usar la delta de tiempo" va mejor y mas fluido. Entonces porque no usaste ese metodo?????

    Aparte que no le veo bien usar 2 temporizadores al mismo tiempo (setTimeout y requestAnimationFrame) no entiendo como puedes saber que hay una mejor técnica y usar la peor.

    ResponderEliminar
    Respuestas
    1. Puede que en general delta de tiempo sea mejor, pero eso no quita que este método sea más sencillo, lo que considero conviene más para los que apenas empiezan a programar.

      Además, aunque delta de tiempo es en general mejor, hay casos específicos donde es mejor una técnica de ciclos regulares, como el Snake o los juegos de plataformas clásicos.

      Tampoco es que esta sea "la peor" técnica, solo es una alternativa a considerar, y analizar cual es la que mejor conviene para el juego que desarrollas. Yo solo explico el abanico de posibilidades, y dejo que estos detalles técnicos sean elegidos por la conveniencia de cada programador y el código que desean desarrollar.

      Eliminar
  14. Hola, a ver si alguien me puede ayudar. Estoy creando un juego Mario y necesito que se registren 2 teclas si estan estan pulsadas. Pero cuando lo registro: Tecla.Arriba = true; Tecla.Derecha = true; (que salte y vaya a la derecha, pero lo que hace es congelarse el jugador, no se mueve, pero si solo pulso 1 tecla entonces se mueve. No entiendo xd, a ver como se hará.

    ResponderEliminar
    Respuestas
    1. Posiblemente sea alguna forma en que realices la comparación o la lectura, pero para saber cuál es el problema, habría que revisar tu código. ¿De casualidad el ejemplo de plataformas más adelante en el curso te presenta el mismo problema? Quizá ese pueda ayudarte a averiguar como realizar lo que buscas.

      Eliminar
  15. Se que llego unos cuantos años tarde, pero Karl, tus tutoriales son muy buenos y la buena onda que le pones y la ayuda a la gente son incluso mejores! Espero que hayas disfrutado haciendo esto tanto como nosotros (los usuarios) lo disfrutamos tambien :)

    ResponderEliminar
  16. ¿Como se hace para que el Sprite choque con los bordes?
    Con este Js:

    window.addEventListener('load',init,false);
    var canvas=null,ctx=null;
    var x=50,y=50;
    var lastPress=null;
    var pause=true;
    var dir=0;

    var KEY_ENTER=13;
    var KEY_LEFT=37;
    var KEY_UP=38;
    var KEY_RIGHT=39;
    var KEY_DOWN=40;

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

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

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

    function act(){
    if(!pause){
    // Change Direction
    if(lastPress==KEY_UP)
    dir=0;
    if(lastPress==KEY_RIGHT)
    dir=1;
    if(lastPress==KEY_DOWN)
    dir=2;
    if(lastPress==KEY_LEFT)
    dir=3;

    // Move Rect
    if(dir==0)
    y-=10;
    if(dir==1)
    x+=10;
    if(dir==2)
    y+=10;
    if(dir==3)
    x-=10;

    // Out Screen
    if(x>canvas.width)
    x=0;
    if(y>canvas.height)
    y=0;
    if(x<0)
    x=canvas.width;
    if(y<0)
    y=canvas.height;
    }
    // Pause/Unpause
    if(lastPress==KEY_ENTER){
    pause=!pause;
    lastPress=null;
    }
    }

    function paint(ctx){
    ctx.fillStyle='Red';
    ctx.fillRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle='blue';
    ctx.fillRect(x,y,10,10);

    ctx.fillStyle='#fff';
    //ctx.fillText('Last Press: '+lastPress,0,20);
    if(pause){
    ctx.textAlign='center';
    ctx.fillText('PAUSE',150,75);
    ctx.textAlign='left';
    }
    }

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

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

    ResponderEliminar
    Respuestas
    1. Debes cambiar el código que sigue al comentario "Out of screen" con la acción que desees en su lugar. La pregunta es ¿Qué acción deseas realizar en lugar de regresar por el otro lado de la pantalla?

      Eliminar
  17. Este blog ha sido eliminado por un administrador de blog.

    ResponderEliminar
  18. hola
    tengo una duda
    por qué comentaste esta linea //ctx.fillText('Last Press: ' + lastPress, 0, 20);

    ResponderEliminar
    Respuestas
    1. Para que no aparezca en la versión final del juego, ya que sólo se necesita descomentar cuanto se está depurando la información de las teclas requeridas para tu juego.

      Eliminar
  19. hola
    tengo una duda
    por qué comentaste esta linea //ctx.fillText('Last Press: ' + lastPress, 0, 20);

    ResponderEliminar
  20. Tío, eres una máquina.
    Mil gracias.
    Como único aporte (Aunque quizás quieras mantener el tutorial básico) es sustituir el cambio de dirección en vez de con if, con sendos switch.
    Aparte eso. Que muchísimas gracias

    ResponderEliminar
  21. Oye y como hago si quiero que cuando el juego este pausado al presionar cualquier tecla de direccion se despause el juego y al pulzar enter se pause

    ResponderEliminar
    Respuestas
    1. ¡Esa es una excelente pregunta!

      Para despausar el juego con cualquier tecla, agrega esto dentro de la condición "if(pause)":

      // Unpause
      if (lastPress != null) {
      pause = false;
      lastPress = null;
      }

      Y para pausar el juego, agregas esta línea dentro de la condición opuesta:

      // Pause
      if (lastPress == KEY_ENTER) {
      pause = true;
      lastPress = null;
      }

      Eliminar
  22. gracias ya lo cambie en mi codigo pero ahora surgio un inconveniente, ahora al perder automaticamente se despausa el juego... no hay una forma de hacer un segundo codigo para despausar el juego... por ejemplo: al cargar la pagina se ejecute el codigo:

    // Unpause
    if (lastPress != null) {
    pause = false;
    lastPress = null;
    }

    y al perder, para poder despausar el juego se ejecute otro

    ResponderEliminar
    Respuestas
    1. Puedes usar la booleana gameOver para determinar que código ejecutar para despausar el juego.

      Eliminar
  23. Solo una sugerencia:
    si cuando defines la variable "dir", en vez de 0 pones null, al empezar el cuadrado estará quieto, y no moviéndose verticalmente

    ResponderEliminar
  24. Hola amigo, buenas noches, estoy siguiendo estos tutoriales y la verdad muy buenos estoy aprendiendo tecnicas que no habia probado nunca en esto de las animaciones, note un error que tal vez quisieras considerar, te explico, con tu codigo aseguras que el cuadrado verde siempre este en pantalla pero...
    si apretas una de las dos teclas que modifican x (la derecha o la izquierda) justo cuando la variable y = canvas.height el cuadrado no se vera mas en el canvas... eso pasa por que la variable y se reinicia a 0 cuando y > canvas.height, entonces:
    si primero apretamos la flecha abajo, los valores de y iran aumentando constantemente y cuando este valor cumple la condicion y > canvas.height el valor de y se reinicia a 0,
    pero suponiendo que justo cuando y = canvas.height, es decir un momento antes de que se reinicie y aparezca el cuadrado arriba del canvas, apretamos la tecla derecha o la tecla izquierda, eso hara que la variable y deje de modificarse (aumentar su valor) y comienze a modificarse la variable x, esto provoca que el cuadrado verde no se vea mas en el canvas, pero esta ahi llendo de izquierda a derecha pero un poquito mas abajo de nuestro canvas, esto se puede solucionar poniendo en el condicional que verifica cuando el cuadrado verde se sale de pantalla if(y>=canvas.height){//codigo} en lugar de if(y>canvas.height){//codigo} y de igual forma en el condicional que controla la poscicion en x.

    Espero haber explicado bien, saludos, voy a seguir viendo estos tutos que estan muy buenos.

    ResponderEliminar
  25. buenas tardes
    exclente tutorial, soy nuevo en esto de la programacion y al ejecutar el codigo se me presento un error en esta parte:
    function repaint()
    {
    window.requestAnimationFrame(repaint);
    paint(ctx);
    }
    ya que me indica que "window.requestAnimationFrame " no es una funcion . le agracezco me puedan ayudar

    ResponderEliminar
    Respuestas
    1. ¿Me podrías decir qué navegador y versión estás usando? También, ¿El demo en esta entrada sí funciona para ti?

      Eliminar
    2. estoy usando google chrome como navegador, y dsafortunadamente lo probe con el demo y no me corre, agradezco tu ayuda

      Eliminar
    3. siento aparecer como desconocido inicialmente

      Eliminar
    4. He probado múltiples versiones de Chrome, y ninguna me ha dado conflicto alguno. ¿De casualidad podría tratarse de algún Addon o Framework que esté reescribiendo el comportamiento de esta función?

      Eliminar
  26. pues estoy usando xampp, no se si ese pueda ser el problema

    ResponderEliminar
  27. como te podria enviar un print de pantalla para mostrarte l que aparece?

    ResponderEliminar
  28. este es el codigo en js
    var
    KEY_ENTER = 13,
    KEY_LEFT = 37,
    KEY_UP = 38,
    KEY_RIGHT = 39,
    KEY_DOWN = 40,
    canvas = null,
    ctx = null,
    lastPress = null,
    pause = true,
    dir = 0,
    score = 0,
    player = null,
    food = null;
    window.requestAnimationFrame = (function ()
    {
    return
    window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    function (callback)
    {
    window.setTimeout(callback, 17);
    }; }());
    document.addEventListener('keydown', function (evt)
    {
    lastPress = evt.which;
    }, false);
    function Rectangle(x, y, width, height)
    { this.x = (x == null) ? 0 : x;
    this.y = (y == null) ? 0 : y;
    this.width = (width == null) ? 0 : width;
    this.height = (height == null) ?
    this.width : height;

    this.intersects = function (rect)
    {
    if (rect == null)
    {
    window.console.warn('Missing parameters on function intersects');
    }
    else
    {
    return
    ( this.x < rect.x + rect.width &&
    this.x + this.width > rect.x &&
    this.y < rect.y + rect.height &&
    this.y + this.height > rect.y);
    }
    };

    this.fill = function (ctx)
    {
    if (ctx == null)
    {
    window.console.warn('Missing parameters on function fill');
    }
    else
    { ctx.fillRect(this.x, this.y, this.width, this.height);
    }
    };
    }
    function random(max)
    {
    return Math.floor(Math.random() * max);
    }
    function paint(ctx) {
    // Clean canvas
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    // Draw player
    ctx.fillStyle = '#0f0';
    player.fill(ctx);
    // Draw food
    ctx.fillStyle = '#f00';
    food.fill(ctx);
    // Debug last key pressed
    ctx.fillStyle = '#fff';
    //ctx.fillText('Last Press: '+lastPress,0,20);
    // Draw score
    ctx.fillText('Score: ' + score, 0, 10);
    // Draw pause
    if (pause)
    {
    ctx.textAlign = 'center';
    ctx.fillText('PAUSE', 150, 75);
    ctx.textAlign = 'left';
    }
    } function act()
    {
    if (!pause)
    {
    // Change Direction
    if (lastPress == KEY_UP)
    { dir = 0; }
    if (lastPress == KEY_RIGHT)
    { dir = 1; }
    if (lastPress == KEY_DOWN)
    { dir = 2; }
    if (lastPress == KEY_LEFT)
    { dir = 3; }
    // Move Rect
    if (dir == 0)
    { player.y -= 10; }
    if (dir == 1)
    { player.x += 10; }
    if (dir == 2)
    { player.y += 10; }
    if (dir == 3)
    { player.x -= 10; }
    // Out Screen
    if (player.x > canvas.width)
    { player.x = 0; }
    if (player.y > canvas.height)
    { player.y = 0; }
    if (player.x < 0)
    { player.x = canvas.width; }
    if (player.y < 0)
    { player.y = canvas.height; }
    // Food Intersects
    if (player.intersects(food))
    {
    score += 1;
    food.x = random(canvas.width / 10 - 1) * 10;
    food.y = random(canvas.height / 10 - 1) * 10;
    }
    }
    // Pause/Unpause
    if (lastPress == KEY_ENTER)
    {
    pause = !pause;
    lastPress = null;
    }
    }
    function repaint()
    {
    window.requestAnimationFrame(repaint);
    paint(ctx);
    }
    function run()
    {
    setTimeout(run, 50);
    act();
    }
    function init()
    {
    // Get canvas and context
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    // Create player and food
    player = new Rectangle(40, 40, 10, 10);
    food = new Rectangle(80, 80, 10, 10);
    // Start game
    run();
    repaint();
    }
    window.addEventListener('load', init, false);

    ResponderEliminar
    Respuestas
    1. Después de probar tu código, me di cuenta que en la función donde personalizas la función window.requestAnimationFrame, das una nueva línea después de "return". Eso hace que interprete que debe retornar vacío, ignorando el resto de la función; por eso da error tu código. Sí eliminas esta nueva línea, tu código deberá funcionar sin problemas.

      Eliminar