如何优化画布渲染以动态加载HTML5游戏世界?

时间:2016-02-22 20:19:20

标签: javascript html5 canvas html5-canvas web-worker

我一直在为自己的游戏开发等距游戏引擎。目前,它是一个大型,开放的世界,地图数据是从Node.js服务器动态检索的。

要了解我正在做什么......在大多数情况下,这是一个以瓷砖为基础的世界。因此每个地图都有最大数量的cols,rows(19),每个世界都有col,row(6)的最大地图数量。所以这是一张6x6的世界地图,每张地图包含19x19个图块。每当玩家移动到新的地图/区域时,客户端请求周围地图的3x3矩阵,其中心地图是玩家当前所在的地图。这部分非常优化。

然而,我的问题是找到一种优化画布到画布上的好方法。目前,我没有太多的延迟,但我也有一台快速的电脑,但我担心有时它可能会导致其他人滞后/混乱其他图形的渲染。

基本上,我现在如何使用它是从服务器发回数据的时候,它将每个地图以及它必须呈现的每个列/行的所有平铺图像添加到缓冲区中。游戏循环的每个循环,它基本上将25块瓷砖的一小部分渲染到特定地图的隐藏画布上。当所有请求的地图完成渲染(在几个游戏循环之后)时,摄像机将继续并将这些隐藏的地图合并到3x3矩阵的1个大地图画布中(通过从隐藏的画布切片部分并将它们合并到新的画布)。

理想情况下,我希望整个过程都是异步的。但我一直在调查网络工作者,显然他们不支持画布。有没有人想出一个类似的东西并保持优化的过程?

谢谢!

1 个答案:

答案 0 :(得分:0)

这是在每个帧中渲染19x19网格的示例。在每个帧中从右到左从上到下添加新的随机区块。网格以相同的顺序呈现,您可以看到这适用于重叠切片。

我认为保存每个磁贴并创建一个渲染整个网格的功能是最好的。因此,如果玩家在3x3周围区域获得更新,则下载并保留这些图块并重新渲染整个网格。

更新 我提供了一个消除透支和切换的功能。这可能会提高某些人的表现。它从下到上从左到右绘制。这将首先绘制重叠项目,并使用globalCompositeOperation" distination-over"告诉画布在添加新内容时单独留下现有像素。这意味着将像素放在画布上的工作量减少,因为它不会覆盖未使用的像素。



var cols = 19;
var tile_width = 32;
var rows = 19;
var tile_height = 16;
var y_offset = 64;

var h_tw = tile_width / 2;
var h_th = tile_height / 2;

var frames = 0;
var fps = "- fps";

setInterval(function(){
  fps = frames + " fps";
  frames = 0;
}, 1000);

var can = document.getElementById('tile');
var ctx = can.getContext('2d');

var wcan = document.getElementById('world');
var wctx = wcan.getContext('2d');
wcan.width = cols * tile_width;
wcan.height = rows * tile_height + y_offset;

var tiles = initTiles();


document.getElementById('toggle').addEventListener('click', function() {
  if (this.innerHTML == 'renderWorld') {
    renderFn = renderWorldNoOverdraw;
    this.innerHTML = "renderWorldNoOverdraw";
  } else {
    renderFn = renderWorld;
    this.innerHTML = "renderWorld";
  }
});

//renderWorld();
var ani_x = cols;
var ani_y = 0;
var renderFn = renderWorld;
ani();


function initTiles () {
  var tiles = [];
  
  for (var y = 0; y < rows; y++) {
    var row = [];
    for (var x = 0; x < cols; x++) {
      var can = document.createElement('canvas');
      can.width=tile_width;
      can.height=tile_height+y_offset;
      row[x]=can;
    }
    tiles[y] = row;
  }  
  return tiles;
}

function ani() {
  var can = tiles[ani_y][--ani_x]
  
  if (ani_x == 0) ani_x = cols, ani_y++;
  ani_y %= rows;
  var ctx = can.getContext('2d');
  randTile(can, ctx);
  
  renderFn();
  requestAnimationFrame(ani);
}

// renders from bottom left to right and skips
// drawing over pixels already present.
function renderWorldNoOverdraw() {
  frames++;
  wctx.clearRect(0,0,wcan.width,wcan.height);
  wctx.save();
  wctx.globalCompositeOperation = "destination-over";
  wctx.translate(0, y_offset);
  var x_off = 0;
  var y_off = 0;
  var y_off2 = 0;
  for (var y = rows; y--;) {
    x_off = (cols * h_tw)- ((rows-y) * h_tw);
    y_off = y * h_th + tile_height;
    y_off2 = y_off;
    for (var x = 0; x < cols; x++) {
      var can = tiles[y][x];
      wctx.drawImage(can, x_off, y_off2 + y_offset);
      y_off2 -= h_th;
      x_off += h_tw;
    }
  }
  wctx.translate(0,-y_offset);
  wctx.fillStyle = "#ddaadd";
  wctx.fillRect(0,0,wcan.width, wcan.height);
  wctx.restore();
  wctx.fillStyle= "black";
  wctx.fillText(fps, 10, 10);
}

function renderWorld() {
  frames++;
  wctx.fillStyle = "#CCEEFF";
  wctx.fillRect(0, 0, wcan.width, wcan.height);
  wctx.save();
  wctx.translate(0, y_offset);
  var x_off = 0;
  var y_off = 0;
  var y_off2 = 0;
  for (var y = 0; y < rows; y++) {
    x_off = (cols * h_tw) + (y * h_tw) - h_tw;
    y_off = y * h_th;
    y_off2 = y_off;
    for (var x = cols; x--;) {
      var can = tiles[y][x];
      wctx.drawImage(can, x_off, y_off2 - 64);
      y_off2 += h_th;
      x_off -= h_tw;
    }
    y_off += h_th;
    x_off -= h_tw;
  }
  wctx.restore();
    wctx.fillStyle= "black";
  wctx.fillText(fps, 10, 10);
}

function randTile(can, ctx) {
  var maxH = can.height - 24;
  var ranH = Math.floor(Math.random() * maxH);
  var h = Math.max(ranH, 1);
  ctx.clearRect(0, 0, can.width, can.height);
  ctx.beginPath();
  ctx.save();
  ctx.translate(0, can.height - 16);

  ctx.moveTo(0, 8);
  ctx.lineTo(16, 0);
  ctx.lineTo(32, 8);
  ctx.lineTo(16, 16);
  ctx.lineTo(0, 8);
  ctx.strokeStyle = "#333333";
  ctx.stroke();

  // random floor color
  var colors = ["#dd9933", "#22aa00", "#66cccc", "#996600"];
  ctx.fillStyle = colors[Math.floor(Math.random() * 4)];
  ctx.fill();

  // random building
  if (Math.floor(Math.random() * 8) == 0) {
    ctx.beginPath();
    ctx.moveTo(8, 8);
    ctx.lineTo(8, -h - 4);
    ctx.lineTo(16, -h);
    ctx.lineTo(16, 12);
    ctx.lineTo(8, 8);
    ctx.stroke();
    ctx.fillStyle = "#333333";
    ctx.fill();

    ctx.beginPath();
    ctx.moveTo(16, 12);
    ctx.lineTo(16, -h);
    ctx.lineTo(24, -h - 4);
    ctx.lineTo(24, 8);
    ctx.lineTo(16, 12);
    ctx.stroke();
    ctx.fillStyle = "#999999";
    ctx.fill()

    ctx.beginPath();
    ctx.moveTo(16, -h);
    ctx.lineTo(24, -h - 4);
    ctx.lineTo(16, -h - 8);
    ctx.lineTo(8, -h - 4);
    ctx.moveTo(16, -h);
    ctx.stroke();
    ctx.fillStyle = "#CCCCCC";
    ctx.fill()
  }
  ctx.restore();
}
&#13;
body {
  background-color: #444444;
}
&#13;
<button id="toggle">renderWorld</button><br/>
<canvas id='tile' width="32" height="32" style="display:none"></canvas>
<canvas id="world" width="608" height="368">
</canvas>
&#13;
&#13;
&#13;