Previous slide Forward
Next Item Back

Breathing Universes

Using Canvas and Node.js to create multiplayer games

Alexandre Amaral de Carvalho

Breathing Universes

Summary

  • Introduction
  • The Server
  • Node
  • The Client
  • Canvas
Breathing Universes - Intro

Checklist for a Multiplayer game

  • Server - runs game
  • Client shows a view of the universe
  • Client sends commands
  • Server sends updates
  • Server cycles, evolve game state
  • Client cycles, redraw the view
Breathing Universes - Intro

Defining User Commands

  • Turn (left/right cursor keys)
  • Face (click/touch on play area, radar on HUD)
  • Speed up/down (cursors/ touch speed on HUD)
  • Fire (space key / touch Fire on HUD)
  • Interact (touch NPC on play area)
  • And many others ...
Breathing Universes

Server

  • HTTP Server
  • Websockets Server
  • Listening
  • Logging In
  • State Update
Breathing Universes - Server

HTTP Server

  • Client should request state changes frequently
  • ... wait for response ...
  • redraw the view after the response
  • Slow, wastes resources, not practical
Breathing Universes - Server

Sample HTTP Server

  • Simple Node.js HTTP server
  • var http = require('http');
  • var monkey = http.createServer(function (request, response) {
      response.writeHead(200, {'Content-Type': 'text/plain'});
      response.end(request.url + '\n');
    });
  • monkey.listen(1337, "127.0.0.1");
  • echoes the request url
Breathing Universes - Server

WebSockets Server

  • Connect once
  • Bidirectional communication
  • Each end can send messages anytime
  • Fast
  • Meaningful Messages
  • Warning: Highly unstable!
Breathing Universes - Server

Sample WebSockets Server

  • ws = require(ws.js);
  • ws.setupEventHandlers(
      this.onConnect.bind(this),
      this.onData.bind(this),
      this.onClose.bind(this),
      this.onError.bind(this)
    );
  • if (ServerConfig.PROTOCOL == 'wss')
        ws.createSecureServer(options);
    else
        ws.createServer();
  • ws.listen(ServerConfig.LISTEN_PORT);
Breathing Universes - Server

Websockets - How to use it

  • search on github
  • e.g. npm install socket.io
  • My initial lib -> http://github.com/ncr/node.ws.js
  • added support for node 0.4 ssl
  • and draft10, recently implemented by chrome v14, firefox 7
  • notes
  • node 0.6.0 stable released on 2011-11-04
  • node 0.6.1 stable released on 2011-11-11
Breathing Universes - Server

Listening to users

  • No, this is not an usability study!
  • Messages that require a session:
  • clients = {};
  • onData = function(websocket, messageStr) {
  • var client = clients[message.uuid]; // or by comparing websocket
    if (client) client.ping();
  • There will also be messages that do not require session:
  • e.g. login, register, etc...
Breathing Universes - Server

Logging in Users

  • function processLogin(websocket, message) { ... 
  •   var user = new User();
  •   var uuid = generate_uuid(...);
  •   user.uuid = uuid;
  •   user.stream = websocket;
  •   clients[uuid] = user;
  •   // do initialization of the user
  •   user.ping(); // keep session alive
    }
Breathing Universes - Server

Sending Messages to Users

  • Sessionless Messages
  • function doSomething(socket, data) {
      data2 = process(data);
      socket.write(data2);
    }
  • Session Messages
  • var client = clients[index];
  • client.stream.write(data);
Breathing Universes - Server

State Update Cycle

  • for each client:
  • check if it timed out (ping)
  • auto-save it if necessary
  • set next pos, heading, speed ...
  • for every character, do their AI cycle.
  • for every hazard, set next position.
  • send universe state updates to every user.
Breathing Universes - Server

Why node?

  • Shared Interface/Message Code
  • Easy to implement assynchronous tasks
  • no threads
  • Single Player: adapt existing code to WebWorkers
  • One Process == All game/user data easily accessible
Breathing Universes

Client

  • WebSocket Connection
  • Reading
  • Writing
  • Drawing Cycle
  • Canvas
Breathing Universes - Client

WebSocket Connection

  • function getSocket(url){
  •   if (window.ElectricalSocket) return new ElectricalSocket(url);
  •   if (window.WebSocket) return new WebSocket(url);
      if (window.MozWebSocket) return new MozWebSocket(url);
    }
  • You can also use flash as a fallback...
  • Example WebSocket urls:

    ws://somewebserver:someport/resource

    wss://somewebserver2:someport2/resource2

Breathing Universes - Client

Reading from WebSocket

  • Read asynchronously
  • Once again using event handlers
  • var websocket = SpaceCommunication.getWebSocket(websocketUrl);
    websocket.onopen = function(){ /* change some state */ };
    websocket.onmessage = function(evt){
      var message = evt.data; 
      /* do something with data */
    };
    websocket.onclose = function(){ /* change some state */}; 
    websocket.onerror = function(){ 
      /* lib error - alert user? log to console? */
    };
    						
Breathing Universes - Client

Writing to WebSocket

  • Asynchronous
  • Why you shouldn't care
  • websocket.send(messageStr);
Breathing Universes - Client

Drawing in Canvas

  • Start up
  • Lines
  • Shapes
  • Text
  • Images
  • Transformations
Breathing Universes - Client - Canvas

Start up

  • Create the canvas and style it
  • var canvas = document.createElement("canvas");
    canvas.setAttribute("width", canvasWidth + "px");
    canvas.setAttribute("height", canvasHeight + "px");
    canvas.style.zIndex = 20;
    
    gameSection.appendChild(canvas);
    
  • Get the canvas context
  • var ctx2d = canvas.getContext('2d');
  • All drawing ops will use the canvas context
Breathing Universes - Client - Canvas

The drawing cycle

  • What would you use
  • setInterval?
  • setTimeout?
  • what about requestAnimationFrame?
  • requestAnimFrame = (function(){
      return window['requestAnimationFrame'] ||
        window['webkitRequestAnimationFrame'] ||
        window['mozRequestAnimationFrame'] ||
        window['oRequestAnimationFrame'] ||
        window['msRequestAnimationFrame'] ||
        function(/* function */ callback, /* DOMElement */ element){
          window.setTimeout(callback, 1000/FPS/2);
        };
    })();
    
Breathing Universes - Client - Canvas

Using requestAnimationFrame

  • function drawCycle() {
      drawMainActionArea(ctx2d);
      requestAnimFrame(drawCycle, canvas);
    }
    						
  • bootstrap the draw cycle with
  • drawCycle();
  • When does it "pause"?
  • canvas invisible, e.g. change tab.
  • You may also implement ways to pause the drawing cycle
Breathing Universes - Client - Canvas

Drawing mostly static things

  • Call "draw", only when it changes.
  • On the draw cycle, protected by a flag.
  • Use the DOM with zIndex above the canvas
  • Use multiple canvas
  • e.g. Health, Lives, etc
Breathing Universes - Client - Canvas

Lines

  • ctx.beginPath()
  • ctx.moveTo(x1,y1)
  • ctx.lineTo(x2,y2)
  • ctx.closePath()
  • ctx.strokeStyle="#000"
  • ctx.lineWidth = 1
  • ctx.stroke()
Breathing Universes - Client - Canvas

Lines

Line, Y U NO SHARP?

Breathing Universes - Client - Canvas

Line - Y U NO SHARP!

Breathing Universes - Client - Canvas

Lines and pixels



Breathing Universes - Client - Canvas

Shapes

  • ctx.beginPath();
  • ctx.arc(centerX, centerY, radius, startAngle, endAngle, bAntiClockwise);
  • ctx.quadraticCurveTo(cp1x, cp1y, x, y)
  • ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
  • ctx.closePath();
  • ctx.fill();
Breathing Universes - Client - Canvas

Text

  • text


  • ctx.fillText(text, 300, 100)
  • ctx.measureText(text).width
Breathing Universes - Client - Canvas

Images (loading)

  • var image = new Image();
  • image.onload = image_loaded_callback;
  • image.onerror = image_error_callback;
  • image.src = filename;
Breathing Universes - Client - Canvas

Images (drawing)

  • ctx2d.drawImage(image, 
      srcImageX, srcImageY, srcWidth, srcHeight, 
      dstX     , dstY     , dstWidth, dstHeight);
  • ctx2d.drawImage(image, 
      0, 0, imgWidth/2, imgHeight/2, 
      5, 5, imgWidth*2, imgHeight*2
    );
Breathing Universes - Client - Canvas

Images (rotating)

  • ctx.save();
      ctx.translate(centerX, centerY);
      ctx.rotate(heading);
      ctx.drawImage(...);
    ctx.restore();
  • What does this do?
    ctx.rotate(Math.PI/4);
    ctx.drawImage(ship, 0,  0, w, h,
                       20, 10, w, h);
  • Correct version
    ctx.translate(20+w/2,10+h/2);
    ctx.rotate(Math.PI/4);
    ctx.drawImage(ship,0,0,w,h,-w/2,-h/2,w,h);
Breathing Universes - Client - Canvas

Opacity/Alpha

  • ctx.globalAlpha = 0.5;
  • image
    ctx.globalAlpha = 0.5;
    ctx.drawImage(ship,0,0,w,h,0,0,w,h);
    // restore globalAlpha value
Breathing Universes - Client - Canvas

Animation (with sprites)

  • It's just drawing a different image every set of frames
  • var spriteItemPos = 0;
    var destImageHeight = 45;// height of sprite 
    function drawSprite() // call once every 1/15 s?
    {
      var srcImageHeightStart = spriteItemPos * destImageHeight;
      ctx.clearRect(0,0, 50, 50);
      ctx.drawImage(img, 
        0, srcImageHeightStart, img.width, destImageHeight, 
        0, 0, img.width, destImageHeight
      );
      spriteItemPos++;
    }
Breathing Universes

References

Breathing Universes

Just shut up!
Show me tha game!

Breathing Universes

Questions?

Breathing Universes

Thank you