繁体   English   中英

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

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

因此, 在这里我做了一个射击游戏,可以在我的PC上正常运行,但是对于互联网性能较差/计算机功能较弱的人(例如,在学校/我的几个朋友)来说,它的运行速度很慢,这是我的第一张画布游戏,所以我不确定通常的优化技巧。

我正在通过套接字在游戏循环中每秒发送60次客户端信息,然后将其余玩家信息(仅客户端需要的最低信息)发送给客户端。

抱歉,这个问题有点含糊,只想从根本上寻找优化的技巧。 如果需要更多代码,请问! 谢谢!

这是我的绘画功能:

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

优化图形和通讯

缺少一些重要信息

  • 玩家人数?
  • 每个玩家的子弹数量?
  • 您要发送给每台客户端计算机什么?
  • 每个客户发回什么?

我会做一些假设。

图像

查看您提供的代码,并假设最多只有10个左右的播放器带有六发子弹的播放器,您可能会因为在较慢的计算机上呈现完整页面大小(在1920到1080左右)而呈现出速度变慢

避免矢量绘图调用

您可以通过使用图像而不是canvas矢量绘制调用来改善渲染。

绘制旋转的图像

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

预渲染文字

将播放器名称渲染到屏幕外画布

例如

  // 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 : ?};

然后您可以在玩家图像上绘制玩家名称

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

简化

为了健康,我建议使用健康栏,而不是文字。

寻找替代品

对于网格绘图,网格线非常慢。 创建一个足够大的屏幕外画布,以绘制一个网格正方形。 在游戏开始时,从该画布上创建一个图案(只创建一次图案以节省周期)

然后,而不是清除屏幕,将填充设置为该图案,然后使用fillRect绘制网格图案。 您可以使矩形偏移以移动网格。

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

这比绘制每条线要快得多。

图像,粒子和东西

子弹。 只要保持转换简单,绘制许多小图像比许多线条更快。

通过检查树木是否在屏幕上,您可以对其进行一些优化,如果没有,请不要绘制它们。 如果大约有一半始终不在屏幕上,那么您将获得好处;如果大多数时间大部分都在屏幕上,则需要检查的额外代码将超过收益。 这也适用于子弹。


通讯

我假设您正在从服务器进行广播,每个客户端都会获得有关每个玩家,其位置,健康状况和项目符号以及一些其他游戏状态信息的持续的玩家数据流。

每个客户端还将返回到其他播放器的数据返回到服务器。

没有! 不要发送JSON

如果您通过JSON“ DONT !!!”进行序列化,我不知道您如何发送 带宽很饿。 将数据编码为最小数据大小。

EG玩家位置。

编码到最小可能的位数。

网格为5000 x 5000像素。 如果您将其作为JSON发送

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

最少为29个字节,即232位。 存储0到5000之间的值并四舍五入为一个像素是13位(8192个像素的范围)。 您可以将其加倍到14位,以获得一半的像素精度。 为了使事情变得更加简单,只需将坐标设置为16位即可。

编码发送

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;

解码

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

发送位置数据而不是JSON 29需要4个字节,这几乎节省了8倍的带宽。

对所有数据执行相同操作,将其编码为最小字节数。 不要发送代码中已经存在的结构化数据(如属性名称)。

不要发送可以计算的数据。

如果子弹很笨,一旦发射后一直沿直线移动,直到完成,您都不需要每帧发送该信息。 发送玩家已被解雇并解雇时玩家所在的位置,让所有客户端(和服务器)计算机创建并跟踪项目符号。

当子弹被击中时,玩家会将其发送到服务器,并确认该玩家击中并且射击的玩家都已发出信号,表明子弹已击中,并且如果您也在服务器上运行子弹,则与服务器上的游戏状态相匹配。

创建协议

不要一直发送所有数据。 将相关数据作为唯一数据包发送。 每个数据包都应以标头开头,只是一个8位数字,用于标识数据包中的数据类型。 数据包的类型,玩家被解雇,玩家被击中,玩家被移动,玩家已离开游戏。 等等...

优先考虑数据并设置带宽预算。 首先发送高优先级数据,如玩家pos,玩家命中;当预算中有空间时(例如总分或玩家x已被玩家y击中),则发送优先级较低的数据

我可以继续下去,但是我不知道您如何管理数据。 这些仅是建议,如果您运行的是千兆位线路,则JSON数据就可以了,原始的序列化数据转储将起作用。 如果某些玩家的线路速度较慢并与其他人共享带宽(导致可变的数据速率),则您必须减少每秒发送的字节数以容纳,或者如果连接速度太慢以致于将游戏的其余部分拉回原位,则只需终止连接即可。 。

暂无
暂无

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

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