Hello I’m Jafar
 Multiplayer Worlds
           with
     Node.js + HTML




http://jaf.ar.com
Inspired By

      RED INTERACTIVE
        http://FF0000




2009 - I Love Flash
2010 - Steve Jobs Hates Flash
2011 - Flash dies slow death... :(
My Website is....




     Not perfect....

Probably really hackable...

   Still using globals....

 Chat room needs filters...

 Server not authoritave
OH NO BUGS IN CHROME!


Works well in Chrome 19 !
        Download Chrome Canary


Worked well in Chrome 16

 Chrome updates to 17...
   ...bugs come back
                   (image flicker)



 Might be a cache issue....
 Might be a Mac thing...
 Maybe Wifi on Mac thing
http://www.eclipse.org/forums/index.php/mv/tree/262108/#page_top
MICROSOFT SAYS
 NO WEBSOCKETS

So Screw IE9 or Lower
      and Opera and Safari



 Although Socket.io does
work on browsers without
      web sockets
           (too choppy)




   Microsoft Changes Their Mind
   IE10 pretty good....
STACK


Node.js - server
Socket.IO - cross browser sockets
Now.js - namespace, easy chat rooms

Express - static files, pages
jQuery - front end stuff
World.js
//ANIMATION LOOP
window.requestAnimFrame = (function(){
   return window.requestAnimationFrame ||
   window.webkitRequestAnimationFrame ||
   window.mozRequestAnimationFrame ||
   window.msRequestAnimationFrame
})();




         //MAIN LOOP
         (function animloop(){

               moveCamera();
               gravity();
               floor();

               myPlayer();
               movePlayers();
               controlPlayer();

               moveLasers();
               otherLasers();

               requestAnimFrame(animloop);

         })();
JUST USE HTML DIV’s
         Canvas too slow for a large map
  Because when you draw on canvas you must also clear
              each move and each pixel
                3000 x 3000 = 9 Million px/sec
3000px




 3000px                       1920 x 1024

 “Tip: DOM is usually always faster than canvas....”
           http://craftyjs.com/demos/tutorial/tutorial.html
Where is My Player?
           in relation to the DOM Window


var PosX = $('#myPlayer').offset().left - $(window).scrollLeft();


 #myPlayer                             .player                .player

     .player



                                       .player                .player




 if( PosX < 50 || PosX > (winW - 50) )            //within 50px of window



 window.scrollBy(x,y);     //scroll the window to keep player in view
var players = {};

     id, x, y, direction, moving, floor, skin


                                   CLIENT
 //KEY DOWN
 function kdRight(){
      now.pullPlayers(now.core.clientId,player.x, player.y, 0,1,floor_y,skin_num);
 }


                                   SERVER
//PULL PLAYERS
everyone.now.pullPlayers = function(id,x,y,dir,mov,flo,skin){

       actors[id] = {x:x, y:y, dir:dir, mov:mov,flo:flo,skin:skin};

       var playerUpdate = {};
       playerUpdate = {id:id,x:x,y:y,dir:dir,mov:mov,flo:flo,skin:skin};
       everyone.now.showPlayers(playerUpdate);

};



                                      CLIENT
//SHOW PLAYERS
now.showPlayers = function(plyr) {
     players[plyr.id] = {x:plyr.x, y:plyr.y, dir:plyr.dir, mov:plyr.mov,flo:plyr.flo,skin:plyr.skin};
}



            >>>> LOOP THE PLAYERS OBJECT
Animation Loop
//MOVE PLAYERS
function movePlayers(){

      //FOR LOOP
     for(var i in players) {

           //REMOVE
           $(".player[rel='" + i + "']").remove();

           //ADD
           $('#Players').append('<div class="player" rel="' + i + '"></div>');


           >>>>IF MOVING (next page)
           >>>>ELSE STANDING STILL (next page)


     }


}
//MOVING
if(players[i].mov == 1) {

        //LEFT
        if(players[i].dir == 1) {

            players[i].x--;
            $(".player[rel='"+ i +"']").css('left', players[i].x );
            $(".player[rel='"+ i +"']").css('top', players[i].y );
            $(".player[rel='"+ i +"']").css('background-image',img_walk_l);

        //RIGHT
        } else if(players[i].dir == 0){

            players[i].x ++;
            $(".player[rel='"+ i +"']").css('left', players[i].x );
            $(".player[rel='"+ i +"']").css('top', players[i].y );
            $(".player[rel='"+ i +"']").css('background-image',img_walk_r);

        } else {

            $(".player[rel='"+ i +"']").css('left', players[i].x );
            $(".player[rel='"+ i +"']").css('top', players[i].y );
            $(".player[rel='"+ i +"']").css('background-image',img_front);
        }


 //STANDING STILL
} else {

        //LEFT
        if(players[i].dir == 1) {
           $(".player[rel='"+ i +"']").css('background-image',img_l);
           $(".player[rel='"+ i +"']").css('left', players[i].x );
           $(".player[rel='"+ i +"']").css('top', players[i].y );
        //RIGHT
        } else if (players[i].dir == 0){
           $(".player[rel='"+ i +"']").css('background-image',img_r);
           $(".player[rel='"+ i +"']").css('left', players[i].x );
           $(".player[rel='"+ i +"']").css('top', players[i].y );
        //BACK
        } else if (players[i].dir == 'b'){
           $(".player[rel='"+ i +"']").css('background-image',img_back);
           $(".player[rel='"+ i +"']").css('left', players[i].x );
           $(".player[rel='"+ i +"']").css('top', players[i].y );
        //FRONT
        } else {
           $(".player[rel='"+ i +"']").css('background-image',img_front);
           $(".player[rel='"+ i +"']").css('left', players[i].x );
           $(".player[rel='"+ i +"']").css('top', players[i].y );
        }

//END MOV
}
Wait What About My Player
                  //GLOBALS
                  --movement
                  --leftKey
                  --rightKey


 //MY PLAYER
 function myPlayer() {

     if ( movement && leftKey ) {

           player.x --;
           $('#player').css('left',player.x );

     } else if ( movement && rightKey ) {

            player.x ++;
            $('#player').css('left',player.x );

     } else {

            $('#player').css('left',player.x );

      }

 //END FUNC
 }
GRAVITY LOOP
                        We use the global object “players{}”

//GRAVITY
function gravity() {

    //MY PLAYER
     if ( player.y !== floor_y ) {

             player.y ++;
             player.y ++;
             $('#player').css('top',player.y );
     }

    //FOR LOOP
    for (var i in players) {

         //IN THE AIR
         if( i === now.core.clientId && players[i].y !== floor_y ) {

                players[i].y++;
                players[i].y++;
                $(".player[rel='"+ i +"']").css('top', players[i].y );

         } else if ( i !== now.core.clientId && players[i].y !== players[i].flo) {

                players[i].y++;
                players[i].y++;
                $(".player[rel='"+ i +"']").css('top', players[i].y );

         } else {
                    //do nothing
         }

    }

}
FLOOR LOOP
                          changing players floor position

     //FLOOR
     function floor(){


            //FLOOR 1
           if(player.y == floor1 && player.x <= 810 && player.x >= 100){
                 floor_y = floor1;
                 player.y = floor1;
           }

           //FALLING
           if ( player.y == floor1 - 2 && player.x <= 810 && player.x >= 100) {
                  floor_y = floor1;
                  now.pullPlayers();
           }

           //JUMPING
           if ( player.y == floor1 + 2) {
                  now.pullPlayers();
           }


     //END FUNC
     }




                                    **PULL PLAYERS**
now.pullPlayers(now.core.clientId,player.x, player.y, direction, movement, floor_y,skin_num)
LASERS LOOP                                                           VARIABLES
                                                                       var lasers = [];
                                                                       var other_lasers = [];
                                                                       var laser_v = 27;

                                   CLIENT

 $(window).keyup(function(e) {

 case 191:
    if(!$(‘.placeHolder’).is(“:focus”)) {
       now.fireLasers(now.core.clientId,player.x + 12, player.y + 12, direction);
       lasers.push([player.x + 12, player.y + 12, direction]);
       e.preventDefault();
    }
    break;
 }

                                   SERVER
 //FIRE LASERS
 everyone.now.fireLasers = function(id,x,y,dir){

       var laserUpdate = {};
       laserUpdate = {id:id,x:x,y:y,dir:dir};
       everyone.now.showLasers(laserUpdate);

 };
                                   CLIENT

//SHOW LASERS
now.showLasers = function(laz) {
       if(laz.id != now.core.clientId) {
            other_lasers.push( [laz.x, laz.y, laz.dir] );
     }
}


       >>>> MOVE AND DRAW LASERS
Function to Draw Our Lasers
  //LASERS
  function drawLasers (x, y, dir) {

       if( dir == 0 || dir == 1) {
             game.clearRect(x,y,-65,2);
            game.clearRect(x, y,65,2);
            game.fillStyle = '#f00';
            game.fillRect(x, y,10,1);

           } else {

               game.clearRect(x,y,-2,65);
               game.clearRect(x, y,2,65);
               game.fillStyle = '#f00';
               game.fillRect(x, y,1,10);

       }

  }
MOVE LASERS

function moveLasers() {

     for(var i = 0; i < lasers.length; i++) {

         //IF WITHIN BOUNDS
         if(lasers[i][0] < player.x + w && lasers[i][0] > player.x - w && lasers[i][1] > 1500 ) {

            if(lasers[i][2] == 1) {
              lasers[i][0] -= laser_v;
            } else if(lasers[i][2] == 0){
              lasers[i][0] += laser_v;
            } else {

                lasers[i][1] -= laser_v;
            }

            drawLasers(lasers[i][0], lasers[i][1],lasers[i][2]);

         } else if(lasers[i][0] > player.x + w + 1 || lasers[i][0] < player.x - w +1){

            lasers.splice(i,1);

         } else if(lasers[i][1] < 0 ){

            lasers.splice(i,1);

         } else {
            //do nothing
         }


     }

 }
OTHER PLAYERS LASERS

function otherLasers() {

    for(var i = 0; i < other_lasers.length; i++) {

        if(other_lasers[i][0] < player.x + w && other_lasers[i][0] > player.x - w ) {

            if(other_lasers[i][2] == 1) {
              other_lasers[i][0] -= laser_v;
            } else if(other_lasers[i][2] == 0){
              other_lasers[i][0] += laser_v;
            } else {
              other_lasers[i][1] -= laser_v;
            }

            drawLasers(other_lasers[i][0], other_lasers[i][1],other_lasers[i][2]);


        } else {
            other_lasers.splice(i,1);

        }

    }

}

Node meetup feb_20_12

  • 1.
    Hello I’m Jafar Multiplayer Worlds with Node.js + HTML http://jaf.ar.com
  • 2.
    Inspired By RED INTERACTIVE http://FF0000 2009 - I Love Flash 2010 - Steve Jobs Hates Flash 2011 - Flash dies slow death... :(
  • 3.
    My Website is.... Not perfect.... Probably really hackable... Still using globals.... Chat room needs filters... Server not authoritave
  • 4.
    OH NO BUGSIN CHROME! Works well in Chrome 19 ! Download Chrome Canary Worked well in Chrome 16 Chrome updates to 17... ...bugs come back (image flicker) Might be a cache issue.... Might be a Mac thing... Maybe Wifi on Mac thing http://www.eclipse.org/forums/index.php/mv/tree/262108/#page_top
  • 5.
    MICROSOFT SAYS NOWEBSOCKETS So Screw IE9 or Lower and Opera and Safari Although Socket.io does work on browsers without web sockets (too choppy) Microsoft Changes Their Mind IE10 pretty good....
  • 6.
    STACK Node.js - server Socket.IO- cross browser sockets Now.js - namespace, easy chat rooms Express - static files, pages jQuery - front end stuff
  • 7.
    World.js //ANIMATION LOOP window.requestAnimFrame =(function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame })(); //MAIN LOOP (function animloop(){ moveCamera(); gravity(); floor(); myPlayer(); movePlayers(); controlPlayer(); moveLasers(); otherLasers(); requestAnimFrame(animloop); })();
  • 8.
    JUST USE HTMLDIV’s Canvas too slow for a large map Because when you draw on canvas you must also clear each move and each pixel 3000 x 3000 = 9 Million px/sec 3000px 3000px 1920 x 1024 “Tip: DOM is usually always faster than canvas....” http://craftyjs.com/demos/tutorial/tutorial.html
  • 9.
    Where is MyPlayer? in relation to the DOM Window var PosX = $('#myPlayer').offset().left - $(window).scrollLeft(); #myPlayer .player .player .player .player .player if( PosX < 50 || PosX > (winW - 50) ) //within 50px of window window.scrollBy(x,y); //scroll the window to keep player in view
  • 10.
    var players ={}; id, x, y, direction, moving, floor, skin CLIENT //KEY DOWN function kdRight(){ now.pullPlayers(now.core.clientId,player.x, player.y, 0,1,floor_y,skin_num); } SERVER //PULL PLAYERS everyone.now.pullPlayers = function(id,x,y,dir,mov,flo,skin){ actors[id] = {x:x, y:y, dir:dir, mov:mov,flo:flo,skin:skin}; var playerUpdate = {}; playerUpdate = {id:id,x:x,y:y,dir:dir,mov:mov,flo:flo,skin:skin}; everyone.now.showPlayers(playerUpdate); }; CLIENT //SHOW PLAYERS now.showPlayers = function(plyr) { players[plyr.id] = {x:plyr.x, y:plyr.y, dir:plyr.dir, mov:plyr.mov,flo:plyr.flo,skin:plyr.skin}; } >>>> LOOP THE PLAYERS OBJECT
  • 11.
    Animation Loop //MOVE PLAYERS functionmovePlayers(){ //FOR LOOP for(var i in players) { //REMOVE $(".player[rel='" + i + "']").remove(); //ADD $('#Players').append('<div class="player" rel="' + i + '"></div>'); >>>>IF MOVING (next page) >>>>ELSE STANDING STILL (next page) } }
  • 12.
    //MOVING if(players[i].mov == 1){ //LEFT if(players[i].dir == 1) { players[i].x--; $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); $(".player[rel='"+ i +"']").css('background-image',img_walk_l); //RIGHT } else if(players[i].dir == 0){ players[i].x ++; $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); $(".player[rel='"+ i +"']").css('background-image',img_walk_r); } else { $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); $(".player[rel='"+ i +"']").css('background-image',img_front); } //STANDING STILL } else { //LEFT if(players[i].dir == 1) { $(".player[rel='"+ i +"']").css('background-image',img_l); $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); //RIGHT } else if (players[i].dir == 0){ $(".player[rel='"+ i +"']").css('background-image',img_r); $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); //BACK } else if (players[i].dir == 'b'){ $(".player[rel='"+ i +"']").css('background-image',img_back); $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); //FRONT } else { $(".player[rel='"+ i +"']").css('background-image',img_front); $(".player[rel='"+ i +"']").css('left', players[i].x ); $(".player[rel='"+ i +"']").css('top', players[i].y ); } //END MOV }
  • 13.
    Wait What AboutMy Player //GLOBALS --movement --leftKey --rightKey //MY PLAYER function myPlayer() { if ( movement && leftKey ) { player.x --; $('#player').css('left',player.x ); } else if ( movement && rightKey ) { player.x ++; $('#player').css('left',player.x ); } else { $('#player').css('left',player.x ); } //END FUNC }
  • 14.
    GRAVITY LOOP We use the global object “players{}” //GRAVITY function gravity() { //MY PLAYER if ( player.y !== floor_y ) { player.y ++; player.y ++; $('#player').css('top',player.y ); } //FOR LOOP for (var i in players) { //IN THE AIR if( i === now.core.clientId && players[i].y !== floor_y ) { players[i].y++; players[i].y++; $(".player[rel='"+ i +"']").css('top', players[i].y ); } else if ( i !== now.core.clientId && players[i].y !== players[i].flo) { players[i].y++; players[i].y++; $(".player[rel='"+ i +"']").css('top', players[i].y ); } else { //do nothing } } }
  • 15.
    FLOOR LOOP changing players floor position //FLOOR function floor(){ //FLOOR 1 if(player.y == floor1 && player.x <= 810 && player.x >= 100){ floor_y = floor1; player.y = floor1; } //FALLING if ( player.y == floor1 - 2 && player.x <= 810 && player.x >= 100) { floor_y = floor1; now.pullPlayers(); } //JUMPING if ( player.y == floor1 + 2) { now.pullPlayers(); } //END FUNC } **PULL PLAYERS** now.pullPlayers(now.core.clientId,player.x, player.y, direction, movement, floor_y,skin_num)
  • 16.
    LASERS LOOP VARIABLES var lasers = []; var other_lasers = []; var laser_v = 27; CLIENT $(window).keyup(function(e) { case 191: if(!$(‘.placeHolder’).is(“:focus”)) { now.fireLasers(now.core.clientId,player.x + 12, player.y + 12, direction); lasers.push([player.x + 12, player.y + 12, direction]); e.preventDefault(); } break; } SERVER //FIRE LASERS everyone.now.fireLasers = function(id,x,y,dir){ var laserUpdate = {}; laserUpdate = {id:id,x:x,y:y,dir:dir}; everyone.now.showLasers(laserUpdate); }; CLIENT //SHOW LASERS now.showLasers = function(laz) { if(laz.id != now.core.clientId) { other_lasers.push( [laz.x, laz.y, laz.dir] ); } } >>>> MOVE AND DRAW LASERS
  • 17.
    Function to DrawOur Lasers //LASERS function drawLasers (x, y, dir) { if( dir == 0 || dir == 1) { game.clearRect(x,y,-65,2); game.clearRect(x, y,65,2); game.fillStyle = '#f00'; game.fillRect(x, y,10,1); } else { game.clearRect(x,y,-2,65); game.clearRect(x, y,2,65); game.fillStyle = '#f00'; game.fillRect(x, y,1,10); } }
  • 18.
    MOVE LASERS function moveLasers(){ for(var i = 0; i < lasers.length; i++) { //IF WITHIN BOUNDS if(lasers[i][0] < player.x + w && lasers[i][0] > player.x - w && lasers[i][1] > 1500 ) { if(lasers[i][2] == 1) { lasers[i][0] -= laser_v; } else if(lasers[i][2] == 0){ lasers[i][0] += laser_v; } else { lasers[i][1] -= laser_v; } drawLasers(lasers[i][0], lasers[i][1],lasers[i][2]); } else if(lasers[i][0] > player.x + w + 1 || lasers[i][0] < player.x - w +1){ lasers.splice(i,1); } else if(lasers[i][1] < 0 ){ lasers.splice(i,1); } else { //do nothing } } }
  • 19.
    OTHER PLAYERS LASERS functionotherLasers() { for(var i = 0; i < other_lasers.length; i++) { if(other_lasers[i][0] < player.x + w && other_lasers[i][0] > player.x - w ) { if(other_lasers[i][2] == 1) { other_lasers[i][0] -= laser_v; } else if(other_lasers[i][2] == 0){ other_lasers[i][0] += laser_v; } else { other_lasers[i][1] -= laser_v; } drawLasers(other_lasers[i][0], other_lasers[i][1],other_lasers[i][2]); } else { other_lasers.splice(i,1); } } }