Aprende a crear juegos en HTML5 Canvas

domingo, 26 de enero de 2014

Saltando a las plataformas

De los laberintos a las plataformas solo hay un salto. ¡Literalmente!

La forma en que se construyen los mapas de plataformas, usa el mismo método para crear mapas de laberintos, cambiando tan solo algunos detalles en el desplazamiento vertical para simular el uso de plataformas, en lugar del desplazamiento desde vista arriba que simulan los laberintos. Y el día de hoy veremos como ajustar estos detalles para entrar en un genero totalmente nuevo, basándonos en el código de Mapas de Mosaico que vimos anteriormente para crear nuestro juego de laberintos.

Lo primero que tenemos que tener en cuenta, es que los juegos de plataformas se basan siempre en aceleraciones, a diferencia de los laberintos que podíamos elegir un desplazamiento constante. Para almacenar la aceleración de cada objeto en nuestro juego, agregaremos el vector de velocidad en la coordenada x, y el vector de velocidad en la coordenada y, inicializados en cero, dentro de la función rectángulo:
        vx: 0,
        vy: 0,
Es importante regresar a cero estos dos valores también en la función "reset" para asegurarnos que no se inicie con una aceleración no deseada cuando empiece un nuevo juego.

Comenzaremos por ajustar algunos detalles en el desplazamiento sobre el eje x. Para este juego aplicaremos aceleración usando una de las técnicas que aprendimos precisamente en el tema de Aceleración. Puedes usar la que más te haya gustado; yo he elegido esta:
            // Set vectors
            if (pressing[KEY_RIGHT]) {
                if (player.vx < 10) {
                    player.vx += 1;
                }
            } else if (player.vx > 0) {
                player.vx -= 1;
            }
            if (pressing[KEY_LEFT]) {
                if (player.vx > -10) {
                    player.vx -= 1;
                }
            } else if (player.vx < 0) {
                player.vx += 1;
            }
Una vez establecida la velocidad de nuestro personaje, proseguimos a moverlo y revisar si está impactando una pared. En tal caso, ajustamos su posición para que aparezca al lado correspondiente de la pared, y removemos su aceleración:
            // Move player in x
            player.x += player.vx;
            for (i = 0, l = wall.length; i < l; i += 1) {
                if (player.intersects(wall[i])) {
                    if (player.vx > 0) {
                        player.right = wall[i].left;
                    } else {
                        player.left = wall[i].right;
                    }
                    player.vx = 0;
                }
            }
Ahora pasemos al movimiento en el eje y. Habrá algunas diferencias con respecto al eje x, ya que en este caso estamos tratando con una fuerza de gravedad constante hacia abajo, en lugar de una desaceleración:
            // Set gravity
            player.vy += 1;
            if (player.vy > 10) {
                player.vy = 10;
            }
Notarás que estoy dando un límite de velocidad de 10 pixeles a la aceleración de la gravedad. Si la aceleración de la gravedad es mayor a la altura de los bloques en el juego, existe la posibilidad de que se atraviese el suelo, lo cual crearía un efecto no deseado en nuestro juego. Limitar la velocidad a la altura de los bloques es un truco sencillo que evita este problema, y en general no suele ser notado por los jugadores que la aceleración deja de crecer tras cierto límite de velocidad.

Ahora toca la acción que es la esencia dentro de los juegos de plataforma: Saltar. Sabemos que debemos esperar a que el botón sea presionado para asignar una velocidad negativa a la aceleración contra el suelo (el impulso), que será el que nos permita elevarnos por los aires hasta que la gravedad nos vuelva a traer de regreso al suelo. ¿Pero como asegurarnos de hacerlo solo cuando estemos tocando el suelo? En realidad resulta tan sencillo como eso: Una variable booleana que nos indique cuando estemos en el suelo, y solo en dado caso, realizar la acción de saltar:
            // Jump
            if (onGround && lastPress === KEY_UP) {
                player.vy = -10;
            }
Así de sencillo es. Para saber cuando estamos en el suelo, agregaremos esta acción al analizar si estamos tocando una pared. Comenzamos por asignar como falsa la variable "onGround", después desplazamos al jugador de acuerdo a su aceleración en el eje y, y revisamos si toca alguna pared (Que también actúan como techo y suelo), si la aceleración es mayor de 0 (está cayendo) y toca una pared, eso significa que está tocando suelo, y por tanto asignamos la variable correspondiente como verdadera:
            // Move player in y
            onGround = false;
            player.y += player.vy;
            for (i = 0, l = wall.length; i < l; i += 1) {
                if (player.intersects(wall[i])) {
                    if (player.vy > 0) {
                        player.bottom = wall[i].top;
                        onGround = true;
                    } else {
                        player.top = wall[i].bottom;
                    }
                    player.vy = 0;
                }
            }
De esta forma ya tenemos aplicada la base para nuestro juego de plataformas. He cambiado la lava en el centro del mapa por paredes, para poder usarles como plataforma en este ejemplo. Ahora podemos saltar de sitio en sitio por todo nuestro escenario con libertad, de igual forma que se hace en todos los juegos de plataformas populares.

Eliminando la repetición de tecla.


Habrás notado que cuando escribes, y dejas presionada una tecla por largo rato, esta se ejecuta de forma continua y repetitiva, permitiendo escribir una misma letra de forma repetida sin tener que teclear múltiples veces dicha tecla. Este efecto también afecta nuestros juegos, y si dejas presionada una tecla por largo rato, verás que la acción ligada se ejecuta de forma continúa. Por ejemplo, en el caso del juego de plataformas, si dejas presionada la tecla "arriba", verás que el personaje vuelve a saltar apenas toca el suelo.
Para eliminar este efecto no deseado en nuestro juego, necesitamos algún truco que prevenga la repetición de la tecla. En realidad, esto es muy sencillo y ya tenemos todo preparado para resolver este problema: En el momento en que una tecla sea presionada, verificaremos que esta no se encuentre en el arreglo de teclas presionadas, y solo en tal caso, asignaremos la última tecla presionada a la variable "lastPress":
    document.addEventListener('keydown', function (evt) {
        if (!pressing[evt.which]) {
            lastPress = evt.which;
        }
        pressing[evt.which] = true;
    }, false);
La variable no volverá a ser falsa hasta que la tecla deje de ser presionada, por lo que cuando este escucha sea disparado por el efecto de "repetición de tecla", esta versión del código prevendrá que dicha repetición afecte a nuestro juego. Ahora que ya sabes de este truco, revisa los juegos que hemos desarrollado anteriormente; verás que algunos de ellos son afectados por la repetición de tecla, pero ahora con este truco, podrás prevenir dicho efecto para que no afecte la jugabilidad de tus juegos.

Código final:

[Canvas not supported by your browser]
/*jslint bitwise: true, es5: true */
(function (window, undefined) {
    'use strict';
    var KEY_ENTER = 13,
        KEY_LEFT = 37,
        KEY_UP = 38,
        KEY_RIGHT = 39,
        KEY_DOWN = 40,
        
        canvas = null,
        ctx = null,
        lastPress = null,
        pressing = [],
        pause = false,
        gameover = true,
        onGround = false,
        worldWidth = 0,
        worldHeight = 0,
        cam = null,
        player = null,
        wall = [],
        lava = [],
        map0 = [
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 2, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 1, 0, 0, 1],
            [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 1, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 1, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
            [1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        ];

    function Camera() {
        this.x = 0;
        this.y = 0;
    }

    Camera.prototype = {
        constructor: Camera,
        
        focus: function (x, y) {
            this.x = x - canvas.width / 2;
            this.y = y - canvas.height / 2;

            if (this.x < 0) {
                this.x = 0;
            } else if (this.x > worldWidth - canvas.width) {
                this.x = worldWidth - canvas.width;
            }
            if (this.y < 0) {
                this.y = 0;
            } else if (this.y > worldHeight - canvas.height) {
                this.y = worldHeight - canvas.height;
            }
        }
    };
    
    function Rectangle2D(x, y, width, height, createFromTopLeft) {
        this.width = (width === undefined) ? 0 : width;
        this.height = (height === undefined) ? this.width : height;
        if (createFromTopLeft) {
            this.left = (x === undefined) ? 0 : x;
            this.top = (y === undefined) ? 0 : y;
        } else {
            this.x = (x === undefined) ? 0 : x;
            this.y = (y === undefined) ? 0 : y;
        }
    }
    
    Rectangle2D.prototype = {
        constructor: Rectangle2D,
        left: 0,
        top: 0,
        width: 0,
        height: 0,
        vx: 0,
        vy: 0,
        
        get x() {
            return this.left + this.width / 2;
        },
        set x(value) {
            this.left = value - this.width / 2;
        },
        
        get y() {
            return this.top + this.height / 2;
        },
        set y(value) {
            this.top = value - this.height / 2;
        },
        
        get right() {
            return this.left + this.width;
        },
        set right(value) {
            this.left = value - this.width;
        },
        
        get bottom() {
            return this.top + this.height;
        },
        set bottom(value) {
            this.top = value - this.height;
        },
        
        intersects: function (rect) {
            if (rect !== undefined) {
                return (this.left < rect.right &&
                    this.right > rect.left &&
                    this.top < rect.bottom &&
                    this.bottom > rect.top);
            }
        },
        
        fill: function (ctx) {
            if (ctx !== undefined) {
                if (cam !== undefined) {
                    ctx.fillRect(this.left - cam.x, this.top - cam.y, this.width, this.height);
                } else {
                    ctx.fillRect(this.left, this.top, this.width, this.height);
                }
            }
        }
    };

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

    document.addEventListener('keyup', function (evt) {
        pressing[evt.which] = false;
    }, false);

    function setMap(map, blockSize) {
        var col = 0,
            row = 0,
            columns = 0,
            rows = 0;
        wall.length = 0;
        lava.length = 0;
        for (row = 0, rows = map.length; row < rows; row += 1) {
            for (col = 0, columns = map[row].length; col < columns; col += 1) {
                if (map[row][col] === 1) {
                    wall.push(new Rectangle2D(col * blockSize, row * blockSize, blockSize, blockSize, true));
                } else if (map[row][col] === 2) {
                    lava.push(new Rectangle2D(col * blockSize, row * blockSize, blockSize, blockSize, true));
                }
            }
        }
        worldWidth = columns * blockSize;
        worldHeight = rows * blockSize;
    }

    function reset() {
        player.left = 40;
        player.top = 40;
        player.vx = 0;
        player.vy = 0;
        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';
        player.fill(ctx, cam);

        // Draw walls
        ctx.fillStyle = '#999';
        for (i = 0, l = wall.length; i < l; i += 1) {
            wall[i].fill(ctx, cam);
        }
        
        // Draw lava
        ctx.fillStyle = '#f00';
        for (i = 0, l = lava.length; i < l; i += 1) {
            lava[i].fill(ctx, cam);
        }

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

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

            // Set vectors
            if (pressing[KEY_RIGHT]) {
                if (player.vx < 10) {
                    player.vx += 1;
                }
            } else if (player.vx > 0) {
                player.vx -= 1;
            }
            if (pressing[KEY_LEFT]) {
                if (player.vx > -10) {
                    player.vx -= 1;
                }
            } else if (player.vx < 0) {
                player.vx += 1;
            }

            // Set gravity
            player.vy += 1;
            if (player.vy > 10) {
                player.vy = 10;
            }

            // Jump
            if (onGround && lastPress === KEY_UP) {
                player.vy = -10;
            }

            // Move player in x
            player.x += player.vx;
            for (i = 0, l = wall.length; i < l; i += 1) {
                if (player.intersects(wall[i])) {
                    if (player.vx > 0) {
                        player.right = wall[i].left;
                    } else {
                        player.left = wall[i].right;
                    }
                    player.vx = 0;
                }
            }
            
            // Move player in y
            onGround = false;
            player.y += player.vy;
            for (i = 0, l = wall.length; i < l; i += 1) {
                if (player.intersects(wall[i])) {
                    if (player.vy > 0) {
                        player.bottom = wall[i].top;
                        onGround = true;
                    } else {
                        player.top = wall[i].bottom;
                    }
                    player.vy = 0;
                }
            }

            // Out Screen
            if (player.x > worldWidth) {
                player.x = 0;
            }
            if (player.x < 0) {
                player.x = worldWidth;
            }

            // Bellow world
            if (player.y > worldHeight) {
                gameover = true;
                pause = true;
            }

            // Player Intersects Lava
            for (i = 0, l = lava.length; i < l; i += 1) {
                if (player.intersects(lava[i])) {
                    gameover = true;
                    pause = true;
                }
            }

            // Focus player
            cam.focus(player.x, player.y);
        }
        
        // Pause/Unpause
        if (lastPress === KEY_ENTER) {
            pause = !pause;
        }
    }

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

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

        lastPress = null;
    }

    function init() {
        // Get canvas and context
        canvas = document.getElementById('canvas');
        ctx = canvas.getContext('2d');
        canvas.width = 300;
        canvas.height = 200;
        worldWidth = canvas.width;
        worldHeight = canvas.height;

        // Create camera and player
        cam = new Camera();
        player = new Rectangle2D(40, 40, 10, 10, true);
        
        // Set initial map
        setMap(map0, 10);
        
        // Start game
        run();
        repaint();
    }

    window.addEventListener('load', init, false);
}(window));
Regresar al índice

31 comentarios:

  1. ESTO EJEMPLOS SON EL IMPULSO PARA DECIDIRSE A CREAR JUEGOS, Y TODO ERA MUY LINDO CUANDO LO EMPECE HACER ASI JJJ, PERO LLEGO EL MOMENTO DE ALGO RELATIVAMENTE GRANDE Y SI LA VIDA NO ES TAN FACIL JEJE, CODIGO Y RUTINAS
    DIFERENTES, SER DISEÑADOR Y PROGRAMADOR PERO A PESAR DE TODO ESTO LO MEJOR DEL MUNDO. :)

    CUAL OPCION CREES QUE SERIA MEJOR CREAR UN ARRAY NORMAL COMO LO HACES O UN ARRAY DENTRO DE UN ARRAY DIGAMOS A=[[0,0,0,]]?

    ResponderEliminar
    Respuestas
    1. El array anidado (El que pones en el ejemplo), de más popular y quizá mas sencillo de mantener, pues no debes preocuparte por conocer las columnas para enviarlas en el constructor. Sin embargo, con el tiempo descubrí que el arreglo sencillo es más fácil de mantener y modificar, además que ocupa menos memoria, y con algunos trucos, te permite técnicas avanzadas en proyectos complejos, pero estos son casos muy especiales que me han tocado.

      Otra ventaja de los arreglos anidados es que te permite usar columnas de valores variables, pero hasta ahora, no he conocido proyecto que se beneficie de ello.

      Al final, es cuestión del programador de elegir la técnica con la que mejor se sienta cómodo.

      Eliminar
  2. LO DIGO PORQUE ASI LO ESTOY HACIENDO DIVIDO LA DIFICULTAD DE LOS NIVELES DENTRO DE OTRO ARRAY:
    NIVELES= [
    1-2FACIL
    [[0,0,0,0,0,0]
    [0,0,0,0,0,0,]]
    3-4MEDIO
    [[0,0,0,0,0,0,]
    [0,0,0,0,0,0,]]
    ];

    ResponderEliminar
    Respuestas
    1. ¿Y que te impide hacerlo así con el método que les he enseñado?

      NIVELES= [
      //1-2FACIL
      [0,0,0,0,0,0,
      0,0,0,0,0,0],
      //3-4MEDIO
      [0,0,0,0,0,0,
      0,0,0,0,0,0]
      ];

      Eliminar
  3. JE SI ES QUE ESTABA HACIENDO UNA COMPARACION DE LAS DOS TAMAÑO Y LA FACILIDAD PARA ACCEDER A ELLA.
    COMO TU LO HACES SOLO HAY DOS NIVELES JJJ.

    ResponderEliminar
  4. Buenas, me gustaria saber si podrias subir un tutorial sobre como hacer que los disparos reboten.. como en un juego de billar.

    ResponderEliminar
    Respuestas
    1. Si tu interés es de rebote sobre sobre paredes y objetos cuadrados, es tan fácil como multiplicar por -1 el vector donde ha colisionado. Si quieres rebotar círculos con otros círculos, entonces ya requieres de una fórmula un tanto más compleja para calcular el rebote correcto.

      Dime que clase de rebote necesitas, si deseas que te ayude con un ejemplo más claro. ¡Felices códigos!

      Eliminar
  5. necesito rebotar circulos contra circulos y circulos contra arcos.. rebotando sobre ellos o siguiendo su curso, estoy desarrollando en juego al estilo PEGGLE de pop cap games agregadeceria que me ayudes con un ejemplo

    ResponderEliminar
    Respuestas
    1. o por ejemplo el rebote de una nave(con velocidad y angulo x) estrellándose contra la cara externa del circulo o contra la cara interna del circulo

      Eliminar
    2. El nuevo ángulo de la colisión entre dos círculos, es iguak al ángulo del centro del otro círculo al centro del mismo al momento del impacto. ¿Era esta tu duda respecto al rebote?

      Eliminar
    3. no me queda del todo claro, supogamos que tengo 2 circulos, A y B.
      B esta quieto y A choca contra el, hacia donde ira A y hacia donde ira B?
      Supongamos que A se encuentra inicialmente en x=50,y=50 del canvas y B en x=200,Y=180 del canvas

      Eliminar
    4. Depende del ángulo con el que le pegue. Si le da directo, toda su fuerza pasa al siguiente circulo en el mismo ángulo, en cambio, si le da de lado, la fuerza y desviación es proporcional al ángulo con el que le ha pegado.

      Esto es mas propiedades de física mas que lógica de videojuegos, si quieres conocerle a mas detalle, quizá te gustaría buscarle en Internet por este rumbo.

      Eliminar
  6. Amigo muy buenos tus tutoriales, yo te tengo una pregunta acerca del salto y caer sobre una plataforma, lo que pasa es que intento hacer que el personaje realice esta accion pero que tenga un movimiento no acelerado sino constante, ademas lo estoy haciendo directamente en android, entonces cuando reconozco que toco la pantalla en los lados donde estan los distintos botones(derecha, izquierda y salto) lo que hago es guardar un booleano para cada uno asi se cuando se toca y cuando no cada uno de esos botones, el problema no esta en el caminar puesto que tanto derecha como izquierda corre perfecto, sin embargo en el salto voy aumentando la posicion en x del sprite hasta llegar a sierto sprite de la animacion donde empiezo a disminuir el x, y el salto parece perfecto sin embargo cuando voy a hacer que salte sobre una plataforma no logro hacerlo, puedo hacer que si camina choque contra la plataforma y no la sobrepase pero si la salto una vez que toco la plataforma este se devuelve, y no he logrado solucionar esto, para comprobar esto lo hago de la siguiente manera
    Si mi personaje choca con el objeto{
    si personaje.y <= plataforma.y+plataforma.heigth y salto {
    personaje.y = plataforma.y+plataforma.heigth
    salto = false
    }
    }else{
    codigo de colision al caminar
    }

    Entonces no se que me recomendarias hacer o si me puedes aconsejar, de antemano perdon por el gran comentario pero realmente me ayudarias mucho si tienes algun consejo.

    Gracias.

    ResponderEliminar
    Respuestas
    1. Para empezar, ¿Estas usando JavaScript u otro lenguaje? y después, ¿estás localizando un techo o una plataforma?

      Por que di estas usando JavaScript e intentas detectar una plataforma, lo estado haciendo al reves (player.y<platform.y-player.y).

      El código como está, funcionaría en un código 3D de coordenadas invertidas, o si intenta detectar un techo.

      Avisame si iba por aquí el problema. ¡Suerte!

      Eliminar
    2. Estoy trabajando en android, con andengine, mi problema es como en mario que al caminar puede saltar sobre una plataforma que se encuentra en el aire o sobre una montaña, etc, el juego es en 2d y necesito calcular es la parte de arriba de la plataforma donde se supone debe caer el personaje.

      Eliminar
    3. Estuve estudiando a detalle tu código, y habiendo leído que AndEngine usa un sistema de coordenadas 3D (inverso al 2D clásico como el que udamos en este tutorial) llegué a la conclusión que estaba correcto... O casi.

      El detalle es que en el momento que el personaje colisiona con el objeto, su valor ya no es menor al de la parte superior del objeto, por el que la primer condición nunca llega a ser verdadera.

      Te aconsejo por ello que revises primero el desplazamiento en x y luego en y, como en el ejemplo de esta entrega, para verificar si el jugador entró en contacto con el durante su caída o no.

      Eliminar
  7. oye, podrias hacerlo pero con tiro parabolico, creo q seria mas real, no, es que asi solo se ejecuta en y, pero uno puede saltar describiendo movimientos hacia arriba y hacia adelante, no ?

    ResponderEliminar
    Respuestas
    1. Si saltas mientras te estás moviendo hacia adelante, de hecho estás dentro de un movimiento parabólico. Puedes aplicar esta misma técnica con una velocidad constante en x mas la gravedad en y si deseas realizar un tiro parabólico en forma.

      Eliminar
  8. Hola! están excelentes los tutoriales!.. Por qué no siguieron los de plataformas?

    Saludos!

    ResponderEliminar
    Respuestas
    1. ¡Está en desarrollo! Desafortunadamente han salido otros temas que es mejor ver antes, y he tenido proyectos que me absorben el tiempo, lo que me ha retrasado en poder actualizar el blog, ¡Pero ten por seguro que el tema continuará para cubrir todos los detalles de este género de videojuegos!

      De igual forma, estoy seguro que con esta base de las físicas que lleva este género, la gente ya tiene las herramientas necesarias para poder crear uno. Sí estás desarrollando uno y te surgen dudas al respecto, no dudes en preguntar, con gusto te ayudaré en lo posible.

      ¡Muchas gracias por el apoyo, y suerte con tus proyectos! ¡Felices códigos!

      Eliminar
    2. Agradezco tu disposición Karl. Cada día haces más posible mi sueño de crear juegos!

      Saludos y éxito en todo!

      Eliminar
  9. este tutorial es super bueno, ya podemos hacer nuestro propio Mario Bros XD, estoy ansioso por el siguiente tutorial.

    ResponderEliminar
  10. ¿Como se puede hacer, para añadirle una animacion de sprites cuando se mueve el jugador, pero que al detenerse cambie de esprite?

    ResponderEliminar
    Respuestas
    1. Ese tema fue visto con anterioridad en el tema Animación Condicional: http://juegos.canvas.ninja/2013/12/animacion-condicional.html

      Eliminar
  11. que tal amigo, tus tutoriales realmente bueno enseñando aspectos muy importante en el desarrollo de videojuegos, de hecho me ha ayudado mucho en algunas pruebas de distintas funcionalidades que he hecho, pero tengo una duda en esto de las plataformas algo que aun no se ha abordado y es el ataque, puesto que no se bien como ejecutar un ataque es decir por ejemplo tengo una serie de sprites de ataques por decir algo patada y ataque con un cuchillo, entonces en el momento del ataque estos tienen distintos rangos de daño hacia el enemigo y los tamaños de los sprites son distintos, entonces estaria bien por ejemplo para que saber si se esta atacando poner una variable que me identifique si se esta atacando para asi el ataque se produzca solo cuando el jugador activo el ataque y no siempre que colisione, tambien poner otra variable que me indique que ataque fue y de esta manera saber que ataque fue y asi determinar cuanta cantidad de daño debe resivir el enemigo, podria ser algo asi? no se que te parezca y si me puedas dar algunos consejos en este asunto. Muchas Gracias.

    ResponderEliminar
    Respuestas
    1. ¡Me parece que lo tienes muy bien definido! ¡Muchas felicidades!

      Así como lo dices, la mejor forma de revisar la clase de ataque ejecutándose, seria a través de variables que ayuden a detectar dicha condicional.

      Ya que hablas de varios tipos de ataque, posiblemente la mejor forma de manejar esto sería con una variable numérica, donde cero sea ningún ataque en este momento, y los valores siguientes representen cada uno un tipo de ataque.

      ¡Suerte! ¡Felices códigos!

      Eliminar
    2. bien, gracias es bueno saber que no ando por tan mal camino jejeje, sin embargo te tengo otra preguntica como podria hacerse la parte de encadenar golpes en esa parte si ando un poco confuso, que me recomiendas?.

      Eliminar
    3. ¿Que clase de funcionamiento tienes para esta cadena de ataques? Puede ser muy variado desde un simple contador de combo, hasta usar diferentes animaciones o cambiar el tipo de daño.

      De igual forma, para creo que cualquiera de las opciones en la cadena, requerirás un pequeño contador de tiempo que indique si el nuevo golpe ha sido efectuado como consecuente del anterior, o esta cadena debe reiniciar desde cero. Y posiblemente un contador de cadena, que indique cuantos golpes consecutivos han ocurrido desde el inicio de esta, y realizar las acciones determinadas para dicho estado.

      Eliminar
  12. hola como estas?, desde ya quiero decir que te felicito por tu blog, muy completo y bien explicado.
    Tengo una duda con respecto a este tema de salto de plataforma. Se como funciona, pero no entiendo porque funciona, paso a explicar:
    suponiendo que vengo en caida apretando la tecla derecha, y en el momento de chocar con el objeto aterrizo en la punta derecha de la plataforma, en el momento que entre en contacto con el objeto, como la velocidad vx es mayor a 0(esta apretando la tecla derecha), me tendria que mandar direcatamente al lado izquierdo de la plataforma:
    if (player.vx > 0){
    player.right = wall[i].left;
    }
    pero sin embargo no lo hace y no entiendo porque.
    Hago unas aclaraciones:
    No tengo el mismo codigo que el tuyo. Estaba haciendo mi propio juego de plataformas, me trabe en este tema, y me cruze de casualidad con tu blog. Tengo la misma funcion para la gravedad, la misma condicion de contacto con el objeto, lo unico que no tengo igual es una velocidad para x(se mueve de manera constaste)

    ResponderEliminar
    Respuestas
    1. ¡Hola Ramiro! Gracias por tu comentario.

      No sé si la lógica completa sea idéntica o no, pero en el caso de este ejemplo (Y tal vez igual en tu código), lo que ocurre es que se evalúan las dos direcciones por aparte: Primero se mueve en el eje X, y si existen colisiones, significa que son paredes, así que actúa con respecto a esta forma. Posteriormente se mueve en Y, y si existen colisiones en este punto, significa que es suelo o techo, y por ello prosigue de forma distinta. Es de esta forma que puede detectar sin conflictos si está colisionando contra pared o suelo, sin hacer efectos no deseados en su proceso.

      Espero esto solucione tu duda.

      Eliminar