Canvas – Juego de Serpiente

Hola! En mi primera entrada en el blog, voy a explicar como programar un juego en JavaScript, Canvas y HTML5. Se trata del ejemplo más antiguo del mundo de videojuegos: “La serpiente“.

La mecánica es muy simple, sólo hay que evitar que la serpiente choque contra los bordes de la pantalla (o contra si misma) y intentar comer todas las manzanas que puedas.

  1. Debemos crear un documento “HTML” sencillo con un elemento <canvas> en su cuerpo
    ( o <body>). Lo llamaremos “index.html” y lo guardamos en una carpeta llamada, por ejemplo, “Serpiente”.

    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
    &lt;head&gt;
     &lt;title&gt;Serpiente&lt;/title&gt;
     &lt;script src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
     &lt;canvas id=&quot;canvas&quot; width=&quot;450&quot; height=&quot;450&quot;&gt;&lt;/canvas&gt;
    &lt;/body&gt;
    &lt;/html&gt;
    

    El elemento <canvas> tiene parámetros width y heigth, los cuales definen la altura del cuadro en el que vamos a dibujar más adelante, son prescindibles , ya que si no los definimos, el elemento <canvas> tendrá tamaño 0x0 y no se verá. Después del elemento <title> importaremos librería jQuery de JavaScript para poder trabajar más cómodamente.

  2. Ya hemos acabado con HTML pero nuestra tarea no acaba más que empezar.  Ahora debemos crear nuestro fichero JavaScript, el cual vamos a llamar “functions.js“.
    Después de crearlo, debemos importarlo al documento HTML “index.html“, añadiendo una etiqueta <script> después de la que ya tenemos para jQuery.

    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
    &lt;head&gt;
     &lt;title&gt;Serpiente&lt;/title&gt;
     &lt;script src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
     &lt;script type=&quot;text/javascript&quot; src=&quot;functions.js&quot;&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
     &lt;canvas id=&quot;canvas&quot; width=&quot;450&quot; height=&quot;450&quot;&gt;&lt;/canvas&gt;
    &lt;/body&gt;
    &lt;/html&gt;
    
  3. Debemos indicar que la aplicación Canvas se ejecute cuando la página cargue completamente, para ello vamos a usar  $(document).ready(function{}); La función del parámetro de “ready” es la que se va a ejecutar.
    Nuestra aplicación necesitará unas cuantas variables para ejecutarse correctamente: el elemento html donde se va alojar, el lienzo donde va a pintar, la altura y la anchura del lienzo, el ancho de columna que va a tener nuestro juego, dirección en la que se mueve la serpiente, la posición  de la manzana y finalmente la puntuación. Actualmente deberíamos tener algo como esto:

    $(document).ready(function(){
     var canvas = $(&quot;#canvas&quot;)[0]; //Elemento html
     var ctx = canvas.getContext(&quot;2d&quot;); //El lienzo
     var w = $(&quot;#canvas&quot;).width(); //Anchura del lienzo
     var h = $(&quot;#canvas&quot;).height(); //Altura del lienzo
    
     var cw = 10; //Tamaño de columnas
     var d;  //Dirección del movimiento
     var food; //Posición de la manzana
     var score; //Puntuación
    
     var snake_array; //Lista de puntos que ocupa la serpiente
    });
    
  4. Ahora debemos inicializar nuestras variables y empezar a dibujar! Muy fácil decirlo, no tanto hacerlo.
    Lo primero seria definir la posición tanto la serpiente como de la manzana, para ello vamos a crear dos funciones bastante sencillas:

    function create_snake(){
      var length = 5; //Longitud de serpiente por defecto
      snake_array = []; //Array vació al que se le añadirán las futuras posiciónes
     for(var i = length-1; i&gt;=0; i--){
        snake_array.push({x: i, y:0});
      }
     }
    
     function create_food(){
      food = {
        x: Math.round(Math.random()*(w-cw)/cw),
        y: Math.round(Math.random()*(h-cw)/cw),
      };
     }
    

    La primera sirve para crear un array de objetos que contienen X e Y de cada punto de la serpiente, que por defecto mide 5 puntos, en otras palabras, 5 veces nuestro valor de ancho de cada columna (5*cw). Mientras que la segunda función genera un objeto “food” con las posiciones X e Y aleatorias que se encuentran en nuestro lienzo ( X < w; Y<h).

    Por pura obsesión con el control y esteticismo, vamos a definir una función llamada “init” para englobar todos los demás procesos de inicialización en el.  Aquí vamos a definir la dirección del movimiento por defecto, puntuación por defecto y la velocidad de ejecución de nuestro juego (lo rápido que se va a mover la serpiente por el lienzo). Esa última operación es la más delicada e importante, se realiza mediante el uso de la función setInterval() de JavaScript. A continuación podéis ver como se hace:

    function init(){
     d = &quot;right&quot;; //Definimos la dirección por defecto
     create_snake(); //Creamos la serpiente
     create_food(); //Creamos la manzana
     score = 0; //Definimos la puntuación por defecto
    
     //Si la variable GLOBAL &quot;game_loop&quot; existe -&gt; vaciamos su contenido
     if(typeof game_loop != &quot;undefined&quot;) clearInterval(game_loop);
     //Definimos nueva variable global &quot;game_loop&quot; y
     //le indicamos que funcione como un hilo de ejecución que cada 60 misisegundos
     //ejecuta la  función &quot;paint&quot; -&gt; que se encargará de dibujar tooooodo lo que hay
     //en nuestro lienzo.
     game_loop = setInterval(paint, 60);
    }
    

    Ya que hemos definido la función “init“, deberíamos darle uso y ejecutarla justo después de las definiciones de las variables al principio de nuestro fichero JavaScript.
    El conjunto de código actual debería quedar más o menos así:

    $(document).ready(function(){
     var canvas = $(&quot;#canvas&quot;)[0];
     var ctx = canvas.getContext(&quot;2d&quot;);
     var w = $(&quot;#canvas&quot;).width();
     var h = $(&quot;#canvas&quot;).height();
    
     var cw = 10;
     var d;
     var food;
     var score;
    
     var snake_array;
    
    init();
    
    function init(){
     d = &quot;right&quot;;
     create_snake();
     create_food()
     score = 0;
    
     if(typeof game_loop != &quot;undefined&quot;) clearInterval(game_loop);
     game_loop = setInterval(paint, 60);
    }
    function create_snake(){
      var length = 5;
      snake_array = [];
     for(var i = length-1; i&gt;=0; i--){
        snake_array.push({x: i, y:0});
      }
     }
    
    function create_food(){
      food = {
        x: Math.round(Math.random()*(w-cw)/cw),
        y: Math.round(Math.random()*(h-cw)/cw),
      };
     }
    });
    
  5. Ahora nos toca definir el método para dibujarlo todo. Lo vamos a llamar “paint“. Se trata de la función más compleja de esta aplicación, así que tengan cuidado!
    -Nuestro lienzo debe tener un fondo, el cual debe de repintarse en cada secuencia, para este ejemplo, pintaremos fondo blanco con un borde negro de 1px.
    -El movimiento tiene una lógica bien simple, con cada secuencia, el ultimo bloque (la cola) se posicionara enfrente del primer bloque (la cabeza).
    -Si la serpiente choca con el primer bloque contra una de las paredes o contra si misma, el juego debe reiniciarse (en otras palabras, llamar la función “init” de nuevo).
    -Si la serpiente choca con el bloque que representa la manzana, se le añadirá un bloque al final (ya que de este modo el bloque final no cambiará de posición realmente, si no que el primer bloque se moverá adelante y el último se quedará donde estaba) y la puntuación aumentará en 1.
    -Se mostrará la puntuación actual en la esquina inferior izquierda.

    function paint(){
     ctx.fillStyle = &quot;white&quot;;//Seleccionamos color blanco
     ctx.fillRect(0, 0, w, h);//Pintamos fondo
     ctx.strokeStyle = &quot;black&quot;;//Seleccionamos color negro
     ctx.strokeRect(0, 0, w, h);//Pintamos un cuadrado sin rellenar
    
     //Almacenamos X e Y del primer bloque de la serpiente en las variables &quot;nx&quot; y &quot;ny&quot;
     var nx = snake_array[0].x;
     var ny = snake_array[0].y;
     //A continuación, dependiendo de la dirección del movimiento,
     //cambiaremos la posición de primer bloque.
     if(d == &quot;right&quot;) nx++;
     else if(d == &quot;left&quot;) nx--;
     else if(d == &quot;up&quot;) ny--;
     else if(d == &quot;down&quot;) ny++;
    
     //Si en algún momento el primer bloque llega a tener la
     //posición -1, máxima anchura, máxima altura o la misma posición
     //que uno de los bloques que componen la serpiente actualmente
     //el juego se reiniciará
     if(nx == -1 || nx == w/cw || ny == -1 || ny == h/cw || check_collision(nx, ny, snake_array)){
      //Reiniciamos el juego con valores por defecto
      init();
      return;
     }
    
     //Si la cabeza de la serpiente tiene la misma posición
     //que la manzana, añadimos un bloque a la cola y sumamos 1
     //a la puntuación
     if(nx == food.x &amp;&amp; ny == food.y){
      //el bloque de cola
      var tail = {x: nx, y: ny};
      score++;
      //Despues de consumir la manzana, creamos una nueva
      create_food();
     }else{
      //Borra el ultimo bloque de Array
      var tail = snake_array.pop();
      tail.x = nx; tail.y = ny;
     }
     snake_array.unshift(tail); //Vuelve a poner la cola como primer elemento
    
     for(var i = 0; i &lt; snake_array.length; i++){
      var c = snake_array[i];
      //Pintamos cuadros 10x10
      paint_cell(c.x, c.y);
     }
     //Dibujamos la comida
     paint_cell(food.x, food.y);
     //Dibujamos la puntuación
     var score_text = &quot;Puntuación: &quot; + score;
     ctx.fillText(score_text, 5, h-5);
     }
    

    En este código hemos usado dos funciones: “check_collision” y “paint_cell” que no están definidas todavía, pero su funcionamiento es bastante claro.
    paint_cell” dibuja un cuadro del tamaño de cuadricula*cuadricula (cw*cw) en la posición indicada.
    check_collision” comprueba que la X e Y no se encuentran en la lista de posiciones que se le pasa(Array “snake_array” en este caso).

    function paint_cell(x, y) {
     ctx.fillStyle = &quot;blue&quot;;
     ctx.fillRect(x*cw, y*cw, cw, cw);
     ctx.strokeStyle = &quot;white&quot;;
     ctx.strokeRect(x*cw, y*cw, cw, cw);
    }
    
    function check_collision(x, y, array) {
     for(var i = 0; i &lt; array.length; i++) {
      if(array[i].x == x &amp;&amp; array[i].y == y)
       return true;
      }
     return false;
    }
    
  6. Por último, debemos definir las teclas, pulsando las cuales la dirección del movimiento de la serpiente debe de cambiar.
    Eso se consigue usando $(document).keydown(function(e){}); La función del parámetro keydown es la que se ejecuta cuando CUALQUIER tecla es pulsada.
    Para distinguir las teclas que vamos a usar  (las flechas) vamos a usar el código de las teclas deseadas, que son 37, 38, 39, 40. La función quedaría así:

    $(document).keydown(function(e){
     //e.witch devuelve el código de la tecla pulsada
     var key = e.which;
     //Comparamos el código de la tecla pulsada con el
     //código de la tecla deseada y cambiamos la dirección si es necesario
     if(key == &quot;37&quot; &amp;&amp; d != &quot;right&quot;) d = &quot;left&quot;;
     else if(key == &quot;38&quot; &amp;&amp; d != &quot;down&quot;) d = &quot;up&quot;;
     else if(key == &quot;39&quot; &amp;&amp; d != &quot;left&quot;) d = &quot;right&quot;;
     else if(key == &quot;40&quot; &amp;&amp; d != &quot;up&quot;) d = &quot;down&quot;;
    });
    

Ya tenemos  nuestro juego completamente acabado y listo, sólo faltaría probarlo!  Si has siguiendo el tutorial, seguramente ya lo has hecho, si no es así, puedes descargar fichero .zip con la carpeta y los ficheros necesarios para ejecutarlo desde el siguiente enlace [MEDIAFIRE] :

download-banner

 Gracias por leer mi tutorial, un saludo!

Anuncios