Increasingly high latency in node.js multiplayer html5 game

I have created a webpage using node.js, express js, socket.io and jquery. It's a simple game in which players join, give themselves a name, and then move (square) around a canvas on a page. What I noticed is that when more people join the game and move, there is enough lag to make the game unplayable (only three people are connected to each other. Two people are not far behind). I can't figure out if this is server side or client side (this is my first multiplayer project). I am doing all the location calculations on the server and sending an array of all player objects back to each socket so that each client can display all the players. The client only sends input and attracts players.

This is a client side script for the game. This is where I handle input and rendering.

$(document).ready(function()
{
	var socket = io.connect();

	var canvas = document.getElementById("canvas_html");
	var ctx = canvas.getContext("2d");
  
	canvas.width = 512;
	canvas.height = 480;
  
	document.body.appendChild(canvas);

	var player = 
	{
		id: '',
		is_it: false,
		x: canvas.width / 2,
		y: canvas.height / 2,
		velx: 0,
		vely: 0
	}
    
    //tell the server to initialize this client as a new player
	socket.emit('init_client', player);
    
	var client_player_list = [];
    
    //receive a list of the player objects from the server
	socket.on('load_players', function(players)
	{
		client_player_list = players;
	});

	var keysDown = {};

	addEventListener('keydown', function(e)
	{
		keysDown[e.keyCode] = true;
	}, false);

	addEventListener('keyup', function(e)
	{
		delete keysDown[e.keyCode];
	}, false);

	//take input from keys and send input to server
	var update = function()
	{
		if(87 in keysDown)//player holding w
			socket.emit('up');
		if(83 in keysDown)//player holding s
			socket.emit('down');
		if(65 in keysDown)//player holding a
			socket.emit('left');
		if(68 in keysDown)//player holding d
			socket.emit('right');
		if(74 in keysDown)//player holding j
			socket.emit('tag');
	};

	//render all players, update players when server updates
	var render = function()
	{
		//ctx.clearColor = "rgba(0, 0, 0, .3)";
		ctx.clearRect( 0, 0, canvas.width, canvas.height);
		ctx.fillStyle = "#079641";
		ctx.textAlign = 'center';
      
        //loop through the players array and render each one
		for(var i = 0; i < client_player_list.length; i++)
		{
            //if the player is_it, render them as red
			if(client_player_list[i].is_it)
			{	
				ctx.fillStyle = "#8F0E0E";
				ctx.fillRect(client_player_list[i].x, client_player_list[i].y, 20, 20);
                
                //draw the players name above them
				ctx.fillStyle = "#FFF";
				ctx.font="15px Arial";
				ctx.fillText(client_player_list[i].name, client_player_list[i].x + 8, client_player_list[i].y - 3);
				continue;
			}
            
            //if the player !is_it, render them as green
            ctx.fillStyle = "#079641";
			ctx.fillRect(client_player_list[i].x, client_player_list[i].y, 20, 20);
            
            //draw the players name above them
			ctx.fillStyle = "#FFF";
			ctx.font="15px Arial";
			ctx.fillText(client_player_list[i].name, client_player_list[i].x + 8, client_player_list[i].y - 3);
		}
         
        //when the server sends an update, replace the current players array with the one that the server just sent
		socket.on('sv_update', function(players)
		{
			client_player_list = players;
		});
	};

	//main loop
	var main = function()
	{
		setInterval(function()
		{
			update();
			render();
		}, 1000/60);
	};

	main();

	//trigger the disconnect event when a page refreshes or unloads
	$(window).bind('beforeunload', function()
	{
		socket.emit('disconnect');
	});
});
      

Run codeHide result


Below I have included an app.js file (server side script) for my game. I don't know exactly where the problem is, so I figured I'd just post the whole thing.

var express = require('express');
var http = require('http');
var io = require('socket.io', { rememberTransport: false, transports: ['WebSocket', 'Flash Socket', 'AJAX long-polling'] });

var app = express();
var server = http.createServer(app);
server.listen(8080);

app.use(express.static('public'));

io = io.listen(server);

//Declare variables for working with the client-side
var player_speed = 5;
var player_size = 20;
var vel_increment = 0.5;
var canvas_height = 480;
var canvas_width = 512;

//Declare list of players connected
var players = [];

io.sockets.on('connection', function(socket)
{
  var socket_id = socket.id;
  var player_index, player_exists = false;
  var this_player;

  var check_bounds = function()
  {
    //Keep player in the canvas
    if(this_player.y < 0)
        this_player.y = 0;
    if(this_player.y + player_size > canvas_height)
        this_player.y = canvas_height - player_size;

    if(this_player.x < 0)
      this_player.x = 0;
    if(this_player.x + player_size > canvas_width)
      this_player.x = canvas_width - player_size;

    //Keep velocity between -5 and 5
    if(this_player.vely > player_speed)
      this_player.vely = player_speed;
    if(this_player.velx > player_speed)
      this_player.velx = player_speed;

    if(this_player.vely < -player_speed)
      this_player.vely = -player_speed;
    if(this_player.velx < -player_speed)
      this_player.velx = -player_speed;
  };

  var sv_update = function()
  {
    io.sockets.emit('sv_update', players);
    if(player_exists)
    {
      if(players.length == 1)
        this_player.is_it = true;
      check_bounds();
    }
  };
  
  //When a client connects, add them to players[]
  //Then update all clients
  socket.on('init_client', function(player)
  {
    player.id = socket.id;
    players.push(player);

    for(var i = 0; i < players.length; i++)
      if(players[i].id == socket_id)
        player_index = i;
    player_exists = true;
    this_player = players[player_index];

    sv_update();
    socket.emit('load_players', players);

    console.log(players);
  });

  //====================CHAT==========================//
  var address = socket.request.connection.remoteAddress;

  socket.on('new user', function(data, callback)
  {
    if(player_exists)
    {
      this_player.name = data;
      console.log(address + " has connected as '" + data + "'.");
      
      callback();
    }
  });

  socket.on('send message', function(data)
  {
    io.sockets.emit('broadcast', this_player.name, data);
  });
  //====================CHAT==========================//
  
  //if player is_it and is within another player, hitting 'j' will make the other player is_it. 
  socket.on('tag', function()
  {
    if(player_exists)
    {
      for(var i = 0; i < players.length; i++)
      {
        if((this_player.x + player_size >= players[i].x && this_player.x + player_size <= players[i].x + player_size )|| 
          (this_player.x <= players[i].x + player_size && this_player.x + player_size >= players[i].x))
          if((this_player.y + player_size >= players[i].y && this_player.y + player_size <= players[i].y + player_size )|| 
          (this_player.y <= players[i].y + player_size && this_player.y + player_size >= players[i].y))
          {
            if(this_player.is_it)
            {
              this_player.is_it = false;
              players[i].is_it = true;
            }
          }
      }
      sv_update();
    }
  });

  //Gather key input from users...
  socket.on('up', function()
  {
    if(player_exists)
    {
      this_player.y -= player_speed;

      sv_update();
    }
  });

  //Gather key input from users...
  socket.on('down', function()
  {
    if(player_exists)
    {
      this_player.y += player_speed;

      sv_update();
    }
  });

  //Gather key input from users...
  socket.on('left', function()
  {
    if(player_exists)
    {
     this_player.x -= player_speed;

      sv_update();
    }
  });

  //Gather key input from users...
  socket.on('right', function()
  {
    if(player_exists)
    {
      this_player.x += player_speed;

      sv_update();
    }
  });

  //When a player disconnects, remove them from players[]
  //Then update all clients
  socket.on('disconnect', function()
  {
    for(var i = 0; i < players.length; i++)
    {
      if(players[i].id == socket.id)
      {
        players.splice(i, 1);
      }
    }

    sv_update();
  });
});
      

Run codeHide result


----------------------------------------------- --- ------- EDIT --------------------------------------- --- ------------------------ I accepted Ruslana Balchunas' proposal to move the sv_update handler in the client from the render function. This stops the player from lagging behind, but a new problem is that the server is not sending enough updates for all players to move smoothly on any given client. Each client seems to be working smoothly for themselves, but other clients see volatility / lag in them.

Here's the updated code:

CUSTOMER:

$(document).ready(function()
{
	var socket = io.connect();

	var canvas = document.getElementById("canvas_html");
	var ctx = canvas.getContext("2d");
	canvas.width = 512;
	canvas.height = 480;

	document.body.appendChild(canvas);

	var player = 
	{
		id: '',
		name: '',
		is_it: false,
		x: canvas.width / 2,
		y: canvas.height / 2,
		velx: 0,
		vely: 0
	};

	var client_player_list = [];
	socket.on('load_players', function(players)
	{
		client_player_list = players;
	});

	var keysDown = {};

	addEventListener('keydown', function(e)
	{
		keysDown[e.keyCode] = true;
	}, false);

	addEventListener('keyup', function(e)
	{
		delete keysDown[e.keyCode];
	}, false);

	//take input from keys and send input to server
	var update = function()
	{
		if(87 in keysDown)//player holding w
			socket.emit('input', 'up');
		if(83 in keysDown)//player holding s
			socket.emit('input', 'down');
		if(65 in keysDown)//player holding a
			socket.emit('input', 'left');
		if(68 in keysDown)//player holding d
			socket.emit('input', 'right');
		if(74 in keysDown)
			socket.emit('input', 'tag');
	};

	//render all players, update players when server updates
	var render = function()
	{
		//ctx.clearColor = "rgba(0, 0, 0, .3)";
		ctx.clearRect( 0, 0, canvas.width, canvas.height);
		ctx.fillStyle = "#079641";
		ctx.textAlign = 'center';

		for(var i = 0; i < client_player_list.length; i++)
		{
			if(client_player_list[i].is_it)
				ctx.fillStyle = "#8F0E0E";
			else
				ctx.fillStyle = "#079641";

			ctx.fillRect(client_player_list[i].x, client_player_list[i].y, 25, 25);

			ctx.fillStyle = "#FFF";
			ctx.font="15px Arial";
			ctx.fillText(client_player_list[i].name, client_player_list[i].x + 8, client_player_list[i].y - 3);
		}
	};

	socket.on('sv_update', function(players)
	{
		client_player_list = players;
	});

	//main loop
	var main = function()
	{
		setInterval(function()
		{
			update();
			render();
		}, 1000/60);
	};

	main();

	//---- Chat stuff ----
	var toggle = 1;

	$('#users').fadeIn(1000);
	$('#name_field').focus();

	$('#user_form').submit(function(e)
	{
		console.log('ezpz');
		e.preventDefault();
		socket.emit('init_client', player, $('#name_field').val(), function()
		{
			$('#users').fadeOut('slow');
			window.setTimeout(function(){$('#chat').fadeIn('slow');$('#canvas_html').fadeIn('slow');$('#info').fadeIn('slow')}, 1000);
		});
	});

	$('#desk').submit(function(e)
	{
		e.preventDefault();
		socket.emit('send message', $('#message').val());
		$('#message').val('');
	});

	socket.on('broadcast', function(name, data)
	{
		$('#message_window').append('<p class="p' + toggle + '">' + name + ": " + data + '</p>');
		$('#message_window')[0].scrollTop = $('#message_window')[0].scrollHeight;

		if(toggle == 1)
			toggle = 2;
		else
			toggle = 1;
	});

	//trigger the disconnect event when a page refreshes or unloads
	$(window).bind('beforeunload', function()
	{
		socket.emit('disconnect');
	});
});
      

Run codeHide result


SERVER:

var express = require('express');
var http = require('http');
var io = require('socket.io', { rememberTransport: false, transports: ['WebSocket', 'Flash Socket', 'AJAX long-polling'] });

var app = express();
var server = http.createServer(app);
server.listen(8080);

app.use(express.static('public'));

io = io.listen(server);

//Declare variables for working with the client-side
var player_speed = 5;
var player_size = 25;
var canvas_height = 480;
var canvas_width = 512;

//Declare list of players connected
var players = [];

io.sockets.on('connection', function(socket)
{
  var socket_id = socket.id;
  var player_index, this_player, player_exists = false;

  //When a client connects, add them to players[]
  //Then update all clients
  socket.on('init_client', function(player, name, callback)
  {
    player.id = socket.id;
    players.push(player);

    for(var i = 0; i < players.length; i++)
      if(players[i].id == socket_id)
        player_index = i;
    player_exists = true;
    this_player = players[player_index];

    this_player.name = name;
    callback();

    sv_update();
    socket.emit('load_players', players);
  });

  socket.on('send message', function(data)
  {
    io.sockets.emit('broadcast', this_player.name, data);
  });

  //Gather key input from users...
  socket.on('input', function(key)
  {
    if(player_exists)
    {
      if(key == 'up')
        this_player.y -= player_speed;
      else if(key == 'down')
        this_player.y += player_speed;
      else if(key == 'left')
        this_player.x -= player_speed;
      else if(key == 'right')
        this_player.x += player_speed;
      else if(key == 'tag')
        for(var i = 0; i < players.length; i++)
          if(can_tag(players[i]))
          {
            this_player.is_it = false;
            players[i].is_it = true;
          }

      sv_update();
    }
  });

  //When a player disconnects, remove them from players[]
  //Then update all clients
  socket.on('disconnect', function()
  {
    for(var i = 0; i < players.length; i++)
      if(players[i].id == socket.id)
        players.splice(i, 1);

    sv_update();
  });

  var check_bounds = function()
  {
    //Keep player in the canvas
    if(this_player.y < 0)
        this_player.y = 0;
    if(this_player.y + player_size > canvas_height)
        this_player.y = canvas_height - player_size;

    if(this_player.x < 0)
      this_player.x = 0;
    if(this_player.x + player_size > canvas_width)
      this_player.x = canvas_width - player_size;
  };

  var sv_update = function()
  {
    io.sockets.emit('sv_update', players);
    if(player_exists)
    {
      if(players.length == 1)
        this_player.is_it = true;
      check_bounds();
    }
  };

  var can_tag = function(target)
  {
    if(this_player.x < target.x + player_size && this_player.x + player_size > target.x && this_player.y < target.y + player_size && player_size + this_player.y > target.y && this_player.is_it)
      return true;
  }

  sv_update();
});
      

Run codeHide result


Your help would be greatly appreciated :)

+3


source to share


1 answer


Move below code from client side render function.



//when the server sends an update, replace the current players array with the one that the server just sent
socket.on('sv_update', function(players)
{
    client_player_list = players;
});

      

0


source







All Articles