node.js多人html5游戏中的延迟越来越高

时间:2021-01-22 18:58:06

I created a webpage using node.js, express js, socket.io, and jquery. It is a simple game wherein players join, give themselves a name, and then move themselves(a square) around a canvas on the page. What I've noticed is: when more people join the game and move around, there is enough lag to make the game unplayable (at only three people connected. Two people doesn't lag it much). I can't figure out if this is server-side lag or client-side (this is my first project dealing with multiplayer). I am doing all of the location calculations on the server, and sending an array of all the player objects back to every socket so that each client can render all of the players. The client only sends input and draws the players.

我使用node.js创建了一个网页,表达了js,socket.io和jquery。这是一个简单的游戏,其中玩家加入,给自己一个名字,然后在页面上的画布周围移动自己(正方形)。我注意到的是:当更多的人加入游戏并四处走动时,就会有足够的滞后让游戏无法播放(只有三个人连接。两个人并没有多少时间)。我无法弄清楚这是服务器端延迟还是客户端(这是我的第一个处理多人游戏的项目)。我正在服务器上进行所有位置计算,并将所有玩家对象的数组发送回每个套接字,以便每个客户端都可以呈现所有玩家。客户端仅发送输入并绘制玩家。

This is the client side script for the game. This is where I handle the 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');
	});
});

Below, I have included the app.js file (the server-side script) for my game. I am not sure exactly where the problem is, so I figured I'd just post the whole thing.

下面,我已经为我的游戏添加了app.js文件(服务器端脚本)。我不确定问题究竟在哪里,所以我想我只是发布了整个事情。

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();
  });
});

--------------------------------------------------------- EDIT ------------------------------------------------------------------ I took Ruslanas Balčiūnas's suggestion for moving the sv_update handler in the client out of the render function. This stops the player from lagging, but the new problem is that the server doesn't send enough updates for all the players to move fluidly on any given client. Each client seems to be running smoothly for just themselves, but the other clients see them as choppy/lagging.

-------------------------------------------------- -------编辑------------------------------------------ ------------------------我采用了RuslanasBalčiūnas的建议,将客户端中的sv_update处理程序移出render函数。这会阻止玩家滞后,但新问题是服务器没有为所有玩家发送足够的更新,以便在任何给定的客户端上流畅地移动。每个客户似乎都在顺利运行,但其他客户认为它们不稳定/滞后。

Here is the updated code:

这是更新的代码:

CLIENT:

$(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');
	});
});

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();
});

Your help would be greatly appreciated :)

非常感谢您的帮助:)

1 个解决方案

#1


Move below code from render function in client side.

在客户端的渲染功能下移动代码。

//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;
});

#1


Move below code from render function in client side.

在客户端的渲染功能下移动代码。

//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;
});