Aprende a crear juegos en HTML5 Canvas

lunes, 20 de febrero de 2012

Parte 6. El juego de la serpiente.

Ahora que ya conocemos las bases para hacer un juego, es hora de pulir algunos detalles y darle a nuestro código la forma de uno de los juegos clásicos: El juego de la serpiente.

Para empezar, comentaremos todo el código con respecto a nuestras paredes para que estas no estorben en el desarrollo y prueba de nuestro juego. Posteriormente será decisión de cada uno si incluirlas o no, y de que forma, para que cada juego sea único.

Lo más esencial del juego de la serpiente, es que el personaje principal va creciendo conforme se alimenta de las manzanas. Para lograr este efecto, utilizaremos un arreglo igual que el de las paredes. Por tanto, sustituiremos de nuestro código la variable “player” por un arreglo al que llamaremos “body”.
    //player = null,
    body = new Array(),
Y cada llamada a “player”, será sustituida por “body[0]”, que es la cabeza de la serpiente. Así pues, donde se movía “player.x” y “player.y”, ahora se moverá “body[0].x” y “body[0].y”. Podemos utilizar la función “reemplazar” de nuestro editor de texto (normalmente Editar » Buscar y Reemplazar) para hacer estas tareas de forma automática.

También es importante resaltar en la función “paint”, que ya no es solo la cabeza la que dibujaremos, si no todo el cuerpo, y esto haremos mediante un “for”:
    // Draw player
    ctx.fillStyle = '#0f0';
    for (i = 0, l = body.length; i < l; i += 1) {
        body[i].fill(ctx);
    }
Para que nuestra serpiente tenga siempre la misma longitud al comienzo de cada juego, debemos “cortarla” a cero, y luego agregar cada parte de su cuerpo tanto larga como la deseemos. Esto se hace en la función “reset” de esta forma:
    body.length = 0;
    body.push(new Rectangle(40, 40, 10, 10));
    body.push(new Rectangle(0, 0, 10, 10));
    body.push(new Rectangle(0, 0, 10, 10));
Es importante que la primer parte del cuerpo (la cabeza) esté en la posición que deseemos que inicie. El resto le seguirá después.

Para mover el cuerpo de la serpiente, se hace uso de un truco peculiar. Este se debe mover de atrás hacia adelante, y antes de moverse la cabeza, haciendo así un efecto de oruga, en que la cola va “empujando” el resto del cuerpo, mediante el siguiente “for”:
        // Move Body
        for (i = body.length - 1; i > 0; i -= 1) {
            body[i].x = body[i - 1].x;
            body[i].y = body[i - 1].y;
        }
De hacerlo de la forma opuesta, todo el cuerpo estaría siempre ocupando el mismo sitio, y no solo “no se vería nada”, si no que además, eso provocaría la perdida del juego al chocar la cabeza con su cuerpo. Para comprobar si el cuerpo choca con la cabeza, se hará de igual forma que como comprobábamos si el personaje chocaba con la pared:
        // Body Intersects
        for (i = 2, l = body.length; i < l; i += 1) {
            if (body[0].intersects(body[i])) {
                gameover = true;
                pause = true;
            }
        }
Nótese que empezamos a contar desde la segunda parte del cuerpo. Esto es por que la parte 0 es la cabeza, y la 1, el cuello. Si empezáramos desde uno de estos, es posible que el juego quedara en constante Game Over “sin razón aparente”.

Por último y muy importante, es hacer crecer a la serpiente. Esto se hace (lógicamente) cuando la cabeza choca contra la manzana, agregando esta línea justo antes de aumentar el score:
           body.push(new Rectangle(food.x, food.y, 10, 10));
Guardemos y actualicemos la página. Con esto, nuestro juego queda concluido, y tendremos un clásico juego de la serpiente, sencillo y completo.

Media


Antes de dar por concluido este pequeño curso, veremos como incluir medios externos en nuestro juego, me refiero claro a imágenes y sonido, algo muy deseado en el desarrollo de los juegos.

Si no he trabajado con imágenes desde un comienzo, es por el importante hecho de resaltar que el tamaño de las imágenes es independiente al tamaño de nuestros rectángulos en nuestro juego.

Actualmente nuestros rectángulos son de 10x10 pixeles, y sin incluimos imágenes más chicas o más grandes, podría aparentar que los objetos en la pantalla no se tocan o viceversa, cuando en realidad ocurriría lo contrario. Es por eso que es sumamente importante que las imágenes tengan siempre el tamaño de nuestros rectángulos en el juego (excepto claro, cuando se usan técnicas avanzadas de efectos visuales).

Para empezar, abramos nuestro editor de imágenes favoritos (Puede ser MS Paint o cualquiera que tengamos), y en un área de 10x10 pixeles, dibujaremos dos dibujos: nuestra comida (una fruta), y la parte del cuerpo de nuestra serpiente (Yo le hice un círculo con manchas, pero pueden dibujarle como les guste).

Estas dos imágenes (“body.png” y “fruit.png”) se guardarán en una carpeta llamada “assets”, dentro de la misma carpeta que nuestro código (es sumamente importante que siempre esté la carpeta “assets” junto al código, o las imágenes no se verán). Ahora, crearemos nuestras variables de imagen:
var iBody = new Image(),
    iFood = new Image();
Y les asignaremos la ruta a la fuente en la función “init”:
    // Load assets
    iBody.src = 'assets/body.png';
    iFood.src = 'assets/fruit.png';
Ahora modifiquemos la función “paint”, comentando donde dibujamos los rectángulos del cuerpo y la comida, y dibujando las imágenes que hemos hecho en su lugar:
    // Draw player
    //ctx.fillStyle = '#0f0';
    for (i = 0, l = body.length; i < l; i += 1) {
        //body[i].fill(ctx);
        ctx.drawImage(iBody, body[i].x, body[i].y);
    }
    
    // Draw food
    //ctx.fillStyle = '#f00';
    //food.fill(ctx);
    ctx.drawImage(iFood, food.x, food.y);
Al guardar y actualizar la página, veremos que ahora nuestros gráficos aparecen en la pantalla en lugar de los rectángulos de colores. Podemos hacer algo similar para poner una imagen de fondo a nuestro juego.

Por último, agreguemos algo de sonido. Cada vez que comamos una fruta, reproduciremos un sonido, y al morir, reproduciremos otro. Puedes conseguir sonidos para tu juego buscándoles en Internet. La declaración será de esta forma:
var aEat = new Audio(),
    aDie = new Audio();
Y se le asigna estos valores en la función “init”:
    aEat.src = 'assets/chomp.oga';
    aDie.src = 'assets/dies.oga';
Para reproducirlos, los agregamos en las área de colisión correspondiente (con la manzana y con el cuerpo), reproduciéndoles así:
    aEat.play();
    aDie.play();
Con esto concluye este pequeño curso, el cual espero haya sido de ayuda y agrada para ustedes. Si tienen dudas, estén seguros que responderé sus comentarios. También pueden enviarme enlaces a los juegos que creen gracias a este curso, los cuales estaré encantado de conocer.

¡Felices códigos!

Recursos:

(Usa clic derecho y "Guardar vínculo como" para guardar estos archivos).

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,
    gameover = true,
    dir = 0,
    score = 0,
    //wall = new Array(),
    body = new Array(),
    food = null,
    iBody = new Image(),
    iFood = new Image(),
    aEat = new Audio(),
    aDie = new Audio();

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 reset() {
    score = 0;
    dir = 1;
    body.length = 0;
    body.push(new Rectangle(40, 40, 10, 10));
    body.push(new Rectangle(0, 0, 10, 10));
    body.push(new Rectangle(0, 0, 10, 10));
    food.x = random(canvas.width / 10 - 1) * 10;
    food.y = random(canvas.height / 10 - 1) * 10;
    gameover = false;
}

function paint(ctx) {
    var i = 0,
        l = 0;
    
    // Clean canvas
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // Draw player
    //ctx.fillStyle = '#0f0';
    for (i = 0, l = body.length; i < l; i += 1) {
        //body[i].fill(ctx);
        ctx.drawImage(iBody, body[i].x, body[i].y);
    }
    
    // Draw walls
    //ctx.fillStyle = '#999';
    //for(i = 0 ,l = wall.length; i < l; i += 1) {
    //    wall[i].fill(ctx);
    //}
    
    // Draw food
    //ctx.fillStyle = '#f00';
    //food.fill(ctx);
    ctx.drawImage(iFood, food.x, food.y);

    // 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';
        if (gameover) {
            ctx.fillText('GAME OVER', 150, 75);
        } else {
            ctx.fillText('PAUSE', 150, 75);
        }
        ctx.textAlign = 'left';
    }
}

function act() {
    var i = 0,
        l = 0;
    
    if (!pause) {
        // GameOver Reset
        if (gameover) {
            reset();
        }

        // Move Body
        for (i = body.length - 1; i > 0; i -= 1) {
            body[i].x = body[i - 1].x;
            body[i].y = body[i - 1].y;
        }

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

        // Move Head
        if (dir == 0) {
            body[0].y -= 10;
        }
        if (dir == 1) {
            body[0].x += 10;
        }
        if (dir == 2) {
            body[0].y += 10;
        }
        if (dir == 3) {
            body[0].x -= 10;
        }

        // Out Screen
        if (body[0].x > canvas.width - body[0].width) {
            body[0].x = 0;
        }
        if (body[0].y > canvas.height - body[0].height) {
            body[0].y = 0;
        }
        if (body[0].x < 0) {
            body[0].x = canvas.width - body[0].width;
        }
        if (body[0].y < 0) {
            body[0].y = canvas.height - body[0].height;
        }

        // Wall Intersects
        //for(i = 0, l = wall.length; i < l; i += 1){
        //    if (food.intersects(wall[i])) {
        //        food.x = random(canvas.width / 10 - 1) * 10;
        //        food.y = random(canvas.height / 10 - 1) * 10;
        //    }
        //  
        //    if(body[0].intersects(wall[i])){
        //        gameover = true;
        //        pause = true;
        //    }
        //}

        // Body Intersects
        for (i = 2, l = body.length; i < l; i += 1) {
            if (body[0].intersects(body[i])) {
                gameover = true;
                pause = true;
                aDie.play();
            }
        }

        // Food Intersects
        if (body[0].intersects(food)) {
            body.push(new Rectangle(food.x, food.y, 10, 10));
            score += 1;
            food.x = random(canvas.width / 10 - 1) * 10;
            food.y = random(canvas.height / 10 - 1) * 10;
            aEat.play();
        }
    }
    
    // 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');

    // Load assets
    iBody.src = 'assets/body.png';
    iFood.src = 'assets/fruit.png';
    aEat.src = 'assets/chomp.oga';
    aDie.src = 'assets/dies.oga';
    
    // Create food
    food = new Rectangle(80, 80, 10, 10);

    // Create walls
    //wall.push(new Rectangle(100, 50, 10, 10));
    //wall.push(new Rectangle(100, 100, 10, 10));
    //wall.push(new Rectangle(200, 50, 10, 10));
    //wall.push(new Rectangle(200, 100, 10, 10));
    
    // Start game
    run();
    repaint();
}

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

90 comentarios:

  1. Muy claro y ameno, gracias

    ResponderEliminar
  2. Quisiera saber si se puede cambiar el tamaño de la pantalla y cambiarle también el fondo por una imagen



    Muy bueno el tutorial... Grax

    ResponderEliminar
    Respuestas
    1. ¡Gracias por tu comentario! Y me alegro te ayude el tutorial.

      Sobre tus dudas, en la lección primera, creamos el canvas con un ancho y alto:
      <canvas id="canvas" width="300" height="150">
      solo deben cambiarse los valores width y height para usar un tamaño distinto.

      Para usar una imagen de fondo, solo debes cambiar la primer línea:
      ctx.clearRect(0,0,canvas.width,canvas.height);
      y dibujar en su lugar la imagen que deseamos de fondo, de igual forma que la manzana y la serpiente, el cual debe tener el alto y ancho de tu canvas:
      ctx.drawImage(iBackground,0,0);

      Suerte en tus siguientes códigos.

      Eliminar
  3. buen tuto, lo que estoy intentando hacer es controlar todo esto con tiempo para el desempate de toodas las personas que jugaran esto, necesito definir el ganador, seguiré buscando, gracias

    ResponderEliminar
  4. No he comprendido exactamente que es lo que buscas, pero tengo la idea de que tratas de que, en caso que dos personas consigan 40 puntos antes de morir, calificar más alto a la que le tomó menos tiempo ¿Es esto lo correcto?

    Si es esto, puedes poner una variable "counter" que sume en uno cada vez que se cicla el código. Esto entre 20, te dará los segundos (Ya que el juego avanza a 20 ciclos por segundo). En el tutorial del próximo lunes explicaré esto precisamente ;)

    ResponderEliminar
  5. gracias por tu apoyo, antes de leer tu pronta respuesta trate de hacerlo por mi mismo y lo hice, solo cree otra función que contara: variable++;
    y esta contaba con la condición que PAUSE=false ya que si es true los segundos dejan de correr.
    aaa, tengo dos funciones uno para segundos y otro para milisegundos, para despojar a los empatados!

    ResponderEliminar
  6. hola de nuevo, lo que estoy haciendo es implementar dos canvas , uno para el juego, y otro que es la zona donde se imprime el puntaje, los segundos, milisegundos, etc.
    lo que pasa es que se me desconfigura el tamaño de la letra, sigo analizando el codigo, mi juego gira en torno a 5px, y no en 10 como tu lo vienes implementando.
    No pido una respuesta ya, porque uno no pide y pide como rey, solo quisiera saber tu punto de vista.a

    ResponderEliminar
  7. Comienzo siendo curioso: ¿Es necesario dos canvas? ¿No puedes hacerlo dentro del mismo canvas? En fin, esto no creo que sea lo más importante.

    En esta parte del curso, aun no manejo distintos tamaños de letra, lo cual me dificulta saber que puedas estar haciendo ahora para que ocurra dicha desconfiguración. ¿Tienes el código subido a algún sitio web? Quizá viéndole me pueda dar una idea más clara.

    Igual, adelantaré el curso de mañana, donde ser resuelven precisamente mucha de las dudas que has tenido estos dos días. Quizá pueda ser de ayuda para ti.

    ResponderEliminar
  8. al poner el puntaje y todo eso... en el mismo canvas, esto estorba el espacio del juego, muchas veces aparece el food detrás de las letras y necesito el lienzo limpio
    este es mi código: http://dl.dropbox.com/u/6768684/snake01.zip
    respeto la seriedad que tienes con tu blog, al mantenerlo al día

    ResponderEliminar
  9. Para empezar, lo que dices es un problema común, y has tenido una forma muy creativa de resolverlo. Pero si ves en la mayoría de los juegos (Excepto recientemente con el Nintendo DS), solo se tiene una pantalla, y con respecto a esta es que ajustan su problema. El método más común, es dibujar un rectángulo negro en un área donde no se dibujarán los objetos del juego, y poner en esta área los datos extras, y mediante el código, limitar que los objetos del juego no entren en esta área (Comenzar a poner objetos desde los 100px en lugar desde el 0, por ejemplo). Independientemente, tu solución es bastante práctica y válida dentro de la plataforma actual en que nos desarrollamos. ¡Bien pensado!

    Ahora si, a resolver tu problema, y se que te reirás conmigo cuando lo comprendas (¡Por que vaya que me pasó también!). En tu CSS, comenta las líneas de height y width, y actualiza. ¿Listo? Ya puedes reír ;)

    Precisamente no he tocado CSS ni estilos, ya que su comportamiento es diferente. Debes asignar height y width directo al canvas, pues CSS "estira" el canvas, no cambia su tamaño. Pero de hecho, esto es una ventaja si sabes aplicarla ¡Pon height y width al 100% en el CSS y verás de lo que hablo! Esta es una de mis técnicas favoritas.

    ¡Suerte! ¡Y muchas gracias!

    ResponderEliminar
  10. que rápido respondes,parece que vivieras en tu blog... y si me reí y ALIVIE!!, no quiero quedar bien pero esta idea de que el css lo estiraba y no lo redimensionaba ya me lo imaginaba, y algo me decía que era desde el canvas, pero tu distes en el punto, gracias, deberíamos de tomarnos un café, bueno ahora a darle las medidas al canvas y a sufrir un poco mas para alinear a la perfección el canvas, lo intentaré con estilos, sino veré la forma de alinearlo desde el canvas,
    siempre lo e dicho la programación es sumamente aburrida cuando algo no te sale o no le entiendes , pero cuando lo comprendes y todo sale como se te pege la gana, vaya que precioso es programar!!

    ResponderEliminar
  11. ¡Mucha suerte entonces! Quizá puedas alinearlos mediante un div contenedor. Suerte con ello ;)

    Me alegro ser de ayuda. El nuevo capítulo ha sido subido ya, puede ser que te ayude para tu tarea actual. ¡Suerte! ;)

    ResponderEliminar
  12. Buenas, me podrias explicar que significa este trozo? es que no lo entiendo... Gracias!


    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){
    return(this.xrect.x&&
    this.yrect.y);
    }
    }
    }

    ResponderEliminar
    Respuestas
    1. Lo explico con detalle en el inicio de la Parte 4. Eso nos crea las características de los rectángulos. Con ello podemos crearles para saber su posición, su tamaño, y saber cuando dos rectángulos hacen intersección entre sí.

      Mediante esta función, creamos el cuerpo de nuestra serpiente, la manzana, las paredes, y prácticamente, cualquier elemento que interactúe con los demás en el juego. Para fines más claros, es un Sprite rectangular.

      Eliminar
  13. Perdona...las prisas...es verdad que está muy clarito al principio. Gracias por contestar

    ResponderEliminar
  14. Buenas otra vez, se puede poner como un mapita de fondo por el que la serpiente avance?

    canvas.style.background='#030'; se puede poner aqui la imagen?

    La idea es que la serpiente vaya avanzando por el mapa en vez de volver el mismo sitio. Supongo que habrá que darle coordenadas al fondo conforme se la serpiente ande, no?
    pero no tengo ni idea de como se hace.

    Gracias por todo. Ya no molesto mas :)

    ResponderEliminar
  15. ya esta solucionado! Gracias de todas formas

    Saludos!

    ResponderEliminar
  16. Quiero darte las gracias por estos tutoriales, no tenia idea de como hacerlo con canvas y HTML5. En dos días he podido hacer el pequeño experimento que quería. Les dejo el link por si les interesa y si quieren el código me pueden decir.

    Es básicamente uno de esos juegos de miedo donde si encuentras la hora aparece el susto.
    http://turnthelight.tk/

    ResponderEliminar
  17. Hola!! Antes que nada loco te felicitio realmente porque el tutorial es super claro..!!

    Estaba metiendo mano en el codigo y tenia ganas de que la serpiente crezca a medida que se mueve independientemente coma o no.. que este creciendo desde un principio.

    Se me ocurrio agregarle una parte del cuerpo en esta seccion del codigo:

    //Move body
    for(i = body.length - 1; i > 0; i--){
    body.push(new Rectangle(0,0,10,10)); <--- aca
    body[i].x = body[i-1].x;
    body[i].y = body[i-1].y;
    }
    Pero cuando lo ejecuto la serpiente crece a medida que avanza sin comer, pero se hace mas lento hasta que el script del navegador se detiene y deja de funcionar. ¿Como hago para que se mueva con fluidez sin que falle?

    ResponderEliminar
    Respuestas
    1. Imprime la longitud de body y verás por que se vuelve tan lento. Al hacerlo dentro del for, estas creciendo el cuerpo de forma exponencial, creando miles de elementos en pocos segundos. Alentando por ello el juego. Debes agregar la nueva parte del cuerpo fuera del for, posiblemente cuando muevas la cola. Dime si funciona!

      Eliminar
    2. Funcionó! :D Solo tenía que colocarlo fuera del For como tú me dijiste y funcionó a la perfección

      //Move body
      for(i = body.length - 1; i > 0; i--){
      <--- Antes aca
      body[i].x = body[i-1].x;
      body[i].y = body[i-1].y;
      }
      body.push(new Rectangle(0,0,10,10)); <--- Ahora aca

      Gracias a tu respuesta pudimos avanzar muchisimo con la construccion de nuestro juego, a medida que vayamos avanzando te mostraremos como va quedando. Una vez mas gracias :D

      Eliminar
  18. Hola, primero que nada muchas gracias por los tutos, realmente son muy buenos.
    Bueno, lo que noto cuando la serpiente come una manzana es que en la esquina superior izquierda, se nota como se dibuja la imagén por unas milesimas de segundo, no sabes por que es? se podría arreglar de alguna forma?, bueno eso es todo, muchas gracias de nuevo por los tutos.
    Saludos!

    pd. Estaría bueno que comiences unos tutos con alguna librería, más precisamente con melonjs, que es open source, saludos y gracias nuevamente.

    ResponderEliminar
    Respuestas
    1. ¡Muchas gracias por todo!

      El detalle que has notado (y me extraña nadie mas haya comentado antes al respecto), se da debido a que creas el nuevo cuerpo en la posición 0,0. Si lo muevs a otro sitio, se creará en un lugar distinto. Prueba hacerlo fuera del escenario.

      Analizaré MelonJS como alternativa para esto. Gracias por el consejo ;)

      Eliminar
    2. Perfecto! ahora quedó lo más bien.
      Esperaré los tutos de MelonJS, una pregunta, no tienes algún canal de youtube o algo, así puedo enterarme de nuevos tutos que crees?

      Saludos!

      Eliminar
    3. Por el momento no tengo canal de YouTube; si lo que te interesa es seguirme, en la página principal hay varios métodos para ello, incluyendo Correo-e, RSS, Google+ y Facebook.

      He estado considerando la idea de un canal de YouTube, que aunque ese método me parece poco práctico para mi, me parece que mucha gente podría verle como una buena alternativa. Te avisaré en caso de hacerlo de esta forma.

      Eliminar
    4. Ok entonces, ya te estoy siguiendo por correo-e
      Saludos!

      Eliminar
  19. Hola, la verdad que aprendi muchisimo con esto, muchas gracias por compartir este excelente tutorial.

    Una duda nada mas, como puedo hacer para que si la snake choca contra un borde de por perdido? porque la mia sigue sobrepasando los limites de mi canvas.

    ResponderEliminar
    Respuestas
    1. En vez de ponerle que salga por el otro lado le pones GAMEOVER = true;
      Se supone que ahí tiene que quedar.

      Eliminar
    2. // Out Screen

      En esa parte cierto? lo hice cambie todos los if, y lo unico que hace al salir es como "rebotar", me podrias dar un ejemplo de como deberia ser ya que en algo estoy fallando y no logro darme cuenta. Muchas gracias por responder.

      Eliminar
    3. Es cierto lo que dijo Elbio, pero también olvido mencionar que debes activar el Pause y el sonido de fin de juego para cada if. En resumen, todo lo que ocurre al cuando la cabeza choca contra el cuerpo:

      {
      GAMEOVER=true;
      PAUSE=true;
      aDie.play();
      }

      También puedes poner todo esto en una función y mandar a llamar dicha función en cada sección que pierdas. Así será mas fácil de mantenerla en caso de futuras modificaciones.

      Eliminar
    4. Muchisimas gracias, me funciono a la perfeccion! Genios.

      Eliminar
  20. Hola

    Como podría colocar un incremento de velocidad usando window.requestAnimationFrame, lo he implementado usando setTimeout(). Pero no se me ocurre como controlar el la aceleracion usando el control por frames.

    Esta ha sido la modificacion:

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

    function repaint(){
    setTimeout(repaint,speed);
    //requestAnimationFrame(repaint);
    paint(ctx);
    }

    Un saludo y enhorabuena por los tutoriales.

    ResponderEliminar
    Respuestas
    1. El repaint no es necesario de ser modificado, solo necesita modificar la velocidad de act, que ya lo estás haciendo bien.

      Eliminar
  21. Fantástico blog! Un trabajo increíble que me está ayudando mucho. Enhorabuena y seguid así.

    Saludos!

    ResponderEliminar
  22. oye amigo tengo un problema intento guardar los png y cuando actualizo la pagina no me sale los guardo en la carpeta "media" que tu dices y no me salen los scrips en la pagina porfavor me ayudas

    ResponderEliminar
    Respuestas
    1. Cambia el nombre de la carpeta a "assets". Ese es el nombre correcto de la misma.

      Eliminar
    2. MUCHAS GRACIAS ME SIRVIÓ TU SI SABES

      Eliminar
  23. Hola, muy buenos tutoriales :D

    Solo tengo una duda, no me suenan los sonidos, ni en tu ejemplo, ni en un jueguito que estoy haciendo, ¿sabes a que se deba? :D

    Saludos

    ResponderEliminar
    Respuestas
    1. Yo no estoy teniendo ningún problema con ello. ¿Podrías decirme qué navegador estás usando y si te imprime algún error en la consola de javascript?

      Eliminar
    2. Hola, gracias por reponder :D

      Estoy usando google chrome version: 35.0.1916.153 m y no me da ningun error en consola D:

      Eliminar
    3. Perdona que haya tardado en contestar. Estoy haciendo pruebas extensas, y todo parece apuntar a que el soporte para m4a ha sido detenido por problemas de licencia. Investigaré más a fondo para buscar una solución al problema.

      Eliminar
    4. woooaaa muchisimas gracias por tomarte el tiempo :D tus tutoriales me han ayudado bastante en cosas que no lograba encontrar por mas que buscaba
      :D

      Eliminar
    5. Solo cambie a formato .WAV y ya suena, muchas gracias :D

      Eliminar
    6. Todo indica que Chrome ha decidido dejar de soportar m4a por conflictos de licencias. Pronto actualizaré el blog para recuperar el audio en el ejemplo.

      Te recomiendo que uses ogg en lugar de wav para salvar un poco de ancho de banda en tu sitio. ¡Felices códigos!

      Eliminar
  24. Hola amigo, copie y pegué el código tal cual y bajé los archivos creando la carpeta y todo pero sin embargo me aparece la imagen que muestras como resultado final pero no me permite jugarlo. El código es el mismo que muestras al final, he usado chrome, mozille y explorer, en mozilla el web developer no me marca error. Debo de modificar algo en concreto? Gracias por su ayuda de antemano!.

    Saludos

    ResponderEliminar
    Respuestas
    1. Solo para saltar lo obvio... ¿Presionaste "Enter" para iniciar el juego? ¿El ejemplo puesto en este sitio es jugable para ti?

      Eliminar
  25. Hola, muchisimas gracias por el tutorial me ha servido mucho. Tenia una consulta: ¿Hay alguna forma de hacer este mismo juego, pero regulando el tiempo con el metodo del delta de tiempo?. Lo que pasa es que lo he intentado pero esto me trae problemas al momento de que crece la serpiente y la cabeza cambia su direccion, porque como no avanza lo mismo que el tamaño del cuadrado (es menor por el delta de tiempo), al momento de mover el cuerpo, los cuadrados que vienen despues de la cabeza la intersectan, y no se mantienen al borde. Espero haberme explicado bien.

    Saludos!

    ResponderEliminar
    Respuestas
    1. Hay juegos que se ven muy beneficiados de usar un tiempo estático en lugar de la delta de tiempo, y sin duda, el juego de la serpiente es uno de ellos.

      No es imposible usar la delta para este juego, pero sin duda es demasiado complejo. El chiste es crear dos serpientes: La virtual, que tiene un desplazamiento fijo, y la dibujada, que se desplaza al tiempo delta siguiendo la posición de la serpiente virtual. Cuando esta segunda alcanza a la primera, es cuando esta primera se desplaza al próximo destino futuro, que en teoría es el mismo desplazamiento que hemos aprendido aquí. Como verás, es bastante complejo, pero la recompensa ante tan compleja tarea, es que el movimiento de la serpiente es mucho más natural y fluido.

      Espero esto resuelva la duda que tenías.

      Eliminar
    2. Muchas gracias por la respuesta. Cuando dices que la segunda serpiente alcanza a la primera ¿A que te refieres? ¿Que todos los cuadrados del cuerpo de cada una coincidan? Y sobre la serpiente virtual, ¿solo se moveria cuando la otra serpiente la alcanza? y en ese caso, ¿como se haria con los movimientos que va haciendo el usuario? ¿Se van guardando? Lo digo para que el movimiento sea inmediatamente luego de presionar la tecla.

      Disculpa por la cantidad de preguntas, pero es para entender mejor el tema. No es de necesidad pero tenia ganas de aprenderlo en caso de tener el mismo problema a futuro.

      Saludos

      Eliminar
    3. Sería más o menos así: Tienes una variable digamos "headPos", esta la mueves un bloque entero al siguiente cuadro de acuerdo a la dirección. Durante los siguientes ciclos, mueves la serpiente en dirección a "headPos" hasta que la sobrepases levemente, y ajustas la posición final a la misma. Justo en ese momento, lees si ha habido un cambio en las teclas precionadas almacenadas hasta ese momento, y procedes a mover de nuevo "headPos" al siguiente bloque. En resumen, algo así sería la lógica.

      Eliminar
    4. Perfecto. ¿Y en los casos en que la serpiente tenga mas de un bloque digamos de cuerpo, y suponiendo que la cabeza cambia la direccion, tendria que esperar hasta que esta alcance a la serpiente virtual, para cambiar la posicion de los demas bloques? Me refiero por ejemplo cuando tiene 3 bloques que van hacia la izquierda, luego la cabeza cambia su direccion hacia arriba, y en ese momento, el 2do bloque tiene que pasar a la posicion antigua en la que estaba la cabeza, para producir ese efecto de serpiente. Mi pregunta es cómo hago ese cambio, y si desplazo el bloque inmediatamente luego de que la serpiente dibujada alcance a la virtual, o ir usando el mismo delta tiempo, donde este ultimo metodo no se me ocurre como.

      Eliminar
    5. Es precisamente como lo explicaste en tu duda.

      Eliminar
    6. Muchas gracias! una ultima cosa: No puedo escuchar los sonidos del juego en esta pagina, ¿A que se debe? Lo probe en Google Chrome y Firefox.

      Eliminar
    7. Al parecer había un problema con el servidor donde estaban alojados y la forma en que enviaba dichos archivos. El problema ya debería estar corregido. ¿Puedes avisarme si ya los escuchas correctamente?

      Eliminar
    8. Se escucha perfecto!

      Eliminar
  26. Muchas gracias por los tutoriales, son muy claros y ayudan muchísimo, por lo menos para mi que estoy empezando !!

    ResponderEliminar
  27. tengo una duda con lo siguiente

    body[ j ].x = body[ j - 1 ].x

    A ti te funciona perfectamente, pero en mi versión este retazo hacer que el cuerpo de mi snake siempre este en la posición de la cabeza es decir en la posicion de body[ 0 ]...

    ResponderEliminar
    Respuestas
    1. ¿Estás haciendo el for inverso? Ese error suele ocurrir cuando no inviertes dicho for...

      Eliminar
    2. Podrías revisar mi código lo he puesto en github

      https://github.com/romualdo97/my-first-canvas-game

      Mira en la dirección assets/scripts/act.js linea 25

      Eliminar
    3. he puesto un 1 en el bucle for en lugar de body.lenght por cuestiones de logging

      Eliminar
    4. Estoy usando el bucle del mismo modo que propones en tu código, es un error interesante el que tengo...

      Eliminar
    5. Ya encontré el problema, y una forma en que tú mismo puedes identificarlo. Si dibujas el cuerpo de tu serpiente con strokeRect en lugar de fillRect, verás que está funcionando sin problemas el código, pero te estas desplazando a 5 pixeles por cuadro, mientras que tu serpiente mide 50x50, haciendo que la diferencia de distancia sea mínima.

      Si tu intensión es que el tamaño de la serpiente sea distinto al tamaño de su desplazamiento, necesitaras una solución creativa para ello. Hay varias posibles, es cuestión de que ingenies cual consideres mejor para tu juego.

      Eliminar
  28. Hola aveces me gusta fastidiar, escribo esto para que te llegue una notificacion y de ese modo llamar tu atencion para que si puedes revises mi codigo, te decia que he puesto el proyecto en github en la siguiente direccion
    https://github.com/romualdo97/my-first-canvas-game

    La parte que no funciona tal como espero se encuentra en /assets/scripts/act.js : line 25
    Advertir que en el bucle for he cambiado body.lenght - 1 por 1 para solo mover la cabeza y el cuello de la snake

    Los cuadros con que inicia la snake estan en
    /assets/scripts/game.js : line 83 dentro de la funcion startWorldObjects()

    ResponderEliminar
  29. Respuestas
    1. ¡Por SPAMER! >3...

      Pero no te abandoné, sólo te puse en la cola de mi lista de prioridades ;)

      Paciencia, joven saltamontes.

      Eliminar
  30. Te reirás de ver mi solución, la he pusheado al repo en github por si quieres un rato de diversión jaja, mejor usare tu técnica...

    ResponderEliminar
    Respuestas
    1. No estoy seguro pero... Algo me dice que eso creará un efecto no deseado...

      Bueno, necesitaba un momento de diversión. Igual diré que fue una solución creativa.

      Eliminar
  31. Me has ayudado muchísimo para introducirme en el HTML5 y JS. No sé si las explicaciones claras porque vengo de una ingeniería y tengo bastante base de programación, pero para mí han sido una maravilla. Por cierto, tengo entre manos un proyecto y me gustaría saber si puedo coger este código como base para mi versión de Snake que casualmente pretendía hacer.

    ResponderEliminar
    Respuestas
    1. Me alegra que este curso haya sido de ayuda para ti. ¡Y por supuesto que puedes usarle para irle perfeccionando hasta conseguir lo que buscas! Este es, después de todo, una base para ello mismo.

      Eliminar
    2. Perfecto, encima madrugador jaja Ya te avisaré cuando acabe para que le eches un ojo a tu hijo bastardo de Snake

      Eliminar
  32. Tengo, un problema, no entiendo muy bien como colocar las imágenes y sonidos, ¿Me pueden ayudar?

    ResponderEliminar
    Respuestas
    1. Por supuesto. ¿Comparaste los códigos de la explicación con el código final? ¿En qué parte tienes duda?

      Eliminar
  33. no puedo poner las imágenes ni sonidos cuando lo intento ,solo aparece la pantalla negra y nada mas, aunque no estoy seguro de como poner el src de las imágenes ni sonidos.

    ResponderEliminar
    Respuestas
    1. Es dentro de la función Init; revisa en el código final, ya cerca del final de este.

      Eliminar
  34. Eres grande Karl!!!! Karl for President!!!

    ResponderEliminar
  35. Nose si estas estas al dia con el blog, pero tengo una duda xq se genera ese bug donde se pierde la serpiente y hay qe restarle el ancho y alto
    if (body[0].x > canvas.width - body[0].width) {
    body[0].x = 0;
    }
    ...

    Si el canvas ya tiene un ancho de 300 y un alto de 150

    ResponderEliminar
  36. estoy tratando de hacer que la velociad de la "oruga" aumente dependiendo del puntuaje
    function run()
    {
    if(score >= 0)
    {
    setTimeout(run, 100);
    act();
    }
    if(score >= 3)
    {
    setTimeout(run, 100);
    act();
    }
    }

    estaba trando de hacerlo de esta manera, pero al momento de que el puntuaje llegaa 3 la "oruga" aumenta demasiado su velocidad. a que se debe esto? que estoy haviendo mal?

    ResponderEliminar
  37. Hola. Tengo un problema con el código y lo he revisado cientos de veces a la vez que comparaba con el tuyo. Lo que ocurre es que aparece score: 0 y pause, luego se pone toda la pantalla en negro y no aparece nada más. ¿Alguien sabe lo que ocurre? Gracias.

    ResponderEliminar
    Respuestas
    1. Si lo que no aparecen son los gráficos, tal vez no los esté cargando correctamente. ¿Ya revisaste que estén en la carpeta correcta con el mismo nombre?

      Independientemente de si ese es el error o no, ¿Ya intentaste usar la consola de Javascript para ver si reporta algún error?

      Eliminar
  38. hola gg, soy el ultimo que tuvo problemas en el primer paso, ahora el juego funciona bien, excepto que cuando la cabeza de la serpiente es intersectada por el cuerpo se pone en pausa en ves de Game Over, halp

    ResponderEliminar
    Respuestas
    1. Posiblemente te haya faltado algo del código de GameOver, sea al perder en la línea 196, o al dibujar el mensaje en la línea 113. Checa si los tienes igual, o algo de ello te faltó.

      Eliminar
    2. jajaja, algo loco, las cheque como 15 veces antes de preguntarte y no captaba jajaja, pues me faltaba activar el valor de gameover a true cuando intersectaba asi que si, gracias :^)
      De hecho, estoy empezando a aprender codigo generalmente, lo unico que habia hecho era un proyecto de la escuela de html y era muy basico, y esto lo hice por diversion, estuve aprendiendo javascript y me ha ayudado mucho tu blog, asi que muchas gracias

      Eliminar
    3. ¡Que bueno que lograste resolverlo! Y me alegra mucho que hayas podido aprender con a través del blog. Ojalá sea de mucha ayuda para ti en el futuro. ¡Éxito!

      Eliminar
  39. Gracias por el código, sí me resultó todo correcto, pero aún sin tener los "png de body y fruit" sí me aparecen. ¿Qué sucederá, de dónde los extrae?. http://s2.subirimagenes.com/otros/previo/thump_9753480das.jpg

    ResponderEliminar
    Respuestas
    1. ¿Cómo tienes tu código de inicialización? Tal vez estés usando rutas absolutas en lugar de relativas.

      Eliminar