Search code examples
javascripthtml5-canvasrenderingjstilemap

Loading a tile map with better performance


So, I'm trying to create a game using javascript and canvas 2d api (without any external libraries/frameworks). I plan to keep it simple for now. However, while creating it, I realized that I don't understand some concepts and especially how to load my tiled based map (created with tiled editor) efficiently

I understand that for my small project it would be enough but if I wanted to load some bigger tile map in my game loop it could slow down the performance quite a bit. I found a pretty good solution when using the worker thread, but I don't know if that would solve the problem. I was thinking of something that would only load visible pixels on the screen, but again, I don't know if that would work and if it would be an ideal solution

any solution to solve this problem would help me to understand it better and I would be grateful for it


Solution

  • You should load your assets only once, before you need them (so at load is usually a good place).
    Then drawing the bitmap with the cropping options of drawImage() is generally good enough. If your tilemap is so big it's a problem to draw it all like that, you may consider splitting it in multiple smaller files, then it networking becomes an issue, and you are targeting only recent browsers, you can use the createImageBitmap method which allows to do the cropping directly, and will store only the cropped tile as bitmap, making it a faster asset to use in drawImage:

    (async () => {
      const blob = await fetch("https://upload.wikimedia.org/wikipedia/commons/6/68/BOE_tile_set.png")
        .then(resp => resp.ok && resp.blob());
      const tiles = [];
      const tileWidth = 28;
      const tileHeight = 36;
      for (let i=0; i<77; i++) {
        const x = i % 8;
        const y = Math.floor(i / 8);
        // create one ImageBitmap per tile
        const bmp = await createImageBitmap(blob, x * tileWidth, y * tileHeight, tileWidth, tileHeight);
        tiles.push({
          bmp,
          x: 0,
          y: 0,
          dirX: Math.random() * 2 - 1,
          dirY: Math.random() * 2 - 1
        });
      }
      
      const canvas = document.querySelector("canvas");
      const ctx = canvas.getContext("2d");
      draw();
      
      function draw() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        tiles.forEach((tile) => {
          tile.x = tile.x + tile.dirX;
          tile.y = tile.y + tile.dirY;
          if (tile.x < -tile.bmp.width) {
            tile.x = canvas.width;
          }
          if (tile.x > canvas.width) {
            tile.x = -tile.bmp.width;
          }
          if (tile.y < -tile.bmp.height) {
            tile.y = canvas.height;
          }
          if (tile.y > canvas.height + tile.height) {
            tile.y = -tile.bmp.height;
          }
          ctx.drawImage(tile.bmp, tile.x, tile.y);
        });
        requestAnimationFrame(draw);
      }
    
    })().catch(console.error);
    <canvas></canvas>