如何在JS画布上仅绘制tilemap的可见部分?

Dan*_*nev 0 javascript canvas html5-canvas

我使用Tiled(3200 x 3200像素)创建了简单的tilemap。我使用此库将其加载到画布上

我每个秒钟绘制整个图块3200 x 3200 60次。我试图四处走动,但效果很好。顺便说一句,我使用ctx.translate在画布上移动。我将此包含在自己的函数中

但是,当我在Tiled中创建较大的地图(32000 x 32000像素)时,页面变得非常冻结。我无法快速走动,我认为大约有10 fps

那么如何解决呢?我必须每秒调用drawTiles()函数60次。但是,有什么方法可以只绘制瓷砖的可见部分吗?就像只画出我在屏幕上看到的内容(我猜是0、0,monitorWidth,monitorHeight)

谢谢

Bli*_*n67 5

绘制大图块

如果您有一个大的图块集,并且仅在画布中看到它的一部分,则只需要计算画布左上角的图块以及适合该图块的上下的图块数即可。

然后绘制适合画布的正方形瓷砖阵列。

在此示例中,图块集为1024 x 1024图块(worldTileCount = 1024),每个图块为64 x 64像素tileSize = 64,从而使总的游戏场为65536像素

左上图块的位置由变量设置worldXworldY

画瓷砖的功能

// val | 0 is the same as Math.floor(val)

var worldX = 512 * tileSize;  // pixel position of playfield
var worldY = 512 * tileSize;

function drawWorld(){
  const c = worldTileCount; // get the width of the tile array
  const s = tileSize;       // get the tile size in pixels

  // get the tile position
  const tx = worldX / s | 0;  // get the top left tile
  const ty = worldY / s | 0;

  // get the number of tiles that will fit the canvas
  const tW = (canvas.width / s | 0) + 2;  
  const tH = (canvas.height / s | 0) + 2;

  // set the location. Must floor to pixel boundary or you get holes
  ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);  

  // Draw the tiles across and down
  for(var y = 0; y < tH; y += 1){
     for(var x = 0; x < tW; x += 1){
         // get the index into the tile array for the tile at x,y plus the topleft tile
         const i = tx + x + (ty + y) * c;

         // get the tile id from the tileMap. If outside map default to tile 6
         const tindx = tileMap[i] === undefined ? 6 : tileMap[i];

         // draw the tile at its location. last 2 args are x,y pixel location
         imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
     }
  }

}
Run Code Online (Sandbox Code Playgroud)

setTransform和绝对坐标。

使用绝对坐标可使一切变得简单。

使用画布上下文setTransform设置世界位置,然后可以在其各自的坐标上绘制每个图块。

   // set the world location. The | 0 floors the values and ensures no holes
   ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);  
Run Code Online (Sandbox Code Playgroud)

这样,如果在位置51023、34256处有一个字符,则可以在该位置绘制它。

   playerX = 51023;
   playerY = 34256;
   ctx.drawImage(myPlayerImage,playerX,playerY);
Run Code Online (Sandbox Code Playgroud)

如果您想要相对于玩家的地图贴图,则只需将世界位置设置为画布大小的一半,向左增加画布的一半,以确保重叠

   playerX = 51023;
   playerY = 34256;

   worldX = playerX - canvas.width / 2 - tileWidth;
   worldY = playerY - canvas.height / 2 - tileHeight;
Run Code Online (Sandbox Code Playgroud)

65536 x 65536像素平铺地图的演示。

如果您拥有60 fps的马匹,并且可以处理更大的帧而没有任何帧速率损失。(使用此方法的地图尺寸限制约为4,000,000,000 x 4,000,000,000像素(32位整数坐标))


更新15/5/2019重新抖动

评论指出,地图滚动时会有些抖动

我进行了一些更改,以极大地简化了随机路径的产生,每240帧(60 fps时为4秒)向外翻转一次。还添加了帧率降低器,如果您在画布上单击并按住鼠标按钮,则帧速率会降低到正常值的1/8,以便更容易看到抖动

产生抖动的原因有两个。

时间误差

第一个也是最不重要的一点,是通过传递给update函数requestAnimationFrame的时间,该间隔不理想,并且由于该时间而导致的舍入误差加剧了对齐问题。

为了减少时间误差,我将移动速度设置为恒定间隔,以最小化帧之间的舍入误差漂移。

将图块与像素对齐

产生抖动的主要原因是必须在像素边界上渲染图块。如果不是这样,则混叠错误将在图块之间创建可见的接缝。

要查看差异,请单击左上方的按钮以打开或关闭像素对齐。

要进行平滑滚动(子像素定位),请将地图绘制到与像素对齐的屏幕外画布上,然后将该画布渲染到显示画布上,并添加子像素偏移。使用画布将获得最佳效果。为了更好,您将需要使用webGL

更新结束

// val | 0 is the same as Math.floor(val)

var worldX = 512 * tileSize;  // pixel position of playfield
var worldY = 512 * tileSize;

function drawWorld(){
  const c = worldTileCount; // get the width of the tile array
  const s = tileSize;       // get the tile size in pixels

  // get the tile position
  const tx = worldX / s | 0;  // get the top left tile
  const ty = worldY / s | 0;

  // get the number of tiles that will fit the canvas
  const tW = (canvas.width / s | 0) + 2;  
  const tH = (canvas.height / s | 0) + 2;

  // set the location. Must floor to pixel boundary or you get holes
  ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);  

  // Draw the tiles across and down
  for(var y = 0; y < tH; y += 1){
     for(var x = 0; x < tW; x += 1){
         // get the index into the tile array for the tile at x,y plus the topleft tile
         const i = tx + x + (ty + y) * c;

         // get the tile id from the tileMap. If outside map default to tile 6
         const tindx = tileMap[i] === undefined ? 6 : tileMap[i];

         // draw the tile at its location. last 2 args are x,y pixel location
         imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
     }
  }

}
Run Code Online (Sandbox Code Playgroud)
   // set the world location. The | 0 floors the values and ensures no holes
   ctx.setTransform(1,0,0,1,-worldX | 0,-worldY | 0);  
Run Code Online (Sandbox Code Playgroud)
   playerX = 51023;
   playerY = 34256;
   ctx.drawImage(myPlayerImage,playerX,playerY);
Run Code Online (Sandbox Code Playgroud)