所以here我做了一个小小的射击游戏只是为了在我的电脑上玩它运行正常,但对于网络较差/功能较弱的电脑(例如在学校/我的几个朋友),这是相当的滞后,这是我的第一个画布游戏,所以我不确定通常的优化技术。
我通过套接字向游戏循环中的客户端发送客户信息,然后将其余的玩家信息(仅客户需要的最小信息)发送给客户端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);
}
}
答案 0 :(得分:2)
缺少一些重要信息
我会做一些假设。
看一下你给出的代码,并假设你最多只有10个左右的子弹,如果它们是整页尺寸(接近1920×1080),那么由于在较慢的机器上渲染会导致一些减速/ p>
您可以使用图像而不是画布矢量绘制调用来改善渲染。
绘制旋转图像
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进行序列化,我不知道你是怎么发送的;不要!#34;&#34;它的带宽非常宽。将数据编码为最小数据大小。
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数据就可以了,原始序列化数据转储将起作用。如果某些播放器处于较慢线路并与其他人共享带宽(导致可变数据速率),则必须减少每秒发送的字节数以适应,或者只是如果速度太慢以至于将其余的游戏拉回来终止连接