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

所以在这里,我做了一个小射手游戏只是为了玩和在我的电脑上运行良好,但与互联网/function较差的电脑(例如在学校/我的朋友很less)的人是相当滞后,这是我的第一个canvas所以我不确定通常的优化技术。

我通过套接字将客户端信息以及其他玩家信息(只有客户端需要的最低限度)发送给客户端60秒钟。

对不起,这个问题有点模糊,基本上只是寻找优化技巧。 如果需要更多的代码,只需要问! 谢谢!

这是我的绘图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); } } 

优化graphics和通信

有一些重要的信息缺失

  • 玩家人数?
  • 每名球员的子弹数量?
  • 你发送给每个客户端机器是什么?
  • 每个客户回送什么?

我会做一些假设。

图像

看看你给出的代码,假设你最多只有10个左右的玩家,如果他们是全页面大小(接近1920×1080),你可以预期由于在较慢的机器上渲染而放慢速度。

避免vector绘制调用

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

绘制旋转的图像

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

预渲染文本

将玩家名称渲染到屏幕外的canvas上

例如

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

简化

为了健康,我会build议一个健康栏,而不是文字。

寻找替代品

对于网格绘制网格线是非常缓慢的。 创build一个足够大的画面canvas来绘制一个方格。 在游戏开始时,从该canvas创build一个模式(只创build一次模式来保存循环)

然后,而不是清除屏幕设置填充到该模式,并使用fillRect绘制网格模式。 你可以抵消rect来移动网格。

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

这将比绘制每一行更快。

图像,粒子和东西

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

而且树木可以通过检查它们是否在屏幕上来优化它们,如果不是这样的话。 如果大约一半的屏幕总是在屏幕外,您将获得一定的收益,如果大部分时间屏幕上显示大部分时间,额外的代码将会超过收益。 这也适用于子弹。


通讯

我假设你正在从服务器进行广播,每个客户端都会为每个玩家获取一个持续不断的玩家数据stream,他们的位置,健康和子弹以及一些额外的游戏状态信息。

每个客户端还向服务器返回他们向其他玩家广播的数据。

没有! 不要发送JSON

我不知道你是如何发送,如果你是通过JSON序列化“不要! 这是很饿的带宽。 将数据编码为最小数据大小。

EG玩家位置。

编码到最小可能的位数。

网格是5000×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; 

这是4个字节来发送位置数据,而不是JSON 29,节省了将近8倍的带宽。

对所有的数据做同样的事情,把它编码成最小的字节数。 不要发送像属性名称这样的结构化数据,这已经在代码中了。

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

如果一颗子弹是愚蠢的,而且一旦被射击直行,直到完成,你不需要每帧发送信息。 发送一个玩家已经开火并且玩家在射击时的位置,让所有客户(和服务器)计算机创build并跟踪子弹。

当一个子弹击中一个玩家发送到服务器,并确认玩家击中和射击的玩家都发出信号,子弹已经命中,并匹配服务器上的游戏状态,如果你正在服务器上运行一个。

创build一个协议

不要一直发送所有的数据。 发送相关数据作为唯一的数据包 每个数据包应该以一个头部开始,只是一个8位的数字,用来标识数据包中的数据types。 数据包types,玩家解雇,玩家击中,玩家移动,玩家已经离开游戏。 等等…

给数据优先级并设置带宽预算。 高优先级的数据首先被发送,比如玩家pos,玩家命中,当预算有空间的时候发送低优先级的数据,比如总分或者玩家x已经被玩家y

我可以继续下去,但我不知道如何pipe理数据。 这些只是build议,如果你正在运行千兆线JSON数据就好了,原始序列化的数据转储将工作。 如果一些玩家的线路较慢并与其他人共享带宽(导致可变的数据速率),则必须减less每秒发送的字节以适应,或者如果速度太慢以致于将其余的游戏拉回。