Aprende a crear juegos en HTML5 Canvas

lunes, 1 de abril de 2013

Mundos grandes y cámara

Hasta el momento, los juegos que hemos hecho han sido pequeños, contenidos en el área de nuestro canvas. ¿Pero que hay si deseamos hacer un mundo enorme, más allá del límite de lo que podemos ver? Para hacer el efecto de desplazarse en un mundo enorme, lo común es desplazar todos los elementos en la pantalla y mantener centrado al jugador. Y la forma más sencilla de realizar este efecto, es mediante el uso de una cámara.

Continuando con el código visto en Mapas de mosaico, ubicaremos en la función init la línea donde creamos el mapa setMap(map0, 30, 10);, y cambiamos el último valor (blockSize) de 10 a 20, para crear un mapa el doble de grande en ambas dimensiones. Un pequeño truco sencillo que nos permitirá llevar a cabo este ejemplo sin mayores complicaciones.

Ahora sí, comencemos construyendo un objeto de tipo cámara, como se muestra a continuación:
    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;
        }
    };
La cámara tiene una posición x, una posición y, y una función focus, que ubica las coordenadas enviadas justo al centro de nuestro canvas (El centro es determinado por el alto y el ancho de nuestro canvas, dividido entre dos).

Declaramos la cámara que usaremos, y la inicializamos en la función "init":
    cam = new Camera();
Y al final de la función "game", afocamos la cámara al centro del jugador:
            // Focus player
            cam.focus(player.x, player.y);
El truco para desplazar todos los objetos de acuerdo a la posición de la cámara, es en realidad, restar la posición de la cámara a cada uno de los objetos del mapa dentro de la función "paint". Para eso, modificaremos la función "fill" en nuestro rectángulo para aceptar una cámara. Si existe, se rellenará el rectángulo de acuerdo a su posición con la cámara, de lo contrario, se rellenará como lo hemos hecho hasta ahora:
        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);
                }
            }
        }
Después modificamos el dibujado de todos los elementos, mandando la cámara a cada uno de ellos:
        // 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);
        }
Al probar el juego, podrás ve que ahora el mundo es muy grande, y la cámara está siempre centrada sobre el personaje. Pero al caminar más allá de la mitad del mundo, el personaje aparece en la otra mitad, de la pantalla, atorado en una pared o sobre el río de lava. ¿Por qué ocurre esto?

Recordemos que en la sección donde evaluamos si nuestro personaje está fuera de pantalla, lo hacemos con las medidas del canvas. ¡Pero ahora nuestro mundo es más grande que nuestro canvas! Por tanto, para corregir esto, declararemos dos variables worldWidth y worldHeight, y modificaremos los límites de movimiento del personaje, de esta forma:
            // Out Screen
            if (player.x > worldWidth) {
                player.x = 0;
            }
            if (player.y > worldHeight) {
                player.y = 0;
            }
            if (player.x < 0) {
                player.x = worldWidth;
            }
            if (player.y < 0) {
                player.y = worldHeight;
            }
¿Cuáles son los valores de worldWidth y worldHeight? En este momento sabemos que son el doble de nuestro canvas, por tanto serían 600 y 400 respectivamente. ¿Pero tenemos certeza que todos nuestros mapas tendrán siempre esa medida? ¿Modificaremos manualmente estos valores cada vez que cambiemos de mapa, dependiendo de la medida del mismo? En realidad, la función "setMap" nos podría dar de forma automática la medida de nuestro mundo. Te doy tres párrafos para que vayas a tu código y analices como podrías hacerlo:

1...

2...

3...

¿Te diste cuenta, o aun no estás muy seguro de ello? El ancho de nuestro mundo, es igual a la cantidad de columnas en nuestro mapa, multiplicado por el ancho de cada columna. De igual forma, el alto de nuestro mundo, es igual a la cantidad de filas, multiplicado por el alto de cada columna. Agreguemos entonces estas dos líneas al final de la función "setMap", para que haga estos cálculos de forma automática:
        worldWidth = columns * blockSize;
        worldHeight = rows * blockSize;
Ahora sí, al probar de nuevo el juego, el personaje se podrá desplazar por todo el mapa, y la cámara lo seguirá siempre.

Límitando la cámara al mundo.

Es genial que ahora podamos tener mundos enormes en una pantalla tan pequeña, ¿Pero no sería mejor si, al llegar la cámara al límite del mundo, esta se detuviera ahí en lugar de mostrar el vacío que hay más allá de los límites de nuestro mundo?

El truco es bastante sencillo, y en realidad ya lo hemos hecho antes. Es exactamente la misma técnica que usamos antes para mantener a nuestro personaje dentro del área del canvas, solo que ahora lo aplicamos a la cámara con respecto al área del mundo.

Así, si la posición en x es menor a cero, se le asigna el valor de cero, y si la posición en x es mayor al ancho del mundo menos el ancho de la cámara (Que en este caso, es el ancho de nuestro canvas), entonces se le asigna dicho valor. Ocurre lo mismo con la posición y respecto a su altura. Así, para mantener nuestra cámara siempre dentro de los límites del mundo, lo haremos agregando estas líneas en la función focus de la misma:
            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;
            }
Con esto, queda concluido el tema de como usar una cámara para desplazarse en un mundo grande.

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,
        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, 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 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,
        
        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) {
        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));
                }
            }
        }
        worldWidth = columns * blockSize;
        worldHeight = rows * blockSize;
    }

    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, 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(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, l = wall.length; i < l; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.top = wall[i].bottom;
                    }
                }
            }
            if (pressing[KEY_RIGHT]) {
                player.x += 5;
                for (i = 0, l = wall.length; i < l; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.right = wall[i].left;
                    }
                }
            }
            if (pressing[KEY_DOWN]) {
                player.y += 5;
                for (i = 0, l = wall.length; i < l; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.bottom = wall[i].top;
                    }
                }
            }
            if (pressing[KEY_LEFT]) {
                player.x -= 5;
                for (i = 0, l = wall.length; i < l; i += 1) {
                    if (player.intersects(wall[i])) {
                        player.left = wall[i].right;
                    }
                }
            }

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

            // 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;
            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;
        worldWidth = canvas.width;
        worldHeight = canvas.height;
        
        // Create camera and player
        cam = new Camera();
        player = new Rectangle2D(40, 40, 10, 10, true);

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

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

43 comentarios:

  1. Siempre tengo dudas con este tema, así que seguro este tutorial me va a venir re bien! Gracias!!

    ResponderEliminar
  2. Santa madre de dios, esto es brujeria jaja xD

    Anda que no me he llevado tiempo ideando formas de conseguir este efecto de mundo amplio pero ninguna ha dado resultado y de repente me muestras como caido del cielo una forma sencilla de conseguirlo. Nunca se me hubiese pasado por la cabeza pensar en un elemento "cámara". Voy a hacer más hincapié en tu ejemplo y a ver que consigo.

    Por cierto Karl, en el código final publicado se te ha olvidado colocar casi toda la parte de la función cámara, seguro que sería la versión anterior o una de pruebas.

    Para aquellos que lo queráis podéis conseguirlo desde el depurador de la consola del navegador.

    Me has alegrado el día y me vas a tener ocupado unas semanas, saludos compañero! ^^

    ResponderEliminar
    Respuestas
    1. Gracias por tus comentarios!

      ¡Y no había notado que omití poner en el código final la función de la cámara! ¡Arreglaré eso en un momento, que es muy importante! Gracias por la nota.

      Eliminar
  3. Buenas, pedazo de tutorial!! Sabes como poner texturas a los bloques del escenario?

    Saludos

    ResponderEliminar
    Respuestas
    1. De la misma forma que se dibuja cualquier imagen! Puedes usar imágenes individuales como se vio en la parte 1.6 "El juego de la serpiente", o con hojas de sprite como se vio en la parte 2.6 "Hojas de Sprite y animaciones".

      Si aun tienes dudas sobre esto, ¡Avísame!

      Eliminar
  4. Compañero, que gran tutorial, he seguido paso a paso cada uno de ellos... aunque a veces me desesperaba y me avanzaba unos cuantos haha, pero siempre regresaba.

    Bueno, una observacion, en la seccion de código completo, olvidaste declarar las variables worldWidth y worldHeight, por lo que si alguien copia y pega tu codigo, no le funciona hehe. Bueno... Este es uno de esos tutoriales que me brinque XD asi que aun no lo leo completo. Un saludo, y sigue posteando este tipo de tutoriales, de verdad, gracias.

    ResponderEliminar
    Respuestas
    1. ¡Muchas gracias por la nota! Revisando el código a detalle, me di cuenta que no solo había olvidado declararles, si no que prácticamente, toda su implementación era nula. Al parecer, fue un error de copia-pega al actualizar los códigos, pero ya ha quedado resuelto para todos los futuros visitantes.

      ¡Felices códigos!

      Eliminar
  5. seria muy interesante que puedas hacer un tutorial sobre juegos rpg, al estilo shinnig force o final fantasy

    ResponderEliminar
    Respuestas
    1. Un juego de RPG es un proyecto muy largo y ambicioso, pero con lo aprendido hasta ahora, tienes el conocimiento para poder desarrollar uno.

      Eliminar
  6. Hola. Tengo una pregunta.

    Dado que repintar un canvas es una acción que consume mucha CPU, me surge la siguiente duda. ¿Qué sería más eficiente?

    1- Borrar el canvas y repintar la zona del mapa que concuerda con la posición de la cámara.

    2- Tener un canvas inferior más grande con todo el mapa pintado y uno encima con el personaje (cámara). Cuando movemos a dicho personaje, desplazaríamos el canvas de abajo haciendo el efecto de movimiento.

    Las posiciones de los bloques y del jugador se guardarían en objetos y, al mover el personaje, habría que recalcular la posición de cada elemento del mapa para el cálculo de colisiones.



    El primero tendría un mayor número de repintados y el segundo, si es un mapa muy grande, haría muchísimos recálculos de posiciones (aunque no repintaría casi nada).

    ¿Qué crees que es más eficiente?


    Muchas Gracias.

    ResponderEliminar
    Respuestas
    1. Por lo que he podido comprobar, canvas automatiza el análisis del área pintable, por lo que la primer opción es equivalente a dibujarle como es hasta ahora.

      De la segunda versión, creo entender que deseas tener un canvas transparente encima de uno con todo el mapa, pero eso dejaría mucho trabajo al DOM.

      Sin embargo, de esta versión se podría sacar una versión más optimizada. Si tu mapa es estático, es decir, si en ningún momento cambiará por influencia de los demás interacciones, puedes crear un canvas virtual y dibujarlo ahí, creándote una imagen de fondo gigantesca. Posteriormente, dibujar en el canvas final solo el área que ha de verse, de forma similar a como se hace con los SpriteSheet.

      Por supuesto, si lo que quieres es optimizar al máximo el redibujado, lo mejor es limpiar y redibujar únicamente el área que ha cambiado. Es así como se hacía en tiempos pasados cuando los procesadores eran muy limitados.

      Sin embargo, hoy día los procesadores tienen muy buen poder para procesar gráficos, por lo que esta técnica es demasiado compleja para el beneficio que se logra, a menos que manejes gráficos muy intensos y veas que requiere dicha diferencia.

      Eliminar
  7. Hola querido Karl, por mas que intento no logro comprender por que esto de la cámara funciona, se ve tan sencillo en el código y tan difícil en la realidad, he creado una versión del codigo que te facilitara comprender mis preguntas, te las dejo en jsfiddle al final del commento

    mis inquietudes principales son.
    1. por que las paredes no se mueven de posición.
    2. por que el personaje si se mueve de posición
    3. por que en el apartado RECTANGLE :: measure position los valores son siempre los mismos

    http://jsfiddle.net/romualdo97/3eprh9L5/

    ResponderEliminar
    Respuestas
    1. NOTA: el circulo rojo es la posición de la cámara

      Eliminar
    2. Ya casi llego a algunas respuestas, por favor no me digas aun, dejame probar mi logica...

      Eliminar
    3. Desactiva la cámara, y verás que ocurre exactamente lo mismo. El personaje se mueve, pero el resto de los objetos en el mundo no lo hacen.

      Lo que en realidad se hace con la cámara, es cambiar el origen donde los objetos se dibujan, sin cambiar su posición. Eso nos permite desplazar lo que estamos observando, sin enfrentar los problemas que podría generar cambiar todo de posición para realizar dicha tarea.

      PD: Recomiendo mucho que focus sea llamado después de actualizar la posición personaje a enfocar, no antes, ya que eso podría generar arrastres no deseados cuando la física de objeto a enfocar sea más compleja.

      Eliminar
    4. Muchas gracias por tu consejo, lo he hecho así por que andaba haciendo algo de logging, tendré siempre en cuenta tu consejo.

      con lo que me dices y algunas otros razonamientos propios es suficiente para entender la lógica de las cámaras, ¿tu aprendiste todo esto en un libro o recopilando info de la web?

      Eliminar
    5. Me alegra saber que te ha ayudado.

      La mitad de lo que aquí enseño lo aprendí recopilando información de la web, y otra mitad la tuve que deducir por mi cuenta, como fue el caso de las cámaras. Posteriormente pude comparar mi método con otros estándares, lo que me ayudó a optimizarle.

      Eliminar
    6. Jaja interesante, y tu, ¿con que lenguaje empezaste? , ¿empezaste a programar para hacer juegos? ¿o ya tenias experiencia en el código y mas bien decidiste hacer realidad tu sueño de programar videojuegos?

      Eliminar
    7. Esa historia es un poco larga, pero intentando resumirla, desde muy pequeño ya quería hacer videojuegos.

      Desde que me enseñaron a programar en la escuela (En logo), intenté buscar empujar los límites de mi conocimiento para hacer algo muy similar a un juego. No fue si no hasta segundo semestre de universidad, cuando encontré el proyecto en Java de uno de los de último semestre en la computadora de la escuela. Era algo muy básico que solo movía un círculo por la pantalla y controlabas con el teclado (Básicamente hacía lo mismo que el tema 1.3 de este blog), pero eso fue todo lo que necesité para poder aprender por mi cuenta como hacer videojuegos.

      Con el tiempo, la escasa documentación de Java a mi alcance y el conocimiento regado en Internet, pude poco a poco hacer los juegos que deseaba aprender durante los siguientes años. Fue tan difícil conseguir toda aquella información en aquel tiempo, que me propuse hacer un lugar donde juntar todo mi conocimiento, para que a nadie le volviera a tomar tanto tiempo y esfuerzo tener que recolectar la información como me pasó a mi. Fue así como nació los orígenes de este blog.

      Eliminar
    8. OoO Admirable... ¿te has preguntado que hubiese pasado si no hubieses encontrado aquel trabaja en el pc?, eres un ejemplo a seguir para muchos

      Eliminar
    9. Habría encontrado otra forma de llegar a mi destino, pues lo había estado buscando desde hacía mucho. Sin embargo, los detalles de como habría ocurrido serían muy distintos, y no podría garantizar que estuviera aquí hoy. ¡Muchas gracias por el apoyo!

      Eliminar
    10. ¡Tienes razón! lo habrías logrado por que lo que hizo que te dedicaras a los videojuegos no fue el trabajo de alguien de ultimo semestre, lo que hizo que pasara lo que pasara fue un suceso de eventos que precedían dicho descubrimiento, entre los que se encontraban un factor clave que difícilmente puede ser destruido por alguna entidad externa ese factor era (es y sera) tu dedicación e interés por la industria, reitero lo anteriormente dicho, eres un gran ejemplo a seguir, con tu blog motivaras a muchas personas y muchas personas te lo agradecerán si es que ya eso no esta pasando...
      Una(s) pregunta mas querido Karl, cuando tu empezaste en el lio del código ¿cuantos años tenias?, ¿por que lo hiciste?, cuando se te ocurrió la idea de los videojuegos ¿el código fue lo primero en lo que pensaste o lo ultimo?, ¿cuanto tiempo crees que paso desde cuando pensaste por primera vez en hacer videojuegos hasta cuando el pensamiento se volvió un poco mas real?
      jaja bueno en definitiva creo que tengo un nuevo héroe ( debo actualizar mi heroe-camiseta )
      Estaré esperando muy ansioso tus respuestas....

      Eliminar
    11. ¿Eso era sólo una pregunta más? ¡Prácticamente me estás pidiendo una autobiografía! Tendré que enviarte el libro autografiado a tu casa :P...

      De acuerdo, deja resumo esta historia entonces. Desde mi infancia, posiblemente unos 8 o 9 años, ya me veía rayando mis cuadernos planeando mundos masivos en plataformas para crear mis propios juegos, y desde aquél entonces ya deseaba hacer ello de mayor. Fue hasta los 16 años que tuve experiencia con los primeros programas con instrucciones para hacer algo más o menos interactivo, pero hasta los 18 cuando realmente comencé a hacer verdaderos juegos programados como tal. Posiblemente hasta los 20 fue que cumplí por primera vez el sueño de crear un platformer que tenía desde mi tierna infancia, y te aseguro que a aquella joven edad, no tenía ni idea de cómo sería realizar las acciones que tenía en mente en código.

      Ahora, yendo un poco más a los tiempos recientes, ya que sabía programar, para resolver tu duda, el proceso es más o menos el siguiente: Primero pienso que es lo que deseo hacer, cómo deseo que se mueva y reaccione el personaje, luego pienso más o menos que clase de comandos necesitaría ejecutar para que dichas acciones se realicen, y finalmente, investigo cómo se escribe el código para ejecutar esos comandos, y pruebo opciones hasta encontrar la que mejor cubre mis necesidades y deseos al respecto.

      Espero esto te ayude a encontrar respuestas sobre tus propios métodos. Muchas gracias por tu apoyo e interés.

      Eliminar
    12. Jajajaja un libro autografiado, que creativo, espero que no te moleste, solo queria saber un poco mas de la mente que se esconde detras de este estraordinario blog, tus experiencias son muy valiosas, siempre todos estaremos muy agradecidos con tigo...

      Eliminar
    13. Estraordinariamente he escrito estraordinario sin X, espero que no te moleste la mala ortografia...

      Eliminar
    14. ¡Por el contrario! Me halaga saber que hay gente a quien mi historia inspire, ha sido un verdadero placer poder compartir mi conocimiento con ustedes. Muchas gracias a ti por tu interés en el blog y en mi persona.

      Eliminar
    15. ¡Jajaja! Descuida, he leído casos peores, y un error lo comete cualquiera. ¡Malo cuando hablan terrible y a propósito! Pero bueno, no los discrimino si necesitan mi ayuda (Mientras les entienda lo que escriben :P)

      Eliminar
    16. Jajaja, que actitud tan agradable... bueno, estaré merodeando tus blog's para ver que nuevas cosas publicas, por ahora solo me queda despedirme temporalmente, nos vemos en el próximo comentario...
      Mis agradecimiento infinitos a ti y a tus blog's

      Eliminar
  8. Hola Karl, se puede hacer un editor de niveles? o hay que hacerlo con el array

    ResponderEliminar
    Respuestas
    1. Los engines comunes en algún lado por detrás del código visible, están almacenados los niveles para poder intercambiar entre ellos.

      ¿A que te refieres con un editor de niveles?

      Eliminar
  9. Gracias Karl por responder. Lo decia porque hacer un mapa de 500 cuadrados si hay que hacer comprobaciones con 60 enemigos requiere muchos procesos y se peta. Pero supongo que hay que diseñarlo a mano con rectangulos mas grandes para hacer 10 en vez de 60.

    ResponderEliminar
    Respuestas
    1. Bueno, hay muchas formas de optimizar esta parte del código. Puedes hacer un algoritmo que detecte bloques cercanos para hacer, como dices, menos bloques de mayor tamaño. También puedes hacer una fórmula que, en lugar de detectar cada bloque de pared, compruebe el personaje con el área correspondiente contra los objetos estáticos del mapa, ahorrando el proceso de verificar cuadro por cuadro del mapa.

      Eliminar
    2. pero para saber si es cercano tendrás que hacer un bucle y pasar por todos o los metes en diferentes vectores? o como seria

      Eliminar
    3. perdon me referia a la segunad parte que cuentas el personaje con el área correspondiente

      Eliminar
    4. En realidad el truco consiste en convertir cada esquina del personaje en un punto de coordinada, y compararlo contra el arreglo original del mapa, por eso sólo funciona contra objetos estáticos, pero de esta forma, sólo comparas 6 puntos por ciclo, en lugar de cada una de las decenas de rectángulos en tu arreglo de pared. Veré si puedo poner un ejemplo práctico este fin de semana.

      Eliminar
  10. Hola Karl, es posible crear un editor de niveles?

    Saludos

    ResponderEliminar
    Respuestas
    1. Es posible. Prácticamente se trata de tener una cuadrícula, y dependiendo donde presiones, agregas dicho valor al arreglo. Pero entrar en detalle posiblemente está fuera del alcance del blog en este momento.

      ¿Intentarás hacer tu código para ello?

      Eliminar
    2. Gracias Karl, lo de agregar valores con el raton ya lo pensé pero como guardo ese arry ??? En un archivo? esa es mi duda

      Eliminar
    3. Me ha llamado mucho la atención sobre cómo descargar el contenido de tu arreglo, lo cual no había considerado, para lo que investigué un poco y armé este fiddle que espero te ayude:

      http://jsfiddle.net/ks2wsto1/

      Eliminar
    4. me gustaria guardar por ejemplo un array asi bloques.push(new Rectangle(x,y,10,alto));

      le voy metiendo y luego recuperarlo porque si los meto pero luego no puedo recuperarlo porque se guarda en el navegador no me sirve.

      Eliminar
    5. He actualizado el Fiddle, mostrando ahora además cómo cargar el arreglo de regreso al navegador y reinterpretado como una matriz:


      http://jsfiddle.net/daPhyre/ks2wsto1/1/

      Una vez que tienes esta matriz, puedes llamar a la función setMap con ella para cargar tu mapa desde el archivo externo.

      Eliminar