Aprende a crear juegos en HTML5 Canvas

lunes, 25 de marzo de 2013

Mapas de mosaico

Los mapas de mosaico (Tile Maps en inglés), permiten crear escenarios de forma relativamente fácil. Suelen ser leídos de mapas de bits o de arreglos de valores (Númericos en el mayor de los casos), en donde cada valor distinto representa un tipo diferente de elemento a agregar en el mapa.

Un ejemplo de un mapa de mosaico sencillo de 4 bloques de ancho por 3 de alto, se representaría en Javascript de la siguiente forma:
var map0=[
    [1, 0, 0, 1],
    [0, 0, 0, 0],
    [1, 0, 0, 1]
];
El el ejemplo anterior, los 0 representan espacios en blanco, y los 1 representan paredes. Así pues, estaríamos creando un mapa de 4x3 con una pared en cada esquina.

Por supuesto, un mapa para nuestros juegos no pueden ser tan pequeño. Tomando en cuenta que estamos haciendo juegos con elementos de 10x10 pixeles, en un escenario de 300x200 pixeles, necesitaríamos crear un mapa de mosaico de 30x20 elementos.

Llevemos entonces a práctica lo anteriormente dicho. Comencemos por declarar un arreglo para cada tipo de elemento que vaya a existir en nuestro mapa de mosaico. En estos mapas, podemos agregar toda clase de elementos: Paredes, obstáculos, enemigos, objetos, etc... Para este práctico ejemplo, mostraré solo dos objetos con los cuales interactuar: Paredes y Lava.
    var 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, 2, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 2, 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]
        ];
He agregado también el mapa de mosaico que usaremos para este ejemplo. Si los cuentas, confirmarás que son 30 valores a lo ancho, y 20 a lo alto. Puedes modificar los valores dentro de este mapa para personalizar tu juego.

Ahora, crearemos una función que lea los valores del mapa. En la primer línea recibimos los valores del mapa de mosaico a leer y el tamaño que tendrá cada bloque (que son de 10 pixeles).
    function setMap(map, blockSize) {
Si quisiéramos usar objetos rectángulares en lugar de cuadrados, podríamos recibir blockWidth y blockHeight. Pero dado que en la mayoría de los juegos, los objetos suelen tener igual proporción, dejaremos así la función por ahora.

Ahora que queda esto explicado, proseguimos con nuestra función. En las primeras líneas, crearemos las variables col y row que nos permitirán conocer nuestra posición actual, las variables columns y rows que nos ayudarán a manejar el total de filas y columnas en el mapa, y limpiaremos los arreglos que usaremos antes de volverlos a llenar:
        var col = 0,
            row = 0,
            columns = 0,
            rows = 0;
        wall.length = 0;
        lava.length = 0;
A continuación, leeremos cada uno de los valores en nuestro mapa de mosaico a través de un "for anidado", leyendo con el primero cada fila, y con el segundo cada columna dentro de la fila correspondiente. Si el valor de la posición actual en el arreglo del mapa corresponde a alguno de los elementos a agregar, lo empujamos al arreglo correspondiente en la posición que le corresponde. Noten que los ceros son simplemente ignorados:
        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));
                }
            }
        }
Con esta función, mandaremos a llamar a nuestro mapa al cargar el juego dentro de la función "init":
        // Set map
        setMap(map0, 10);
De esta forma, leeremos todos los elementos de nuestro mapa de mosaico. Ahora que hemos concluido, solo debo recordarte que no olvides llamar a dibujar los elementos en la función paint:
        // Draw walls
        ctx.fillStyle = '#999';
        for (i = 0; i < wall.length; i += 1) {
            wall[i].fill(ctx);
        }
        
        // Draw lava
        ctx.fillStyle = '#f00';
        for (i = 0; i < lava.length; i += 1) {
            lava[i].fill(ctx);
        }
Y agregar las acciones correspondientes a los elementos en el flujo de juego. El código que tomamos de la entrada pasada ya tiene la interacción con las paredes, así que solo resta agregar la intersección del personaje con la lava:
            // Player Intersects Lava
            for (i = 0; i < lava.length; i += 1) {
                if (player.intersects(lava[i])) {
                    gameover = true;
                    pause = true;
                }
            }
De esta forma, podremos crear con facilidad juegos en el futuro basados en mapas de mosaico.

Codigo 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,
        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, 2, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 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, 2, 2, 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 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,
        
        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) {
                ctx.fillRect(this.left, this.top, this.width, this.height);
            }
        }
    };

    document.addEventListener('keydown', function (evt) {
        lastPress = evt.which;
        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));
                }
            }
        }
    }

    function reset() {
        player.left = 40;
        player.top = 40;
        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);
        
        // Draw walls
        ctx.fillStyle = '#999';
        for (i = 0; i < wall.length; i += 1) {
            wall[i].fill(ctx);
        }
        
        // Draw lava
        ctx.fillStyle = '#f00';
        for (i = 0; i < lava.length; i += 1) {
            lava[i].fill(ctx);
        }

        // 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(deltaTime) {
        var i = 0,
            l = 0;
        
        if (!pause) {
            // GameOver Reset
            if (gameover) {
                reset();
            }

            // Move Rect
            if (pressing[KEY_UP]) {
                player.y -= 5;
                for (i = 0; i < wall.length; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.top = wall[i].bottom;
                    }
                }
            }
            if (pressing[KEY_RIGHT]) {
                player.x += 5;
                for (i = 0; i < wall.length; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.right = wall[i].left;
                    }
                }
            }
            if (pressing[KEY_DOWN]) {
                player.y += 5;
                for (i = 0; i < wall.length; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.bottom = wall[i].top;
                    }
                }
            }
            if (pressing[KEY_LEFT]) {
                player.x -= 5;
                for (i = 0; i < wall.length; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.left = wall[i].right;
                    }
                }
            }

            // 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;
            }

            // Player Intersects Lava
            for (i = 0; i < lava.length; i += 1) {
                if (player.intersects(lava[i])) {
                    gameover = true;
                    pause = true;
                }
            }
        }
        // Pause/Unpause
        if (lastPress === KEY_ENTER) {
            pause = !pause;
            lastPress = null;
        }
    }

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

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

    function init() {
        // Get canvas and context
        canvas = document.getElementById('canvas');
        ctx = canvas.getContext('2d');
        canvas.width = 300;
        canvas.height = 200;
        
        // Create player
        player = new Rectangle2D(40, 40, 10, 10, true);

        // Set map
        setMap(map0, 10);
        
        // Start game
        run();
        repaint();
    }

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

40 comentarios:

  1. No tenía ni idea de esto, llego a saberlo hace unos años me hubiera ahorrado muchíiiiisimas horas de trabajo calculando manualmente los px que tengo que colocar en tal posición.

    Me ha encantado amigo ^^ , como siga a este ritmo de aquí a fin de verano me habré echo un jueguecillo más que decente jaja

    Pd: Como sugerencia creo que sería más fácil utilizar array bidimensional para fila y columna y nos ahorraríamos este algoritmo:

    col++
    if(col>=columns){
    row++;
    col=0;
    }
    }
    }

    Saludos!

    ResponderEliminar
    Respuestas
    1. ¡Me alegro esté siendo de mucha ayuda para ti!

      Lo del arreglo bidimensional, eso usaba originalmente, pero descubrí es mucho más sencillo y menos propenso a errores fatales el usar esta clase de arreglo, así que este algoritmo facilita la tarea de su creación y mantenimiento.

      Personalmente usaba un algoritmo más simplificado que involucraba módulos, pero lo consideré muy complicado para la gente en general, y por eso hice esta versión poco más larga, pero sencilla de comprender.

      Por supuesto, si te sientes más cómodo con arreglos bidimensionales, es cuestión de gustos, y lo que sientas mejor para el desarrollo de tus creaciones. Éxito ;)

      Eliminar
    2. ¿Cuales son los peligros de los arrays bidimensionales, me puedes explicar?, aparentemente son una solución ideal no requieren de ningún condicional ni variable extra.

      Eliminar
    3. Dado que requiere un forma anidado para leerlo, mucha gente no toma en cuenta las excepciones posibles en caso que una fila sea más corta o larga de lo deseado, creando errores de objeto no encontrado cuando se intenta usar los valores faltantes. Pero so haces uso adecuado de estos arreglos, no deberías tener problema alguno.

      Eliminar
    4. Jaja, bueno muchas gracias, me ha quedado claro, también se podría usar el operador modulo como me enseñaste en el capitulo de los sprites...

      Eliminar
    5. Así es como lo hago yo personalmente, tal como le comentaba a Dryken. Pero preferí usar esta versión poco más larga pero comprensiva para que todo el mundo pudiera entenderla con facilidad.

      Eliminar
  2. Te hago una consulta con el tema del mapa.Estoy usando la misma función para definir tanto los bloques, pero en vez de agregar lava, agrego cajas que me gustaría que desaparezcan al colisionar con otro elemento. Te dejo el código que había armado para que se vea mejor la problemática.

    Link : http://www.antworksdesign.com/clientes/game/boom.js

    Gracias por tu tiempo.

    ResponderEliminar
    Respuestas
    1. ¡Me he divertido bastante con tu código! Resolver el problema de la intersección entre ambos elementos ha sido un reto, pero tras varios minutos, ¡Finalmente he llegado al origen del problema!

      Analicemos esto un minuto: Tenemos Rectángulos que colisionan con Rectángulos mediante Intersect (x,y,width,height)... Tenemos Círculos que colisionan con Círculos mediante Distance (x,y,radius). Ahora, intentas colisionar un Rectángulo con un Círculo, el cual uno no tiene Ancho ni Alto, y el otro no tiene Radio... ¿¡Cómo harías para que ambos pueda interactuar entre ellos entonces!?

      Dejaré que intentes llegar por la conclusión por ti mismo. Escríbeme la solución a la que llegues, o si aun no puedes lograrlo, avísame y te daré la mía. ¡Suerte!

      Eliminar
  3. Excelente tutorial, lo poquito que se sobre videojuegos en lo he sacado de tu blog : ) .
    Oye, cuando movemos nuestro rectangulo, se comprueba en un 'for' cuatro veces para posicionarnos después de colisionar, pero, no seria mejor guardar en unas variables nuestra posición antes de oprimir cualquier tecla (las flechas) y al ultimo hacer un solo ciclo, y en caso de que colisione se regresa a la antigua posición?. Creo, no lo he probado, no se si se haga mas rápido o si simplemente no sirva lo que digo jajajaj.

    Sale, se agradece estos tutoriales.

    ResponderEliminar
    Respuestas
    1. Aunque sean cuatro "for", solo se ejecuta uno por ciclo si acaso hubo movimiento, o ninguno en caso contrario, por lo que en este caso, resulta mas optimizado que en la sugerencia que dices. Además... ¿Como sabes si debes regresas en x o y en el caso que mencionas?

      De igual forma, te has dado cuenta que hay muchas posibles soluciones a un mismo problema, y en algunos casos, unas son mejores que otras, como veremos mas tarde. ¡Felices códigos!

      Eliminar
  4. con la ayuda de tus tutoriales y varias horas rompiendome el coco jeje creo que pude avanzar bastante en mi pequeño proyecto.. pero creo que no logro implementar correctamente esta tecnica de mapa en mosaico cuando los objetos a pintar tienen distintos tamaños.. podrias subir un ejemplo de como hacer esto pintando distintas imagenes en lugar de rectangulos?
    Este es mi pequeño proyecto jeje probablemente sea demaaasiadoo pedir pero si algun dia tienes tiempo y ganas agradeceria le hecharas un ojo y me dieras una solucion o me comentaras en que puedo mejorar, si no puedes igual agradesco muchisimo toda la informacion que publicas en este blog.

    PD: escribo tanto aca por que no se como enviarte un mensaje privado.

    https://mega.co.nz/#!8J00DBZK!sAuZqt9_T4i89gFBpQiQmHP3VskaedtqhF7P3VMdpr0

    ResponderEliminar
    Respuestas
    1. ¡Ah! ¡Estas tratanto de implementar un RPG!

      Pero verás, el secreto de los objetos grandes en los RPG clásicos, es que en realidad se tratan de grupos de objetos pequeños, tal como se puede ver en esta imagen: http://actiongames.coltgames.com/rmvx_pic02_full.png

      Ve si esto te da una idea mas clara de como diseñar tu mapa. Si aun no estas seguro, avisame y veré como puedo ayudarte mejor.

      ¡Suerte con tu proyecto!

      Eliminar
    2. aaa gracias por reponder, no lo habia pensado... entonces tengo que crear el mapa como un imagen grande y luego recortarlo en partes iguales??? ,y si es asi ,como hago para pintarlo por medio de un vector? (map1)...
      Es esta la forma mas optima de hacerlo? no me queda muy claro, me gustaria que pudieras ayudarme

      PD: algun consejo para poder mejorar el codigo, hacerlo mas optimo o algo..?? habia pensado crear una clase base de la que hereden las demas.. por que todos(o la mayoria) de los objetos tienen posX,posY,funcion Draw, funcion Intersect, etc...

      Eliminar
    3. Cada elemento tiene su valor. Por ejemplo, cada bloque de pasto es 0, los troncos que parecen de dos, serían 1 y 2 los horizontales, 3 y 4 los verticales, la casa pequeña que está en 6 partes seria el techo 5, 6 y 7 y para el suelo 8, 9, y 10, y así con los demás elementos. Al final pones todo en el vector de mapa para ensamblarlo tal como quieres verlo.

      Sobre la herencia, si múltiples objetos tienen la mayor parte de sus métodos iguales, es muy buena idea considerarle. Recuerda que en JavaScript esto se consigue a través de prototipos.

      ¡Suerte en tu código!

      Eliminar
    4. claro, eso es lo que habia pensado.. pero pareciera ser que no es una forma muy practica, digamos, parece dificil de modificar y de armar.. pero lo voy a intentar.
      Deberia armar el mapa como una imagen grande con photoshop o adobe fireworks y luego ir cortandola para luego ensamblarla por codigo??? lo que me resuslta mas dificil hasta ahora es encontrar los recursos necesarios jeje ya que no tengo idea de como hacer imagenes... no conoceras algun lugar para descargarlos y usar free?? o algun programa? el que se ve en la imagen ( http://actiongames.coltgames.com/rmvx_pic02_full.png ) que pusiste se ve bastante bueno, por lo menos proporcionas los tiles (segun parece).

      Desde ya muchas gracias

      Eliminar
    5. Es la forma en que se hacía originalmente. Quizá ya pueda hacerse de otras formas mas "sencillas", pero que podrían ser complicadas en otros aspectos.

      La idea es no hacer todo el mapa, si no los bloques que sabes que se repetiran, como en esta imagen: http://i1238.photobucket.com/albums/ff481/hanzokimura/HanzoGraphics/Hanzo-TownSet02VS.png . Después la ensamblas en map1 asignando un numeró a cada tile.

      ¿Que clase de recursos estas buscando, por cierto? ¿Para armar el mapa? Se que existen varios, pero no se precisamente de alguno que pudiera recomendarte.

      Eliminar
    6. tratando de implementar lo que dijiste pude aclarar mis dudas y logre un avance.. pero no estoy seguro si lo estoy haciendo bien xq me surgio un problema. cuando encuentro en 1 pinto el pasto cuando encuentro otro numero pinto una parte del objeto.. pero si el objeto tiene orificios o es transparente debajo de el se ve el fondo negro (xq no pinto el pasto) como puedo arreglar esto? creando un array para la superficie y otro para los objetos??? o como, no se si me explico bien

      Eliminar
    7. o deberia editar directamente la imagen incrustandole el fondo que corresponde?.. para q no trasparente y no aparescan partes negras

      Eliminar
    8. todos los recursos necesarios para esto tipo de juego.. personajes.. poderes.. fondos como pasto agua lava arena.. arboles arbustos.. animales, sonidos, etc lo que mas necesito en este momento serian paneles para lo que serian los mensajes ,el inventario del personaje ,slots para los poderes, etc.. trate de hacerlos pero definitivamente no es lo mio jeje me quedaron bastante repulsivos

      Eliminar
    9. La forma clásica y lo mas sencillo, sería poner los objetos sin fondo transparente, si sabes que siempre irán sobre pasto como en este caso. Si existe la posibilidad que uses los mismos elementos en distintos terrenos, quizá si convenga más armarlos en dos capas como fue tu sugerencia.

      Sobre los recursos, no tengo un sitio en específico que pueda recomendarte, pero si buscas en Internet, encontraras muchos recursos gratuitos que puedes usar en tus juegos (Son mayores las posibilidades si lo buscas en ingles).

      Eliminar
    10. muchas gracias por aclarar mis dudas jeje.. ya solucione la mayoria de mis problemas... pero ahora una nueva piedra aparece en mi camino. comenc a atulizar el mouse para mover el personaje pero no se como hacer para que vaya a la casilla donde hice click.. caminando y respetando las casillas :/ espero sepas aconsejarme en esto tambien, desde ya muchas gracias...

      Eliminar
    11. Vaya... Mapas con ratón. ¡Que gran reto te has metido!

      Para empezar, ¿Estas usando camaras? Es muy importante que tomes en cuenta este factor, ya que el ratón es relativo a la pantalla, no a la cámara...

      Eliminar
    12. si, utilizo una camara. pero pense que no presentaria problema si sumo X e Y del raton con X e Y de la camara..
      comenc a pensar en algo pero me parece que no llegare a ningun lado..
      pense en obtener la posicion del personaje al hacer click.. tomar las direcciones en las que puede moverse, elejir el que este mas cerca del objetivo y guardar su direccion.. luego en base a esa posicion volver a tomar las direcciones en las que puedo moverme y asi sucesivamente..
      conoces de alguna forma mejor de hacerlo? gracias

      Eliminar
    13. Bueno, si estás sumando la cámara, debería darte el valor de la posicion real que deseas, pero viendo que no es así, significa que intentas algo mas complicado de lo que estas explicando, o al menos no me queda clara tu intension. ¿Hay alguna forma en que puedas mostrarme lo que haces y lo que deseas hacer para asi ayudarte?

      Eliminar
    14. Acabo de leer con mayor detenimiento, y creo que lo que deseas hacer es un path-finding; ¿algo como esto? http://qiao.github.io/PathFinding.js/visual/

      Eliminar
    15. si exactamente! eso era justo lo que necesito! encontrar el camino mas optimo entre 2 puntos y hacer que el personaje se mueva.. quieres que suba mis codigos para que los veas?? gracias por tu ayuda.

      Eliminar
    16. creo que seria mejor para mi caso implementar el algoritmo A*.. veo que el ejemplo que me pasaste esta hecho con JQuery =/

      Eliminar
    17. Si buscas en Internet el algoritmo, hay muchos resultados que te enseñan la teoría para implementarlo. Además, ya hay varias implementaciones de pathfinder para javascript además del ejemplo que te mostré. Seguro habrá al menos alguna de ellas que sirva.

      Eliminar
    18. Conoceis la libreria rpg js v2 para crear juegos de ese tipo? lo he estado mirando pero no consigo entender como funciona. Creo que implementa los mapas con json. Estaria bien algun tuto de este tipo de juego, no se si es mejor empezar de cero o usar eso ya que parace estar muy bien pensado y optimizado.Que pensais? Un saludo.

      Eliminar
    19. La librería no la he revisado a fondo, por lo que no he podido ver si cubre los aspectos que necesitas para el juego que desarrollas.

      Sobre decidir si usar una librería o hacerlo de cero, definitivamente una buena librería te ahorraría mucho tiempo y dolores de cabeza, pero modificar funcionalidades para una tarea personalizada, puede ser demasiado complejo. Por el contrario, hacerlo de cero te permitirá controlar y optimizar el juego tanto como lo desees. Es en realidad cuestión de poner cada caso en la balanza para decidir cual es mejor para dicho proyecto, de acuerdo a tus necesidades.

      Mucha suerte, ¡Y felices códigos!

      Eliminar
  5. Saludo yo quiero saber por que a mi no se me visualiza el mapa de tu ejemplo

    ResponderEliminar
    Respuestas
    1. ¿Que navegador estás usando? ¿Revisaste ya la consola de Javascript?

      Eliminar
    2. Que bueno que responde de una ces eso quiere decir que esta atento a la web...
      Yo estoy usando el chrome, yo apena estoy empesando a programar no se mucho de javascript pero creo que me se algunos concepto basico yo ya eh hecho algunas cosita practicando pero mire la forma en la que hace los mapa y me parecii muy interesantr y mas facil al a estar creando bloque por bloque manualmente pero intento instegrarlo y no me resulta... como te puedo enviar un codigo para que vea si el mapa esta bien o no y gracias por los tutoruales son muy bueno y ayudan a mejorar las ideas que tenemos sigue asi que te apoyo

      Eliminar
    3. window.onload = function(){
      //variables globales
      var canvas = document.getElementById('canvas');
      var gameloop = setInterval(main, 10);
      var cWidth = document.getElementById('canvas').width;
      var cHeight = document.getElementById('canvas').height;
      var mouse = { posX: cWidth / 2, posY: cHeight / 2};
      var ctx = null;
      var wall = [];
      var lava = [];
      var 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,2,2,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,2,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,2,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,2,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,2,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,2,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,2,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,2,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,2,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,2,2,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 setMap(map, columns, blockSize){
      var col = 0;
      var row = 0;
      wall.length = 0;
      lava.length = 0;
      for(var i = 0; i < map.length; i++){
      if(map[i] == 1){
      wall.push(new Rectangle(col*blockSize, row*blockSize, blockSize, blockSize));
      }else if(map[i] == 2){
      lava.push(new Rectangle(col*blockSize, row*blockSize, blockSize, blockSize));
      col++;
      }
      if(col >= columns){
      row++;
      col = 0;
      }
      }
      }

      document.addEventListener('mousemove',function(evt){
      mouse.posX=evt.pageX-canvas.offsetLeft;
      mouse.posY=evt.pageY-canvas.offsetTop;
      },false);

      if(canvas && canvas.getContext){
      var ctx = canvas.getContext('2d');
      if(ctx){
      cbackground(ctx);
      }
      }

      function cbackground(ctx){
      //pintando el mapa
      ctx.save();
      ctx.fillStyle = '#999';
      for(var i = 0; i < wall.length; i++){
      wall[i].fill(ctx);
      }
      ctx.restore();

      ctx.save();
      ctx.fillStyle = '#f00';
      for(var i = 0; i < lava.length; i++){
      lava[i].fill(ctx);
      }
      ctx.restore();

      //pintando fondo del canvas
      ctx.save();
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, cWidth, cHeight);
      ctx.restore();

      ctx.save();
      ctx.strokeStyle = 'black';
      ctx.lineWidth = 2;
      ctx.strokeRect(0, 0, cWidth, cHeight);
      ctx.restore();

      //pintando el puntero
      ctx.save();
      ctx.beginPath();
      ctx.fillStyle = 'blue';
      ctx.arc(mouse.posX,mouse.posY, 5, 0, 360, true);
      ctx.stroke();
      ctx.restore();
      }

      //gameloop - function of actualization for game
      function main(){
      cbackground(ctx);
      setMap(map0, 30, 10);
      }

      };

      Eliminar
    4. Antes que nada, ¿Revisaste la consola de Javascript (F12)? ¿No te reporta nada?

      A simple vista... ¿Si incluiste la función Rectangle? ¿Donde mandaste a llamar la función main? Y posiblemente debas dibujar cbackground después de setMap, o estarías dibujando un mala vacío. Avisame si alguno de estos era el problema que tenías.

      Eliminar
    5. No eh podido revisar la consola por que es que estoy editando desde mi cel... aparte de eso lo eh tratado todo, eh hecho los paso que me dijiste, la funcion rectangle no la puedo meter en donde estoy asiendo la llamada de main por que a main la estoy llamando desde un setInterval () pero luego de que me hablaste de la funcion rectangle me percate de que no la avia agregado al codigo script y la agregue y agregue las dos funciones mas la del Rectangle.prototype.fill=function() y Rectangle.prototype.intersects=function() pero aun asi mo se me visualiza... asi quedo el ultimo codico como te lo enciare despues de este comentario

      Eliminar
    6. window.onload = function(){
      //variables globales
      var canvas = document.getElementById('canvas');
      var gameloop = setInterval(main, 10);
      var cWidth = document.getElementById('canvas').width;
      var cHeight = document.getElementById('canvas').height;
      var mouse = { posX: cWidth / 2, posY: cHeight / 2};
      var ctx = null;
      var wall = [];
      var lava = [];
      var 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,2,2,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,2,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,2,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,2,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,2,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,2,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,2,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,2,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,2,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,2,2,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 setMap(map, columns, blockSize){
      var col = 0;
      var row = 0;
      wall.length = 0;
      lava.length = 0;
      for(var i = 0; i < map.length; i++){
      if(map[i] == 1){
      wall.push(new Rectangle(col*blockSize, row*blockSize, blockSize, blockSize));
      }else if(map[i] == 2){
      lava.push(new Rectangle(col*blockSize, row*blockSize, blockSize, blockSize));
      col++;
      }
      if(col >= columns){
      row++;
      col = 0;
      }
      }
      }

      document.addEventListener('mousemove',function(evt){
      mouse.posX=evt.pageX-canvas.offsetLeft;
      mouse.posY=evt.pageY-canvas.offsetTop;
      },false);

      if(canvas && canvas.getContext){
      var ctx = canvas.getContext('2d');
      if(ctx){
      cbackground(ctx);
      }
      }

      function cbackground(ctx){
      //pintando el mapa
      ctx.save();
      ctx.fillStyle = '#999';
      for(var i = 0; i < wall.length; i++){
      wall[i].fill(ctx);
      }
      ctx.restore();

      ctx.save();
      ctx.fillStyle = '#f00';
      for(var i = 0; i < lava.length; i++){
      lava[i].fill(ctx);
      }
      ctx.restore();

      //pintando fondo del canvas
      ctx.save();
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, cWidth, cHeight);
      ctx.restore();

      ctx.save();
      ctx.strokeStyle = 'black';
      ctx.lineWidth = 2;
      ctx.strokeRect(0, 0, cWidth, cHeight);
      ctx.restore();

      //pintando el puntero
      ctx.save();
      ctx.beginPath();
      ctx.fillStyle = 'blue';
      ctx.arc(mouse.posX,mouse.posY, 5, 0, 360, true);
      ctx.stroke();
      ctx.restore();
      }

      //gameloop - function of actualization for game
      function main(){
      setMap(map0, 30, 10);
      Rectangle();
      cbackground(ctx);

      }

      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;
      }

      Rectangle.prototype.intersects=function(rect){
      if(rect!=null){
      return(this.xrect.x
      && this.yrect.y);
      }
      }

      Rectangle.prototype.fill=function(ctx){
      ctx.fillRect(this.x, this.y, this.width, this.height);
      };

      }

      Eliminar
    7. Ya probé el código y encontré el error: Estás dibujando el fondo encima del mapa. ¿Como esperas que se vea si le pones un rectángulo blanco encima? :P

      Corregido eso, note que además modificaste la función setMap y pusiste col++ dentro de unas llaves en las que no debería ir. Corrige estos dos detalles, y tu mapa debería verse ya sin problemas.

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

    ResponderEliminar
  7. Como va? Yo aca intentando que me salga pero no encuentro mi error si alguien me puede ayudar se lo agradeceria...
    $(document).ready(function(){
    var canvas = $("#canvas")[0];
    var cWidth = $("#canvas").width();
    var cHeight = $("#canvas").height();
    var ctx = canvas.getContext("2d");

    function init(){
    if(typeof game_loop != "undefined"){
    clearInterval(game_loop);
    }
    game_loop = setInterval(main, 100);
    }

    function main(){
    paint();
    setMap(map0, 10);
    }

    var wall = [],
    lava = [],
    map0 = [
    [1, 1, 1, 1, 1, 1, 1, 1],
    [1, 2, 0, 0, 0, 0, 2, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 2, 0, 0, 2, 0, 1],
    [1, 0, 2, 0, 0, 2, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 1],
    [1, 2, 0, 0, 0, 0, 2, 1],
    [1, 1, 1, 1, 1, 1, 1, 1]
    ];

    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));
    }
    }
    }
    }

    function paint(ctx){
    var i = 0,
    l = 0;

    ctx.fillStyle = "#999";
    for (i = 0; i < wall.length; i += 1){
    wall[i].fill(ctx);
    }

    ctx.fillStyle = "#f00";
    for(i = 0; i < lava.length; i += 1){
    lava[i].fill(ctx);
    }
    }
    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;
    }
    }


    init();

    })

    ResponderEliminar