如何在js画布上仅绘制tilemap的可见部分? [英] How to draw only visible part of the tilemap on js canvas?

查看:134
本文介绍了如何在js画布上仅绘制tilemap的可见部分?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I created simple tilemap using Tiled (3200 x 3200 pixels). I loaded it on my canvas using this library

我每个seocnd绘制整个tilemap 3200 x 3200 60次。
我试图四处走动,它工作正常。顺便说一句,我使用 ctx.translate 在画布上移动。我在我自己的职能中包含了这个 / a>

I draw entire tilemap 3200 x 3200 60 times per seocnd. I tried to move around and it works fine. Btw, I move around canvas using ctx.translate. I included this in my own function

但是当我在Tiled(32000 x 32000像素)中创建更大的地图时 - 我得到了一个非常冻结的页面。我无法快速移动,我认为大概有10 fps

But when I created bigger map in Tiled ( 32000 x 32000 pixels ) - I got a very freezing page. I couldn't move around fast, I think there was about 10 fps

那么如何修复呢?我必须每秒调用 drawTiles()函数60次。但有没有办法只绘制瓷砖的可见部分?喜欢只画我在屏幕上看到的东西(0,0,monitorWidth,monitorHeight我猜)

So how to fix it? I have to call drawTiles() function 60 times per second. But is there any way to draw only visible part of the tile? Like draw only what I see on my screen (0, 0, monitorWidth, monitorHeight I guess)

谢谢你

推荐答案

绘制一个大的tileset



如果你有一个大的tile集并且只在画布中看到它的一部分你只需要计算画布左上角的图块以及适合画布的横向和向下的图块数量。

Drawing a large tileset

If you have a large tile set and only see part of it in the canvas you just need to calculate the tile at the top left of the canvas and the number of tiles across and down that will fit the canvas.

然后绘制适合的画面的方形数组帆布。

Then draw the square array of tiles that fit the canvas.

在示例中,图块集是1024 x 1024个图块( worldTileCount = 1024 ),每个图块是64 64像素 tileSize = 64 ,使总游戏区域为65536像素正方形

In the example the tile set is 1024 by 1024 tiles (worldTileCount = 1024), each tile is 64 by 64 pixels tileSize = 64, making the total playfield 65536 pixels square

设置左上方图块的位置由变量 worldX worldY

The position of the top left tile is set by the variables worldX, worldY

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

}



setTransform and绝对坐标。



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

setTransform and absolute coordinates.

Use absolute coordinates makes everything simple.

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

Use the canvas context setTransform to set the world position and then each tile can be drawn at its own coordinate.

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

这样,如果您在位置51023,34256处有一个角色,您可以在该位置绘制它。

That way if you have a character at position 51023, 34256 you can just draw it at that location.

   playerX = 51023;
   playerY = 34256;
   ctx.drawImage(myPlayerImage,playerX,playerY);

如果你想要相对于玩家的瓷砖地图,那么只需将世界位置设置为画布的一半尺寸向上和向左加一个瓷砖以确保重叠

If you want the tile map relative to the player then just set the world position to be half the canvas size up and to the left plus one tile to ensure overlap

   playerX = 51023;
   playerY = 34256;

   worldX = playerX - canvas.width / 2 - tileWidth;
   worldY = playerY - canvas.height / 2 - tileHeight;



通过65536像素图块地图演示大65536.



如果你有马匹,那么在60fps时可以处理更大的没有任何帧率损失。 (使用此方法的地图大小限制大约为4,000,000,000乘4,000,000,000像素(32位整数坐标))

Demo of large 65536 by 65536 pixel tile map.

At 60fps if you have the horses and can handle much much bigger without any frame rate loss. (map size limit using this method is approx 4,000,000,000 by 4,000,000,000pixels (32 bit integers coordinates))

var ctx = canvas.getContext("2d");

// wait for code under this to setup
setTimeout(() => {


  var w = canvas.width;
  var h = canvas.height;
  var cw = w / 2; // center 
  var ch = h / 2;



  // create tile map
  const worldTileCount = 1024;
  const tileMap = new Uint8Array(worldTileCount * worldTileCount);
  
  // add random tiles
  doFor(worldTileCount * worldTileCount, i => {
    tileMap[i] = randI(1, tileCount);
  });
  
  // this is the movement direction of the map
  var worldDir = Math.PI / 4;



/* =======================================================================
   Drawing the tileMap 
========================================================================*/


  var worldX = 512 * tileSize;
  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
    const tx = worldX / s | 0; // get the top left tile
    const ty = worldY / s | 0;
    const tW = (canvas.width / s | 0) + 2; // get the number of tiles to fit canvas
    const tH = (canvas.height / s | 0) + 2;
    // set the location
    ctx.setTransform(1, 0, 0, 1, -worldX | 0, -worldY | 0);
    // Draw the tiles
    for (var y = 0; y < tH; y += 1) {
      for (var x = 0; x < tW; x += 1) {
        const i = tx + x + (ty + y) * c;
        const tindx = tileMap[i] === undefined ? 6 : tileMap[i];
        imageTools.drawSpriteQuick(tileSet, tindx, (tx + x) * s, (ty + y) * s);
      }
    }

  }


 
  //==============================================================
  // main render function
  function update(timer) {
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    if (w !== innerWidth || h !== innerHeight) {
      cw = (w = canvas.width = innerWidth) / 2;
      ch = (h = canvas.height = innerHeight) / 2;
    } else {
      ctx.clearRect(0, 0, w, h);
    }
    
    // Move the map
    var speed = Math.sin(timer / 10000) * 8;
    worldX += Math.cos(worldDir) * speed;
    worldY += Math.sin(worldDir) * speed;
    worldDir += rand(-0.1, 0.1);
    
    // Draw the map
    drawWorld();

    requestAnimationFrame(update);
  }
  requestAnimationFrame(update);
}, 0);




/*===========================================================================
  CODE FROM HERE DOWN UNRELATED TO THE ANSWER
  
  ===========================================================================*/






const imageTools = (function() {
  // This interface is as is. No warenties no garenties, and NOT to be used comercialy
  var workImg, workImg1, keep; // for internal use
  keep = false;
  var tools = {
    canvas(width, height) { // create a blank image (canvas)
      var c = document.createElement("canvas");
      c.width = width;
      c.height = height;
      return c;
    },
    createImage: function(width, height) {
      var i = this.canvas(width, height);
      i.ctx = i.getContext("2d");
      return i;
    },
    drawSpriteQuick: function(image, spriteIndex, x, y) {
      var w, h, spr;
      spr = image.sprites[spriteIndex];
      w = spr.w;
      h = spr.h;
      ctx.drawImage(image, spr.x, spr.y, w, h, x, y, w, h);
    },
    line(x1, y1, x2, y2) {
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
    },
    circle(x, y, r) {
      ctx.moveTo(x + r, y);
      ctx.arc(x, y, r, 0, Math.PI * 2);
    },
  };
  return tools;
})();

const doFor = (count, cb) => {
  var i = 0;
  while (i < count && cb(i++) !== true);
}; // the ; after while loop is important don't remove
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const seededRandom = (() => {
  var seed = 1;
  return {
    max: 2576436549074795,
    reseed(s) {
      seed = s
    },
    random() {
      return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max
    }
  }
})();
const randSeed = (seed) => seededRandom.reseed(seed | 0);
const randSI = (min, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const tileSize = 64;
const tileCount = 7;

function drawGrass(ctx, c1, c2, c3) {
  const s = tileSize;
  const gs = s / (8 * c3);
  ctx.fillStyle = c1;
  ctx.fillRect(0, 0, s, s);

  ctx.strokeStyle = c2;
  ctx.lineWidth = 2;
  ctx.lineCap = "round";
  ctx.beginPath();
  doFor(s, i => {
    const x = rand(-gs, s + gs);
    const y = rand(-gs, s + gs);
    const x1 = rand(x - gs, x + gs);
    const y1 = rand(y - gs, y + gs);
    imageTools.line(x, y, x1, y1);
    imageTools.line(x + s, y, x1 + s, y1);
    imageTools.line(x - s, y, x1 - s, y1);
    imageTools.line(x, y + s, x1, y1 + s);
    imageTools.line(x, y - s, x1, y1 - s);
  })
  ctx.stroke();
}

function drawTree(ctx, c1, c2, c3) {

  const seed = Date.now();
  const s = tileSize;
  const gs = s / 2;
  const gh = gs / 2;
  ctx.fillStyle = c1;
  ctx.strokeStyle = "#000";
  ctx.lineWidth = 2;
  ctx.save();
  ctx.shadowColor = "rgba(0,0,0,0.5)";
  ctx.shadowBlur = 4;
  ctx.shadowOffsetX = 8;
  ctx.shadowOffsetY = 8;
  randSeed(seed);
  ctx.beginPath();
  doFor(18, i => {
    const ss = 1 - i / 18;
    imageTools.circle(randS(gs - gh * ss, gs + gh * ss), randS(gs - gh * ss, gs + gh * ss), randS(gh / 4, gh / 2));
  })
  ctx.stroke();
  ctx.fill();
  ctx.restore();
  ctx.fillStyle = c2;
  ctx.strokeStyle = c3;
  ctx.lineWidth = 2;
  ctx.save();

  randSeed(seed);
  ctx.beginPath();
  doFor(18, i => {
    const ss = 1 - i / 18;
    imageTools.circle(randS(gs - gh * ss, gs + gh * ss) - 2, randS(gs - gh * ss, gs + gh * ss) - 2, randS(gh / 4, gh / 2) / 1.6);
  })
  ctx.stroke();
  ctx.fill();
  ctx.restore();


}


const tileRenders = [
  (ctx) => {
    drawGrass(ctx, "#4C4", "#4F4", 1)
  },
  (ctx) => {
    drawGrass(ctx, "#644", "#844", 2)
  },
  (ctx) => {
    tileRenders[0](ctx);
    drawTree(ctx, "#480", "#8E0", "#7C0")
  },
  (ctx) => {
    tileRenders[1](ctx);
    drawTree(ctx, "#680", "#AE0", "#8C0")
  },
  (ctx) => {
    drawGrass(ctx, "#008", "#00A", 4)
  },
  (ctx) => {
    drawGrass(ctx, "#009", "#00C", 4)
  },
  (ctx) => {
    drawGrass(ctx, "#00B", "#00D", 4)
  },
]
const tileSet = imageTools.createImage(tileSize * tileCount, tileSize);
const ctxMain = ctx;
ctx = tileSet.ctx;
tileSet.sprites = [];
doFor(tileCount, i => {
  x = i * tileSize;
  ctx.save();
  ctx.setTransform(1, 0, 0, 1, x, 0);
  ctx.beginPath();
  ctx.rect(0, 0, tileSize, tileSize);
  ctx.clip()
  if (tileRenders[i]) {
    tileRenders[i](ctx)
  }
  tileSet.sprites.push({
    x,
    y: 0,
    w: tileSize,
    h: tileSize
  });
  ctx.restore();
});
ctx = ctxMain;

canvas {
  position: absolute;
  top: 0px;
  left: 0px;
}

body {
  background: #49c;
}

<canvas id="canvas"></canvas>

这篇关于如何在js画布上仅绘制tilemap的可见部分?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆