Aprende a crear juegos en HTML5 Canvas

domingo, 1 de diciembre de 2013

Disparen al objetivo

En la ocasión anterior aprendimos las bases de Socket.io y como comunicarnos a través de él a los distintos jugadores conectados. Sin embargo, seguramente te diste cuenta que aun cuando las mirillas compartían escenario a través de todas las ventanas, cada uno estaba jugando su propio juego independiente. Por tanto, es tiempo de ver como hacer que todos los jugadores estén sincronizados en un mismo juego.

Antes de empezar el código, analicemos un poco la situación. Si pensamos en sincronizar el juego actual, eso indica que uno de los distintos juegos, debe indicarle a los demás la nueva posición. Después, cada vez que el objetivo sea disparado por alguno, enviará actualización a los demás. En algún momento del juego, la conexión no será confiable, algunas respuestas llegarán después de otras, todos estarán en lugares dispersos distintos a los que aparentan en el juego, y los objetivos volando por mil lugares distintos entre cada juego.

Debido a experiencias previas donde ocurría esto, fue que aprendí un secreto de los buenos juegos multijugador masivos en línea: Toda la lógica del juego, se desarrolla del lado del servidor. Los clientes únicamente se encargan de leer las entradas de los dispositivos, y de desplegar los elementos visuales en pantalla.

Teniendo esto en mente, es hora de dar un cambio radical a nuestro juego. Comencemos por el cliente, donde eliminaremos la función "act" (La pasaremos al lado servidor), y modificaremos la función "enableInputs" para que envíe las acciones al servidor:
    function enableInputs() {
        document.addEventListener('mousemove', function (evt) {
            mousex = evt.pageX - canvas.offsetLeft;
            mousey = evt.pageY - canvas.offsetTop;
            emitSight(mousex, mousey, 0);
        }, false);
        canvas.addEventListener('mousedown', function (evt) {
            //lastPress = evt.which;
            emitSight(mousex, mousey, evt.which);
        }, false);
    }
Verás que uso la función "emitSight" en lugar de enviar los datos directamente. Esto para normalizar las coordenadas dentro del lienzo, antes de enviarlas:
    function emitSight(x, y, lastPress) {
        if (x < 0) {
            x = 0;
        }
        if (x > canvas.width) {
            x = canvas.width;
        }
        if (y < 0) {
            y = 0;
        }
        if (y > canvas.height) {
            y = canvas.height;
        }
        
        socket.emit('mySight', {x: x, y: y, lastPress: lastPress});
    }
A la función "Circle" removeremos la función de distancia (Eso ahora será calculado del lado servidor), y agregaremos una variable "score" para almacenar el puntaje de cada jugador de forma individual. Crearemos un arreglo con los colores de cada mirilla:
    var colors = ['#0f0', '#00f', '#ff0', '#f00'];
Para así, en la función "paint" dibujar cada mirilla y su respectivo puntaje del jugador:
        for (i = 0, l = players.length; i < l; i += 1) {
            if (players[i] !== null) {
                players[i].drawImageArea(ctx, spritesheet, 10 * (i % 4), 20, 10, 10);
                ctx.fillStyle = colors[i % 4];
                ctx.fillText('Score: ' + players[i].score, 0, 10 + i * 10);
            }
        }
Al terminar el juego, ya no mostraremos simplemente puntaje, si no que resaltaremos que se trata de TU puntaje. Para eso, tendremos una variable "me" que indicará el número de jugador de cada uno (Que más tarde veremos como obtener este valor desde el servidor), e imprimiremos el puntaje de la siguiente forma:
            ctx.fillText('Your score: ' + players[me].score, 110, 100);
Ahora pasemos a la conexión entre el servidor y el cliente. Para empezar, al conectarse un nuevo jugador, además de avisar a todos de su entrada, responderemos al mismo, indicándole su número de jugador:
        socket.emit('me', {id: socket.playerId});
        io.sockets.emit('sight', {id: socket.playerId, x: 0, y: 0});
Esto en el lado cliente, será manejado por esta simple función:
        socket.on('me', function (sight) {
            me = sight.id;
        });
El evento de desconexión se mantiene igual. El evento "mySight" es prácticamente igual, exceptuando que ahora enviamos también el último botón presionado, el cual, en caso de ser 1 (clic derecho), llamará a la función "act" en el lado servidor:
        socket.on('mySight', function (sight) {
            players[socket.playerId].x = sight.x;
            players[socket.playerId].y = sight.y;
            if (sight.lastPress === 1) {
                act(socket.playerId);
            }
            io.sockets.emit('sight', {id: socket.playerId, x: sight.x, y: sight.y, lastPress: sight.lastPress});
        });
Del lado servidor, además de la ya usada "nSight", serán necesarias las siguientes variables: Una que nos indique cuanto tiempo falta para que termine el juego, constantes para el ancho y alto del juego (Pues no hay un canvas del lado del servidor) un arreglo para almacenar los jugadores, y una copia del objetivo:
    var gameEnd = 0,
        canvasWidth = 300,
        canvasHeight = 200,
        players = [],
        target = null;
Nota que tenemos una copia de la función Circle del lado servidor para manejar el objetivo y los círculos. A esta versión de la función, le eliminaremos todas las funciones de dibujado que no son necesarias del lado servidor, dejándole únicamente con la necesaria para calcular la distancia.

Ahora que ya tenemos listas las variables necesarias, analicemos la función "act" del lado servidor. Dado que ahora la actualización del juego no se da ahora por lapso de tiempo, si no por acción, sustituimos el deltaTime por gameEnd, que nos indicará el tiempo en que el juego ha de acabar. Por tanto, cuando el juego ya haya acabado (menos el segundo de margen), crearemos un nuevo tiempo de terminado para el juego, siendo el momento en el que comienza, mas diez segundos:
    function act(player) {
        var now = Date.now();
        if (gameEnd - now < -1000) {
            gameEnd = now + 10000;
            io.sockets.emit('gameEnd', {time: gameEnd});
            target.x = random(canvasWidth / 10 - 1) * 10 + target.radius;
            target.y = random(canvasHeight / 10 - 1) * 10 + target.radius;
            io.sockets.emit('target', {x: target.x, y: target.y});
        }
Como podemos ver en el código, una vez calculado el nuevo tiempo, emitimos este valor a los jugadores. Después cambiamos de posición el objetivo, y emitimos dicho valor también. En un momento más, veremos como manipular esto del lado cliente; antes, terminemos de analizar las acciones en el servidor:
        } else if (gameEnd - now > 0) {
            if (players[player].distance(target) < 0) {
                io.sockets.emit('score', {id: player, score: 1});
                target.x = random(canvasWidth / 10 - 1) * 10 + target.radius;
                target.y = random(canvasHeight / 10 - 1) * 10 + target.radius;
                io.sockets.emit('target', {x: target.x, y: target.y});
            }
        }
    }
En esta parte del código, si no ha terminado el tiempo, vemos entonces si el juego está corriendo. Siendo este el caso, comprobamos si el jugador que disparó está en intersección con el objetivo. De ser así, emitimos un punto más para el jugador que le acertó, cambiamos de posición el objetivo y emitimos su nueva posición.

Ahora que ya tenemos listo nuestro servidor, terminemos de analizar los escuchas de las emisiones faltantes en el lado cliente. Los escuchas de "score" y "target" son muy sencillos, ya que el primero solo suma el puntuaje faltante, y el segundo ubica el objetivo en su nueva posición:
        socket.on('score',function(n,score){
            players[n].score+=score;
        });
        socket.on('target',function(x,y){
            target.x=x;
            target.y=y;
        });
Prefiero enviar los puntos a sumar, que el nuevo puntuaje, por que aun cuando tarde en llegar un dato un poco más del debido, el puntuaje siempre será el correcto. Además, puedo enviar distintos valores a sumar, como por ejemplo, si le dan a un objetivo dorado, este podría valer 3 puntos en lugar de 1 (Ya les di la imagen, ustedes lo programarán si así lo desean).

Con respecto a gameEnd, hay que analizar con un poco más de detalle el escucha:
        socket.on('score', function (sight) {
            players[sight.id].score += sight.score;
        });
        
        socket.on('target', function (t) {
            target.x = t.x;
            target.y = t.y;
        });
Notarás que originalmente asignaba time a la variable "gameEnd" del lado cliente, pero lo he cambiado por hacer el mismo proceso de sumar 10 segundos al tiempo actual en su lugar. Lo mas lógico a primera vista, parece ser controlar el tiempo desde el servidor para asegurarse que todos los jugadores estén sincronizados. Pero hay que tener en cuenta que cada computadora tiene su propia hora, y (en el mejor de los casos) puede haber diferencias de segundos entre cada una y con el servidor. Eso, en un juego que dura tan solo 10 segundos, puede ser una diferencia de tiempo crucial para su jugabilidad. Además habría que ajustar también la diferencia horaria entre los jugadores del mundo.

Por ello calculamos mejor el tiempo del lado cliente, que resulta una solución sencilla para este juego. Si en tu juego es imprescindible la sincronización entre el servidor y el cliente, es posible calcular la diferencia de tiempo entre ellos repitiendo varias veces una sustracción entre el tiempo del servidor y el del cliente, para asegurarse de tener el valor correcto. En este ejemplo, imprimo en la consola del cliente, la diferencia de tiempo entre el servidor y el cliente cada vez que se comienza un nuevo juego, para propósitos ilustrativos:
            if (window.console) {
                window.console.log('Diff: ' + (gameEnd - end.time) / 1000);
            }
Del lado cliente, siempre hay que comprobar que la consola de javascript exista, ya que los navegadores que no la tienen integrada, podrían provocar un error fatal con ello.

Teniendo comprendido todo esto, podemos ya probar nuestro juego finalizado. Como detalle extra antes de concluir, me gustaría agregar una solución rápida para manejar los jugadores.

Si has probado intensamente el juego hasta ahora, habrás ya notado que cada vez que entras de nuevo al juego, los jugadores siguen aumentando indefinidamente, y eso sigue incrementando mientras el servidor no se cierre. Para manejar este problema he creado una sencilla solución, que si bien no es la mas óptima, nos ayudará a manejar de mejor forma a los jugadores.

La idea es que, cada vez que se desconecte un jugador, guardemos su identificador en un arreglo de nombre "stack", de la siguiente forma:
        stack.push(socket.playerId);
Así, al conectarse un nuevo jugador, primero buscamos si hay identificadores pendientes sin usar. De ser así, agarramos uno de la lista, y en caso contrario, le asignamos el identificador siguiente de la variable "nSight" como lo hemos hecho hasta ahora:
        if (stack.length) {
            socket.playerId = stack.pop();
        } else {
            socket.playerId = nSight;
            nSight += 1;
        }
Para optimizar aun mejor el manejo de los jugadores, agregaremos una variable "totalPlayers" a la cual agregaremos 1 cada que se conecta un jugador, y restaremos 1 cada que se desconecta un jugador. Al final se comprueba cada que se desconecta un jugador, si esta variable es cero de nuevo (todos se han desconectado), y de ser así, vaciamos el arreglo "stack" y regresamos el valor de "nSight" a cero:
            if (totalPlayers < 0) {
                stack.length = 0;
                nSight = 0;
                console.log('Sights were reset to zero.');
            } else {
                stack.push(socket.playerId);
            }
Con esto, se manejará de forma más optimizada a los jugadores. Y de esta forma, hemos concluido nuestro primer juego multijugador masivo en línea con la ayuda de node.js y socket.io. ¡Felicidades!

Si te quedaron dudas sobre lo aquí explicado, revisa los códigos finales, y si aun tienes más dudas en el tema, no dudes en dejar tu comentario. Gracias una vez más por acompañarme en estos pequeños cursos. ¡Felices códigos!

Código final:


server.js
/*global console, process, require */
(function () {
    'use strict';
    var serverPort = process.env.PORT || 5000,
        server = null,
        io = null,
        nSight = 0,
        gameEnd = 0,
        canvasWidth = 300,
        canvasHeight = 200,
        totalPlayers = 0,
        players = [],
        stack = [],
        target = null;

    function Circle(x, y, radius) {
        this.x = (x === undefined) ? 0 : x;
        this.y = (y === undefined) ? 0 : y;
        this.radius = (radius === undefined) ? 0 : radius;
    }

    Circle.prototype = {
        constructor: Circle,

        distance: function (circle) {
            if (circle !== undefined) {
                var dx = this.x - circle.x,
                    dy = this.y - circle.y;
                return (Math.sqrt(dx * dx + dy * dy) - (this.radius + circle.radius));
            }
        }
    };

    function random(max) {
        return ~~(Math.random() * max);
    }

    function act(player) {
        var now = Date.now();
        if (gameEnd - now < -1000) {
            gameEnd = now + 10000;
            io.sockets.emit('gameEnd', {time: gameEnd});
            target.x = random(canvasWidth / 10 - 1) * 10 + target.radius;
            target.y = random(canvasHeight / 10 - 1) * 10 + target.radius;
            io.sockets.emit('target', {x: target.x, y: target.y});
        } else if (gameEnd - now > 0) {
            if (players[player].distance(target) < 0) {
                io.sockets.emit('score', {id: player, score: 1});
                target.x = random(canvasWidth / 10 - 1) * 10 + target.radius;
                target.y = random(canvasHeight / 10 - 1) * 10 + target.radius;
                io.sockets.emit('target', {x: target.x, y: target.y});
            }
        }
    }

    function MyServer(request, response) {
        response.writeHead(200, {'Content-Type': 'text/html'});
        response.end('<h1>It\'s working!</h1>');
    }

    target = new Circle(100, 100, 10);

    server = require('http').createServer(MyServer);
    server.listen(parseInt(serverPort, 10), function () {
        console.log('Server is listening on port ' + serverPort);
    });

    io = require('socket.io').listen(server);
    io.sockets.on('connection', function (socket) {
        if (stack.length) {
            socket.playerId = stack.pop();
        } else {
            socket.playerId = nSight;
            nSight += 1;
        }
        totalPlayers += 1;
        players[socket.playerId] = new Circle(0, 0, 5);
        socket.emit('me', {id: socket.playerId});
        io.sockets.emit('sight', {id: socket.playerId, x: 0, y: 0});
        console.log(socket.id  + ' connected as player' + socket.playerId);

        socket.on('mySight', function (sight) {
            players[socket.playerId].x = sight.x;
            players[socket.playerId].y = sight.y;
            if (sight.lastPress === 1) {
                act(socket.playerId);
            }
            //socket.broadcast.volatile.emit('sight', {id: socket.playerId, x: sight.x, y: sight.y, lastPress: sight.lastPress});
            io.sockets.emit('sight', {id: socket.playerId, x: sight.x, y: sight.y, lastPress: sight.lastPress});
        });

        socket.on('disconnect', function () {
            io.sockets.emit('sight', {id: socket.playerId, x: null, y: null});
            console.log('Player' + socket.playerId + ' disconnected.');
            totalPlayers -= 1;
            if (totalPlayers < 1) {
                stack.length = 0;
                nSight = 0;
                console.log('Sights were reset to zero.');
            } else {
                stack.push(socket.playerId);
            }
        });
    });
}());

game.js
/*jslint es5: true */
/*global io */
(function (window, undefined) {
    'use strict';
    var socket = io.connect('http://localhost:5000'),
        canvas = null,
        ctx = null,
        mousex = 0,
        mousey = 0,
        gameEnd = 0,
        me = 0,
        players = [],
        colors = ['#0f0', '#00f', '#ff0', '#f00'],
        target = null,
        spritesheet = new Image();

    function Circle(x, y, radius) {
        this.x = (x === undefined) ? 0 : x;
        this.y = (y === undefined) ? 0 : y;
        this.radius = (radius === undefined) ? 0 : radius;
        this.score = 0;
    }

    Circle.prototype = {
        constructor: Circle,
        
        stroke: function (ctx) {
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
            ctx.stroke();
        },

        drawImageArea: function (ctx, img, sx, sy, sw, sh) {
            if (img.width) {
                ctx.drawImage(img, sx, sy, sw, sh, this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
            } else {
                this.stroke(ctx);
            }
        }
    };

    function enableSockets() {
        socket.on('me', function (sight) {
            me = sight.id;
        });
        
        socket.on('sight', function (sight) {
            if (sight.lastPress === 1) {
                canvas.style.background = '#333';
            }
            if (sight.x === null && sight.y === null) {
                players[sight.id] = null;
            } else if (!players[sight.id]) {
                players[sight.id] = new Circle(sight.x, sight.y, 5);
            } else {
                players[sight.id].x = sight.x;
                players[sight.id].y = sight.y;
            }
        });
        
        socket.on('gameEnd', function (end) {
            var i = 0,
                l = 0;
            //gameEnd = end.time;
            gameEnd = Date.now() + 10000;
            for (i = 0, l = players.length; i < l; i += 1) {
                if (players[i] !== null) {
                    players[i].score = 0;
                }
            }
            if (window.console) {
                window.console.log('Diff: ' + (gameEnd - end.time) / 1000);
            }
        });
        
        socket.on('score', function (sight) {
            players[sight.id].score += sight.score;
        });
        
        socket.on('target', function (t) {
            target.x = t.x;
            target.y = t.y;
        });
    }

    function emitSight(x, y, lastPress) {
        if (x < 0) {
            x = 0;
        }
        if (x > canvas.width) {
            x = canvas.width;
        }
        if (y < 0) {
            y = 0;
        }
        if (y > canvas.height) {
            y = canvas.height;
        }

        socket.emit('mySight', {
            x: x,
            y: y,
            lastPress: lastPress
        });
    }

    function enableInputs() {
        document.addEventListener('mousemove', function (evt) {
            mousex = evt.pageX - canvas.offsetLeft;
            mousey = evt.pageY - canvas.offsetTop;
            emitSight(mousex, mousey, 0);
        }, false);
        canvas.addEventListener('mousedown', function (evt) {
            //lastPress = evt.which;
            emitSight(mousex, mousey, evt.which);
        }, false);
    }

    function paint(ctx) {
        var counter = 0,
            i = 0,
            l = 0;
        
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.strokeStyle = '#f00';
        target.drawImageArea(ctx, spritesheet, 0, 0, 20, 20);
        ctx.strokeStyle = '#0f0';
        for (i = 0, l = players.length; i < l; i += 1) {
            if (players[i] !== null) {
                players[i].drawImageArea(ctx, spritesheet, 10 * (i % 4), 20, 10, 10);
                ctx.fillStyle = colors[i % 4];
                ctx.fillText('Score: ' + players[i].score, 0, 10 + i * 10);
            }
        }

        ctx.fillStyle = '#fff';
        counter = gameEnd - Date.now();
        if (counter > 0) {
            ctx.fillText('Time: ' + (counter / 1000).toFixed(1), 250, 10);
        } else {
            ctx.fillText('Time: 0.0', 250, 10);
        }
        if (counter < 0) {
            ctx.fillText('Your score: ' + players[me].score, 110, 100);
            if (counter < -1000) {
                ctx.fillText('CLICK TO START', 100, 120);
            }
        }
        canvas.style.background = '#000';
    }

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

    function init() {
        canvas = document.getElementById('canvas');
        ctx = canvas.getContext('2d');
        canvas.width = 300;
        canvas.height = 200;
        
        target = new Circle(100, 100, 10);
        spritesheet.src = 'targetshoot.png';

        enableInputs();
        enableSockets();
        run();
    }

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

38 comentarios:

  1. No consigo ver nada...lo siento creo que no explicas algo tan tonto que no lo se...tengo los dos archivos. pero habra que poner algo en el index que se ejecuta no? ..solo esta los ficheros js..

    ResponderBorrar
    Respuestas
    1. Revisa las consolas del navegador y el servidor para localizar posibles errores.

      Pasando por lo lógico... ¿Activaste el archivo run-server.bar?

      Borrar
    2. Si...pero a lo que me refiero...es que ejecuto el index que no tiene ningun archivo vinculado js

      Borrar
    3. ¿No estás usando el index visto en el primer capítulo? Ese está vinculado con game.js en una etiqueta script...

      Borrar
    4. Yo en el index tengo esto:

      < script src="game.js" >< /script >
      < script src="/socket.io/socket.io.js" >< /script >

      introduci el codigo vinculando al archivo games.js... no lo vi en ningun sitio del tutorial supongo porque era logico...

      cuando pongo la consola me sale el siguiente error :
      module.js:340
      throw err;
      ^
      Error: Cannot find module 'socket.io'

      El contenido de mi archivo run-server.command tengo:
      #!/bin/bash
      cd "$(dirname "$BASH_SOURCE")"
      node ./server.js
      read


      PD: separo < del codigo para poder enviar el formulario sin que reconoza el html

      Borrar
    5. Dice que no se encuentra "socket.io", lo que significa que no está instalado. Eso se vio en el tema anterior, se instala con "npm install socket.io".

      Borrar
    6. esa frase la pongo en la consola y luego ejecuto el archivo run-server.command?

      Borrar
    7. Solo debes ejecutarla una vez. Esperas a que instale, y si todo sale bien, las siguientes veces que ejecutes run-server, deberá ejecutar sin regresar el error que me mostraste antes.

      Borrar
    8. Buenas y siento tener tantas preguntas. Revise todo nuevamente y creo que el problema sigue estando en el socket. Cuando lo instalo se me crea la carpeta socket.io pero dentro de ella no esta la ruta igual < script src="/socket.io/socket.io.js" > Te pongo una captura de como tengo http://uploadpie.com/GDsYJ
      entonces al ejecutar el command me sale el siguiente error:
      module.js:340
      throw err;
      ^
      Error: Cannot find module 'socket.io'
      at Function.Module._resolveFilename (module.js:338:15)
      at Function.Module._load (module.js:280:25)
      at Module.require (module.js:364:17)
      at require (module.js:380:17)
      at Object. (/Users/fupi/Downloads/prueba juego/server.js:5:10)
      at Module._compile (module.js:456:26)
      at Object.Module._extensions..js (module.js:474:10)
      at Module.load (module.js:356:32)
      at Function.Module._load (module.js:312:12)
      at Function.Module.runMain (module.js:497:10)

      Borrar
    9. Este comentario ha sido eliminado por el autor.

      Borrar
    10. ya me carga el socket. pero me sale el siguiente error:
      info - socket.io started
      warn - error raised: Error: listen EADDRINUSE

      Borrar
    11. Prueba usar un puerto diferente; seguramente el puerto que intentas usar ya está siendo usado por otra aplicación.

      Borrar
    12. GRACIAS!! era eso..lo he cambiado al puerto 8000. Ahora no me sale ningun error en la terminal. Pero cuando entro en el navegador me sale en blanco..es de decir no me funciona el juego.. como puedo averiguar que esta fallando ahora?

      Borrar
    13. ¿Aparece algo en la consola de JavaScript? En el primer tema enseñe como usarla si no sabes.

      Revisa tambien que algo se imprima en el código fuente de la página. Quizá sea problema en la configuración del servidor.

      Borrar
    14. He mirado en la consola y me da este error:

      Uncaught TypeError: Cannot call method 'getContext' of null
      init
      game.js:18

      que es la linea:
      ctx=canvas.getContext('2d');

      Borrar
    15. No se ha encontrado el canvas con el id deseado en la línea anterior. ¿Estás seguro que creaste un canvas en el HTML y tiene el mismo id del que pides en esa línea?

      Borrar
  2. Buenas, perdona de nuevo y siento mucho molestar tanto. Cuando intento instalar el socket.io me sale el siguiente error:

    npm http GET https://registry.npmjs.org/socket.io
    npm http 200 https://registry.npmjs.org/socket.io
    npm ERR! Error: EACCES, mkdir '/usr/local/lib/node_modules/socket.io'
    npm ERR! { [Error: EACCES, mkdir '/usr/local/lib/node_modules/socket.io']
    npm ERR! errno: 3,
    npm ERR! code: 'EACCES',
    npm ERR! path: '/usr/local/lib/node_modules/socket.io',
    npm ERR! fstream_type: 'Directory',
    npm ERR! fstream_path: '/usr/local/lib/node_modules/socket.io',
    npm ERR! fstream_class: 'DirWriter',
    npm ERR! fstream_stack:
    npm ERR! [ '/usr/local/lib/node_modules/npm/node_modules/fstream/lib/dir-writer.js:36:23',
    npm ERR! '/usr/local/lib/node_modules/npm/node_modules/mkdirp/index.js:37:53',
    npm ERR! 'Object.oncomplete (fs.js:107:15)' ] }
    npm ERR!
    npm ERR! Please try running this command again as root/Administrator.

    npm ERR! System Darwin 11.4.2
    npm ERR! command "node" "/usr/local/bin/npm" "install" "-g" "socket.io"
    npm ERR! cwd /Users/macuser
    npm ERR! node -v v0.10.22
    npm ERR! npm -v 1.3.14
    npm ERR! path /usr/local/lib/node_modules/socket.io
    npm ERR! fstream_path /usr/local/lib/node_modules/socket.io
    npm ERR! fstream_type Directory
    npm ERR! fstream_class DirWriter
    npm ERR! code EACCES
    npm ERR! errno 3
    npm ERR! stack Error: EACCES, mkdir '/usr/local/lib/node_modules/socket.io'
    npm ERR! fstream_stack /usr/local/lib/node_modules/npm/node_modules/fstream/lib/dir-writer.js:36:23
    npm ERR! fstream_stack /usr/local/lib/node_modules/npm/node_modules/mkdirp/index.js:37:53
    npm ERR! fstream_stack Object.oncomplete (fs.js:107:15)
    npm ERR!
    npm ERR! Additional logging details can be found in:
    npm ERR! /Users/macuser/npm-debug.log
    npm ERR! not ok code 0

    ResponderBorrar
    Respuestas
    1. Bueno, al menos ya sabemos que causa el error, y es que socket.io no está siendo instalado. Por lo que puedo ver de los errores, hacen falta permisos:

      npm ERR! Please try running this command again as root/Administrator.

      Prueba con:

      sudo npm install socket.io

      Borrar
  3. Buenas, me encanta tu blog, particularmente este tema, del que no se encuentra demasiado publicado, de ahí la siguiente pregunta, ¿vas a hablar sobre alguna técnica para manejar el lag, como predicción, interpolación y compensación?

    ResponderBorrar
    Respuestas
    1. El tema de MMOG es bastante extenso, y aun hay mucho por hablar de ello. Pero por ahora me concentraré en otros temas inconclusos, por lo que dichos temas podrían tardar mucho en llegar. De igual forma, eventualmente, estos temas serán tratados.

      Borrar
    2. Gracias por la respuesta y por tu blog, mucho animo, es realmente útil.

      Felices Fiestas

      Borrar
  4. ¡Ah! ¡JSLint es un dolor de cabeza sin fundamentos! Si quieres investigar un poco más de él, adelante, pero personalmente te recomendaría que lo ignores, por que suele generar más errores de los que resuelve.

    ResponderBorrar
  5. Buenas tardes, estoy leyendo todos sus tutoriales, me parecen muy interesantes, no eh terminado, sin embargo tengo una duda, habrá algun tutorial para escenarioss mas complejos, es decir 3d pero con imagenes sencillas, ya que deseo aplicarlo para el diseño de una casa, mover el personaje en diferentes ejes dentro de la misma.

    ResponderBorrar
    Respuestas
    1. Dentro del tutorial aún no vemos nada sobre 3D, pero puedes investigar en Google sobre WebGL para ver como se usa canvas en 3D.

      Ahora, si tu propósito no es un videojuego si no quizá una presentación arquitectónica, quizá te interese un programa especializado a modelado de casas como Google SketchUp: http://www.sketchup.com

      Si tienes alguna otra duda, avísame.

      Borrar
  6. Hola! He tenido un gran problema que finalmente he conseguido solucionar, y ha sido el siguiente:
    Resulta que al emitir el evento en servidor 'me' en el lado del cliente éste no se escuchaba T_T y esto era debido a que la definición de la escucha de dicho evento aún no se había producido, es decir, que cuando el server 'emitía' en el cliente aún no existía dicha escucha :S
    La Solución: Establecer un setTimeout en el server antes de emitir el 'me' jeje

    ResponderBorrar
    Respuestas
    1. Otra solución a ello, es llamar a io.connect hasta que el sitio haya cargado dentro de la función init. De esta forma aseguramos que el escucha se ejecute casi al mismo tiempo, y esa posibilidad se reduzca a un tiempo muy cercano a cero.

      Posiblemente lo mejor sea llamarle hasta que se ejecute la función enableSockets. De esta forma nos aseguraremos siempre de hacerlo en el momento más adecuado.

      Borrar
    2. Literalmente, llamas a la función io.connect dentro de la función enableSockets. ¿Cuál es el problema que tienes con esta tarea?

      Borrar
  7. Me gustaría saber cómo podría hacer que todos los jugadores (clientes) vean un mismo enemigo que se está moviendo en la pantalla, digamos en este ejemplo cómo hacer para que el target se mueva y todos los demás jugadores vean ese movimiento.

    ResponderBorrar
    Respuestas
    1. Mantén al enemigo únicamente en el servidor, haz en él los movimientos y envía su posición a los jugadores. De esta forma, todos estarán recibiendo la posición en movimiento del mismo enemigo.

      Borrar
  8. Hola, cuando ejecuto el server.js y entro en el navegador me da el error 404 y el archivo index.html lo tengo en el mismo directorio. A que puede ser debido? Gracias

    ResponderBorrar
    Respuestas
    1. ¿Está la función MyServer configurada correctamente? ¿Modificaste algo en ella? Recuerda que es necesaria para servir los archivos de forma correcta. No se que otro pueda ser el error, pero quizá con más detalles, pueda ayudarte a encontrarlo.

      Borrar
    2. Muchas gracias por tu respuesta lo solucioné!, Perdona que te moleste otra vez pero cuando añado el volatile el nodejs me da el siguiente error:

      Missing error handler on `socket`.
      TypeError: Cannot read property 'emit' of undefined
      at Socket. (/Users/Alex/Desktop/PROYECTO/Proyecto_Fase_1/server.js:69:28)

      Si elimino el volatile funciona correctamente, a que puede ser debido? Gracias!

      Borrar
    3. ¿Me podrías decir cómo resolviste el problema? Es posible que otros tengan el mismo problema en el futuro y tu solución les ayude.

      Sobre volatile, es posible que algo hayan actualizado y ahora se llame de forma distinta; Socket.io Ha cambiado bastante desde su surgimiento, aunque dudo que sea este el problema, y la documentación indica que aun se debe hacer de esta forma ¿Será entonces quizá algo de tu ejemplo específico? ¿Probaste con el ejemplo actual tal cual y te dio algún error?

      Borrar
  9. Si y surgió el mismo error, en cuanto al error de 404 me pasaba por la siguiente razón: Si ejecutaba el server.js desde la misma carpeta el error no me lo daba y cargaba la página correctamente, pero si lo ejecutaba desde otra posición es decir "node /loquesea/server.js" surgía, fallo mío!

    ResponderBorrar
    Respuestas
    1. ¡Ah si! Un error muy común cuando se empieza a aprender. Lo bueno es que lo resolviste, y quizá tu comentario pueda dar una pista a alguien que pase por el mismo problema.

      ¿Haz tenido alguna suerte con los envíos volátiles?

      Borrar
    2. No, parece ser que han eliminado esa opción. Una pregunta, estoy realizando un juego para mi proyecto de fin de curso y el tema es como un juego de tenis de dos jugadores, he conseguido hacer on-line a los jugadores y a la pelota, el problema es que por cada conexión que realizo me crea otra bola y otro par de jugadores. Cómo podría limitar el número de jugadores y el de la bola? No consigo sacarlo.

      Borrar
    3. Depende mucho la forma en que estés manejando la forma en que creas a tus personajes. Para empezar, esto deberías manejarlo del lado servidor, y crearles únicamente al comienzo, ligando a cada jugador la conexión correspondiente. Revisa cómo se crea en este ejemplo el objetivo, que sería el caso de tu pelota, pues solo de seas una, independiente a la cantidad de jugadores.

      Borrar