简体   繁体   English

优化我的Node.js / Js / Socket射击游戏

[英]Optimising my Node.js/Js/Socket shooter game

So here I made a little shooter game just to play around with and on my pc it runs fine but for people with worse internet/less powerful computers (eg at school/few friends of mine) it is quite laggy and this is my first canvas game so I'm not to sure of the usual techniques of optimisation. 因此, 在这里我做了一个射击游戏,可以在我的PC上正常运行,但是对于互联网性能较差/计算机功能较弱的人(例如,在学校/我的几个朋友)来说,它的运行速度很慢,这是我的第一张画布游戏,所以我不确定通常的优化技巧。

I'm sending the clients information and then the rest of the players information(only the minimum that the client needs) to the client 60 times a second in my game loop through a socket. 我正在通过套接字在游戏循环中每秒发送60次客户端信息,然后将其余玩家信息(仅客户端需要的最低信息)发送给客户端。

Sorry for this question being a little vague, just looking for tips on optimisation basically. 抱歉,这个问题有点含糊,只想从根本上寻找优化的技巧。 If any more code is needed just ask! 如果需要更多代码,请问! Thanks! 谢谢!

Here is my draw function: 这是我的绘画功能:

  function draw() {
    ctx.clearRect(0, 0, canvas.width,canvas.height);
    //grey background
    ctx.fillStyle="rgba(128, 128, 128, 0.15)";
    ctx.fillRect(0, 0,canvas.width, canvas.height);
    //drawing background grid
    for(var pos = 25;pos<5000;pos+=25)
    {
      ctx.beginPath();
      ctx.strokeStyle = "rgba(128, 128, 128, 0.75)";
      ctx.lineWidth="1";
      ctx.moveTo(0, pos-player.y+pos);
      ctx.lineTo(canvas.width, pos-player.y+pos);
      ctx.stroke();
    }
    for(var pos = 25;pos<5000;pos+=25)
    {
      ctx.beginPath();
      ctx.strokeStyle = "rgba(128, 128, 128, 0.75)";
      ctx.lineWidth="1";
      ctx.moveTo(pos-player.x+pos, 0);
      ctx.lineTo(pos-player.x+pos, canvas.height);
      ctx.stroke();
    }
    //drawing the clients player
    if(player != '')
    {
      ctx.save();
      ctx.translate((window.innerWidth/2), (window.innerHeight/2));
      ctx.font = '11px Roboto';
      ctx.textAlign="center";
      ctx.fillStyle="black";
      ctx.fillText(player.health, 0, 42);
      ctx.rotate(player.look * (Math.PI / 180));
      var circle = new Path2D();
      circle.arc(0, 0, 30, 0, 2.5 * Math.PI);
      ctx.fillStyle="black";
      ctx.fill(circle);

      ctx.beginPath();
      ctx.strokeStyle="red";
      ctx.lineWidth="4";
      ctx.moveTo(0, -10);
      ctx.lineTo(0, -30);
      ctx.stroke();

      ctx.restore();
      ctx.save();
      ctx.translate((window.innerWidth/2), (window.innerHeight/2));
      ctx.rotate(0);

      ctx.font = '12pt Roboto';
      ctx.textAlign="center";
      ctx.fillStyle="white";
      ctx.strokeStyle="black";
      ctx.strokeText(player.name, 0, 4);
      ctx.fillText(player.name, 0, 4);


      ctx.restore();

      if(player.bullets != 'undefined')
      {
        for(var j = 0;j<player.bullets.length;j++)
        {
          ctx.save();
          ctx.translate((window.innerWidth/2)+(player.bullets[j].playerX - player.x), (window.innerHeight/2)+(player.bullets[j].playerY - player.y));
          ctx.rotate(player.bullets[j].attack * (Math.PI/180));
          ctx.fillStyle = 'black';
          ctx.fillRect(player.bullets[j].traveled, 0, 6, 2);
          ctx.restore();
          ctx.rotate(0);
        }
      }
      ctx.restore();
    }
    //drawing every other player
    for(var i = 0;i<playerArr.length;i++)
    {
      if(player.id  != playerArr[i].id)
      {
        ctx.save();
        ctx.translate((window.innerWidth/2)+(playerArr[i].x - player.x), (window.innerHeight/2)+(playerArr[i].y - player.y));

        ctx.font = '11px Roboto';
        ctx.textAlign="center";
        ctx.fillStyle="black";
        ctx.fillText(playerArr[i].health, 0, 42);

        ctx.rotate(playerArr[i].look * (Math.PI / 180));
        var circle = new Path2D();
        circle.arc(0, 0, 30, 0, 2.5 * Math.PI);
        ctx.fillStyle="black";
        ctx.fill(circle);

        ctx.beginPath();
        ctx.strokeStyle="red";
        ctx.lineWidth="4";
        ctx.moveTo(0, -10);
        ctx.lineTo(0, -30);
        ctx.stroke();

        ctx.restore();
        ctx.save();
        ctx.translate((window.innerWidth/2)+(playerArr[i].x - player.x), (window.innerHeight/2)+(playerArr[i].y - player.y));
        ctx.rotate(0);

        ctx.font = '12pt Roboto';
        ctx.textAlign="center";
        ctx.fillStyle="white";
        ctx.strokeStyle="black";
        ctx.strokeText(playerArr[i].name, 0, 4);
        ctx.fillText(playerArr[i].name, 0, 4);

        ctx.restore();

        for(var j = 0;j<playerArr[i].bullets.length;j++)
        {
          ctx.save();
          ctx.translate((window.innerWidth/2)+(playerArr[i].bullets[j].playerX - player.x), (window.innerHeight/2)+(playerArr[i].bullets[j].playerY - player.y))
          ctx.rotate(playerArr[i].bullets[j].attack * (Math.PI/180));
          ctx.fillStyle = 'black';
          ctx.fillRect(playerArr[i].bullets[j].traveled, 0, 6, 2);
          ctx.restore();
          ctx.rotate(0);
        }
      }
    }
    //drawing trees
    for(var i = 0;i<treeArr.length;i++)
    {
      ctx.save();
      ctx.translate((window.innerWidth/2)+(treeArr[i].x - player.x), (window.innerHeight/2)+(treeArr[i].y - player.y));
      ctx.drawImage(tree, 0, 0, 108, 108);
      ctx.restore();
    }
    if(playing)
    {
      requestAnimationFrame(draw);
    }
  }

Optimizing graphics and comms 优化图形和通讯

There is some important information missing 缺少一些重要信息

  • Number of players? 玩家人数?
  • Number of bullets per player? 每个玩家的子弹数量?
  • What are you sending to each client machine? 您要发送给每台客户端计算机什么?
  • What is each client sending back? 每个客户发回什么?

I will make some assumptions. 我会做一些假设。

Graphics 图像

Looking at the code you gave and assuming that you have only at most 10 or so players with half a dozen bullets you can expect some slowdown due to rendering on slower machines if they are full page size (near 1920 by 1080) 查看您提供的代码,并假设最多只有10个左右的播放器带有六发子弹的播放器,您可能会因为在较慢的计算机上呈现完整页面大小(在1920到1080左右)而呈现出速度变慢

Avoid vector draw calls 避免矢量绘图调用

You can improve the rendering by using images rather than the canvas vector draw calls. 您可以通过使用图像而不是canvas矢量绘制调用来改善渲染。

To draw an image rotated 绘制旋转的图像

function drawCircle(player){
   ctx.setTransform(1,0,0,1,player.x,player.y);
   ctx.rotate(player.look * (Math.PI / 180));
   ctx.drawImage(playerImage, - playerImage.width / 2, - playerImage.height / 2);
}

Pre render text 预渲染文字

Render player names to a off screen canvas 将播放器名称渲染到屏幕外画布

Eg 例如

  // do the following at start of game 
  var names = document.createElement("canvas");
  // for each players name render that onto the names canvas
  // record the bounding box of the name on that canvas
  // eg playerArr[i].nameImage = {x :?, y : ?, w : ?, h : ?};

Then you can draw the players name over the player image 然后您可以在玩家图像上绘制玩家名称

   function drawName(player){
       ctx.setTransform(1,0,0,1,player.x,player.y);
       var loc = playerArr[i].nameImage;
       ctx.drawImage(names, loc.x, loc.y, loc.w, loc.h, -loc.w / 2, -loc.h /2, loc.w, loc.h)
   }

Simplify 简化

For the health I would suggest a health bar rather than text. 为了健康,我建议使用健康栏,而不是文字。

Find alternatives 寻找替代品

For the grid drawing grid lines is very slow. 对于网格绘图,网格线非常慢。 Create a off screen canvas just big enough to draw one grid square. 创建一个足够大的屏幕外画布,以绘制一个网格正方形。 At the start of the game create a pattern from that canvas (only create the pattern once to save cycles) 在游戏开始时,从该画布上创建一个图案(只创建一次图案以节省周期)

Then rather than clear the screen set the fill to that pattern and use fillRect to draw the grid pattern. 然后,而不是清除屏幕,将填充设置为该图案,然后使用fillRect绘制网格图案。 You can offset the rect to move the grid. 您可以使矩形偏移以移动网格。

   gridSize = 25;
   ctx.fillStyle = gridPattern;
   fillRect(
       -gridSize + (player.x % gridSize),
       -gridSize + (playeryx % gridSize),
       innerWidth + gridSize * 2,
       innerHeight * 2
    );

This will be a lot quicker than drawing each line. 这比绘制每条线要快得多。

Images, particles, and stuff 图像,粒子和东西

Bullets. 子弹。 Drawing many small images is quicker than many lines, as long as you keep the transformation simple. 只要保持转换简单,绘制许多小图像比许多线条更快。

And the trees you can optimize them a little by checking if they are on screen or not, if not don`t draw them. 通过检查树木是否在屏幕上,您可以对其进行一些优化,如果没有,请不要绘制它们。 If about half are always off screen you will get a benefit, if most are on screen most of the time, the extra code to check will outweigh the benefit. 如果大约有一半始终不在屏幕上,那么您将获得好处;如果大多数时间大部分都在屏幕上,则需要检查的额外代码将超过收益。 This applies to the bullets as well. 这也适用于子弹。


Communication 通讯

I am assuming you are broadcasting from a server, each client gets a constant stream of player data for each player, their position, health and bullets, and some additional game state info. 我假设您正在从服务器进行广播,每个客户端都会获得有关每个玩家,其位置,健康状况和项目符号以及一些其他游戏状态信息的持续的玩家数据流。

Each client also returns to the server their data that is broadcast to the other players. 每个客户端还将返回到其他播放器的数据返回到服务器。

No! 没有! don't send JSON 不要发送JSON

I don't know how you are sending, if you are serializing via JSON "DONT!!!" 如果您通过JSON“ DONT !!!”进行序列化,我不知道您如何发送 it is very band width hungry. 带宽很饿。 Encode the data into the minimum data size. 将数据编码为最小数据大小。

EG the player position. EG玩家位置。

Encode to minimum possible bit count. 编码到最小可能的位数。

The grid is 5000 by 5000 pixels. 网格为5000 x 5000像素。 If you send that as JSON 如果您将其作为JSON发送

"player" : {
     "x" : 1283,
     "y" : 2345,
 } 

That is at min 29 bytes, or 232 bits. 最少为29个字节,即232位。 To store value between 0 and 5000 rounded to a pixel is 13 bits (range of 8192 pixels). 存储0到5000之间的值并四舍五入为一个像素是13位(8192个像素的范围)。 You can double that to 14 bits to get half pixel precision. 您可以将其加倍到14位,以获得一半的像素精度。 To make things even simpler and just make coordinates 16 bits. 为了使事情变得更加简单,只需将坐标设置为16位即可。

To encode for sending 编码发送

var playerData = new Uint16Array(packetSize)
// data is ordered with first word the x coord, second is y and so on
// pixel step to 1/8th
playerData[0] = Math.floor(player.x * 8) & 0xFFFF;
playerData[1] = Math.floor(player.y * 8) & 0xFFFF;

To decode 解码

player.x = socketData[0] / 8; 
player.x = socketData[1] / 8; 

That is 4 bytes to send position data rather than JSONs 29 a saving of almost 8 times the bandwidth. 发送位置数据而不是JSON 29需要4个字节,这几乎节省了8倍的带宽。

Do the same with all data, encode it into the minimum number of bytes. 对所有数据执行相同操作,将其编码为最小字节数。 Dont send structural data like property names, that is already in the code. 不要发送代码中已经存在的结构化数据(如属性名称)。

Dont send data that can be calculated. 不要发送可以计算的数据。

If a bullets is dumb and once fired traveled in a straight line till done you dont need to send that information every frame. 如果子弹很笨,一旦发射后一直沿直线移动,直到完成,您都不需要每帧发送该信息。 Send that a player has fired and the position the player was when firing, let all the clients (and server) computer create and track the bullet. 发送玩家已被解雇并解雇时玩家所在的位置,让所有客户端(和服务器)计算机创建并跟踪项目符号。

When a bullet hits a player send that to the server and confirm that the player hit and the player that shot both have signaled the bullet has hit and that matches the game state on the sever if you are running one on the server as well.. 当子弹被击中时,玩家会将其发送到服务器,并确认该玩家击中并且射击的玩家都已发出信号,表明子弹已击中,并且如果您也在服务器上运行子弹,则与服务器上的游戏状态相匹配。

Create a protocol 创建协议

Dont send all the data all the time. 不要一直发送所有数据。 Send relevant data as unique packets. 将相关数据作为唯一数据包发送。 Each packet of data should start with a header, just a 8bit number that identifies the type of data that is in the packet. 每个数据包都应以标头开头,只是一个8位数字,用于标识数据包中的数据类型。 Types of packets, Player fired, player hit, player moved, player has left the game. 数据包的类型,玩家被解雇,玩家被击中,玩家被移动,玩家已离开游戏。 etc... 等等...

Give data a priority and set a bandwidth budget. 优先考虑数据并设置带宽预算。 High priority data is sent first, like player pos, player hit, lower priority data is sent when there is room in the budget, like total score or that player x has been hit by player y 首先发送高优先级数据,如玩家pos,玩家命中;当预算中有空间时(例如总分或玩家x已被玩家y击中),则发送优先级较低的数据

I could go on and on but I have no clue how you are managing the data. 我可以继续下去,但是我不知道您如何管理数据。 These are only suggestions and if you are running over gigabit lines JSON data is just fine, and raw serialized data dumps will work. 这些仅是建议,如果您运行的是千兆位线路,则JSON数据就可以了,原始的序列化数据转储将起作用。 If some players are on slower lines and sharing bandwidth with other people (resulting in variable data rates) you will have to reduce the bytes sent per second to accommodate, or just terminate the connection if so slow that it pulls the rest of the game back. 如果某些玩家的线路速度较慢并与其他人共享带宽(导致可变的数据速率),则您必须减少每秒发送的字节数以容纳,或者如果连接速度太慢以致于将游戏的其余部分拉回原位,则只需终止连接即可。 。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM