Hagamos un juego de dibujo con Node.js

Probablemente ya haya oído hablar de node.js. Es un servidor web asíncrono construido sobre el motor JavaScript V8 de Google (el mismo que hace que Chrome sea muy rápido). Con node, puede escribir servicios web escalables en JavaScript, que pueden manejar una gran cantidad de conexiones simultáneas, lo que lo hace perfecto como backend de juegos, chats web y otras tareas en tiempo real.

La idea

Hoy vamos a hacer un simple juego de dibujo en línea. La aplicación permitirá a los usuarios dibujar en la página arrastrando y moviendo el mouse, y mostrará los resultados en un elemento de lienzo grande. Sin embargo, lo que lo diferencia de todos los demás experimentos similares es que las personas se verán en tiempo real mientras lo hacen. Para lograr esto, aprovecharemos la biblioteca socket.io para node.js, que utiliza una variedad de tecnologías, desde websockets hasta AJAX long polling para brindarnos un canal de datos en tiempo real. Debido a esto, el ejemplo funciona en todos los navegadores modernos.

Instalando node.js

Para ejecutar el juego necesitarás instalar node.js. No debería llevar más de unos minutos y es bastante sencillo. Puede continuar y descargar los instaladores desde el sitio oficial. O puede ejecutar este conjunto de comandos si desea instalarlo desde su terminal en Linux u OSX (solo necesita ejecutar el primer script:node-and-npm-in-30-seconds.sh ).

Una vez que termine la instalación, también obtendrá acceso a npm, el administrador de paquetes de nodos. Con esta utilidad, puede instalar bibliotecas útiles y fragmentos de código que puede importar a sus scripts de node.js. Para este ejemplo, necesitaremos la biblioteca socket.io que mencioné anteriormente y node-static, que servirá los archivos HTML, CSS y JS de la aplicación de dibujo. Nuevamente, abra su terminal (o una nueva ventana de símbolo del sistema si está en Windows) y escriba el siguiente comando:

npm install [email protected] node-static

Esto no debería tardar más de unos minutos en completarse.

Ejecución de la aplicación

Si solo desea obtener los archivos y probar la aplicación en su computadora, deberá descargar el archivo desde el botón de arriba y extraerlo en algún lugar de su disco duro. Después de esto, abra un símbolo del sistema/terminal y navegue hasta la carpeta (por supuesto, recuerda cómo funciona el comando cd, ¿no?). Después de esto, escribe este comando y presiona regresar:

node app.js

Debería recibir un mensaje de depuración de socket.io (de lo contrario, probablemente su ruta sea incorrecta; ¡siga practicando con ese comando cd!). ¡Esto significa que todo está funcionando! Ahora abre http://localhost:8080 y debería ver su propia copia de la demostración. ¡Genial!

Estas instrucciones también se aplican si está siguiendo los pasos del artículo y está creando la aplicación desde cero. Lo que nos lleva de vuelta al tutorial:

El HTML

El primer paso es crear un nuevo documento HTML. En su interior, colocaremos el elemento de lienzo sobre el que dibujarán los usuarios y un div para sostener los punteros del mouse. Cada puntero del mouse será un div con el .pointer clase css que está absolutamente posicionada en la página (no discutiremos el estilo en este artículo, abra assets/css/styles.css para echar un vistazo).

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Node.js Multiplayer Drawing Game | Tutorialzine Demo</title>

        <!-- The stylesheets -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>
        <div id="cursors">
            <!-- The mouse pointers will be created here -->
        </div>

        <canvas id="paper" width="1900" height="1000">
            Your browser needs to support canvas for this to work!
        </canvas>

        <hgroup id="instructions">
            <h1>Draw anywhere!</h1>
            <h2>You will see everyone else who's doing the same.</h2>
            <h3>Tip: if the stage gets dirty, simply reload the page</h3>
        </hgroup>

        <!-- JavaScript includes. Notice that socket.io.js is served by node.js -->
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

Puede ver que el lienzo tiene un ancho fijo de 1900 px y una altura de 1000 px, pero los usuarios con pantallas más pequeñas solo verán una parte. Una posible mejora sería ampliar o reducir el lienzo en relación con el tamaño de la pantalla, pero eso se lo dejo a usted.

Para que funcione el canal de comunicación en tiempo real entre el navegador de los usuarios y node.js, debemos incluir el socket.io biblioteca en ambos lugares, sin embargo, no encontrará el socket.io.js archivo incluido en la parte inferior de index.html en el archivo de descarga. Esto se debe a que socket.io intercepta solicitudes a /socket.io/socket.io.js y lo sirve solo para que no tenga que cargar este archivo explícitamente con su aplicación.

El lado del cliente

En otros tutoriales, normalmente llamaríamos JavaScript a esta sección, pero esta vez tenemos JavaScript tanto en el cliente (el navegador de la persona) como en el servidor (node.js), por lo que se debe hacer una distinción adecuada.

El código que ve a continuación se ejecuta en el navegador de la persona. Utiliza socket.io para conectarse al servidor y nos notifica cuando ocurre un evento. Ese evento es un mensaje emitido por otros clientes y node.js nos lo transmite. Los mensajes contienen las coordenadas del mouse, la identificación única del usuario y si están dibujando o no en ese momento.

activos/js/script.js

$(function(){

    // This demo depends on the canvas element
    if(!('getContext' in document.createElement('canvas'))){
        alert('Sorry, it looks like your browser does not support canvas!');
        return false;
    }

    // The URL of your web server (the port is set in app.js)
    var url = 'http://localhost:8080';

    var doc = $(document),
        win = $(window),
        canvas = $('#paper'),
        ctx = canvas[0].getContext('2d'),
        instructions = $('#instructions');

    // Generate an unique ID
    var id = Math.round($.now()*Math.random());

    // A flag for drawing activity
    var drawing = false;

    var clients = {};
    var cursors = {};

    var socket = io.connect(url);

    socket.on('moving', function (data) {

        if(! (data.id in clients)){
            // a new user has come online. create a cursor for them
            cursors[data.id] = $('<div class="cursor">').appendTo('#cursors');
        }

        // Move the mouse pointer
        cursors[data.id].css({
            'left' : data.x,
            'top' : data.y
        });

        // Is the user drawing?
        if(data.drawing && clients[data.id]){

            // Draw a line on the canvas. clients[data.id] holds
            // the previous position of this user's mouse pointer

            drawLine(clients[data.id].x, clients[data.id].y, data.x, data.y);
        }

        // Saving the current client state
        clients[data.id] = data;
        clients[data.id].updated = $.now();
    });

    var prev = {};

    canvas.on('mousedown',function(e){
        e.preventDefault();
        drawing = true;
        prev.x = e.pageX;
        prev.y = e.pageY;

        // Hide the instructions
        instructions.fadeOut();
    });

    doc.bind('mouseup mouseleave',function(){
        drawing = false;
    });

    var lastEmit = $.now();

    doc.on('mousemove',function(e){
        if($.now() - lastEmit > 30){
            socket.emit('mousemove',{
                'x': e.pageX,
                'y': e.pageY,
                'drawing': drawing,
                'id': id
            });
            lastEmit = $.now();
        }

        // Draw a line for the current user's movement, as it is
        // not received in the socket.on('moving') event above

        if(drawing){

            drawLine(prev.x, prev.y, e.pageX, e.pageY);

            prev.x = e.pageX;
            prev.y = e.pageY;
        }
    });

    // Remove inactive clients after 10 seconds of inactivity
    setInterval(function(){

        for(ident in clients){
            if($.now() - clients[ident].updated > 10000){

                // Last update was more than 10 seconds ago.
                // This user has probably closed the page

                cursors[ident].remove();
                delete clients[ident];
                delete cursors[ident];
            }
        }

    },10000);

    function drawLine(fromx, fromy, tox, toy){
        ctx.moveTo(fromx, fromy);
        ctx.lineTo(tox, toy);
        ctx.stroke();
    }

});

La idea básica es que usamos socket.emit() para enviar un mensaje al servidor node.js con cada movimiento del mouse. Esto puede generar una gran cantidad de paquetes, por lo que estamos limitando la velocidad a un paquete cada 30 ms (la función $.now() está definida por jQuery y devuelve la cantidad de milisegundos desde la época).

El evento mousemove no se llama en cada píxel del movimiento, pero estamos usando un truco para dibujar líneas sólidas en lugar de puntos separados:al dibujar en el lienzo, estamos usando el método lineTo, de modo que la distancia entre las coordenadas del mouse es unidos con una línea recta.

¡Ahora echemos un vistazo al servidor!

Lado del servidor

Después de leer el código del lado del cliente, es posible que le preocupe que el código del servidor sea aún más largo. Pero te equivocarás. El código del lado del servidor es mucho más corto y simple. Lo que hace es servir archivos cuando las personas acceden a la URL de la aplicación en sus navegadores y transmiten socket.io mensajes Ambas tareas cuentan con la ayuda de bibliotecas, por lo que son lo más simples posible.

aplicación.js

// Including libraries

var app = require('http').createServer(handler),
    io = require('socket.io').listen(app),
    static = require('node-static'); // for serving files

// This will make all the files in the current folder
// accessible from the web
var fileServer = new static.Server('./');

// This is the port for our web server.
// you will need to go to http://localhost:8080 to see it
app.listen(8080);

// If the URL of the socket server is opened in a browser
function handler (request, response) {

    request.addListener('end', function () {
        fileServer.serve(request, response); // this will return the correct file
    });
}

// Delete this row if you want to see debug messages
io.set('log level', 1);

// Listen for incoming connections from clients
io.sockets.on('connection', function (socket) {

    // Start listening for mouse move events
    socket.on('mousemove', function (data) {

        // This line sends the event (broadcasts it)
        // to everyone except the originating client.
        socket.broadcast.emit('moving', data);
    });
});

¡Con esto nuestra aplicación de dibujo está completa!

¡Listo!

Dibujar es mucho más divertido cuando ves a otras personas haciéndolo al mismo tiempo. ¡Siéntete libre de jugar con el ejemplo y mejorarlo! Algunas ideas:sería increíble tener diferentes pinceles, borradores, colores y formas, o incluso banderas de países junto a los cursores. ¡Vuélvete loco!


No