canvas 上的图块无法正确显示

Tiles won't display correctly on canvas

我正在开发一个需要平铺背景的迷你游戏,我从 png tileSet(16x16 tiles)中挑选的瓷砖。即使图块是 16x16,它们在 spriteSheet 的 canvas 上显示为 32x32 图块(请参见下面的代码)。 问题是,当我人为地放大图块的大小时,它占用了旁边图块的一小部分。图片可能比文字更能描述它:

你可以看到在水的部分,有一条粉红色的线,它不应该在这里(我检查过图块集上没有分离,我相信 0,0 在正确的位置)。我还尝试使用瓷砖的原始尺寸(16x16 而不是 32x32)作为背景,没有看到粉红色的线条,灰色网格也消失了:

所以我猜它来自 32x32,但我不知道如何修复它... 这是我用来加载 tileset 和显示背景的代码:

loadImage('/img/dungeon.png')
.then(image => {
  const sprites = new SpriteSheet(image, 16, 16);
  sprites.define('stone_floor', 1, 0, 32, 32);
  sprites.define('water', 2, 7, 32, 32);
  sprites.define('wall_top', 2, 0, 32, 32);
  sprites.define('void', 0, 0, 32, 32);
  sprites.define('stone_floor&water', 3, 7, 32, 32);

  loadLevel('1-2')
  .then(level => {
    drawBackground(level.background, context, sprites);
  })
});

精灵表class:

class SpriteSheet {
  constructor(image, width, height, offset = 0){
    this.image = image;
    this.width = width;
    this.height = height;
    this.offset = offset;
    this.tiles = new Map();
  }
  define(name, x, y, imgw, imgh){
    const buffer = document.createElement('canvas');
    buffer.width = imgw;
    buffer.height = imgh;
    buffer
        .getContext('2d')
        .drawImage(this.image,
                  x * this.width + x*this.offset, y * this.height + y*this.offset,
                  this.width, this.height,
                  0, 0, imgw, imgh);
    this.tiles.set(name, buffer);
  }
  draw(name, context, x, y){
    const buffer = this.tiles.get(name);
    context.drawImage(buffer, x, y);
  }
  drawTile(name, context, x, y){
    const buffer = this.tiles.get(name);
    context.drawImage(buffer, x * buffer.width, y * buffer.height);
  }
}

PS:该项目高度基于 MethMethod 的教程:video

基于@Blindman67 的评论:

The bleed from neighboring tiles is due to the bilinear filtering when you scale the tile up. The simple solution is to turn of bilinear filtering for the tiles. ctx.imageSmoothingEnabled = false; If you wish to keep the filtering then the only option is to increase the tile size to 18 by 18 adding a border around the tile to match the edge colour. You still only render the 16*16 tile but the filtering will sample from the border pixels rather than the neighboring tile.

我能够解决我的问题,尽管建议的第一个选项不起作用(关闭图像平滑)。我设法修改了我的 spriteSheet class 以便它首先创建一个 18x18 像素的缓冲图像,方法是将 1 个 18x18 的图块叠加在同一个 16x16 的图块上并居中:

defineTile(name, x, y, imgw, imgh) {
    const img_buffer = document.createElement('canvas');
    img_buffer.width = this.width + 4;
    img_buffer.height = this.height + 4;
    img_buffer
      .getContext('2d')
      .drawImage(this.image,
        x * this.width + x * this.offset, y * this.height + y * this.offset,
        this.width, this.height,
        0, 0, this.width + 4, this.height + 4);
    img_buffer
      .getContext('2d')
      .drawImage(this.image,
        x * this.width + x * this.offset, y * this.height + y * this.offset,
        this.width, this.height,
        2, 2, this.width, this.height);

    const buffer = document.createElement('canvas');
    buffer.width = imgw;
    buffer.height = imgh;
    buffer
      .getContext('2d')
      .drawImage(img_buffer,
        2, 2,
        this.width, this.height,
        0, 0, imgw, imgh);
    this.tiles.set(name, buffer);
  }  

尽管我不知道这是否是最优化的方法,但它做得很好!当然,我愿意接受任何可以使这部分变得更好的建议,但我认为问题已解决。