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);
}
尽管我不知道这是否是最优化的方法,但它做得很好!当然,我愿意接受任何可以使这部分变得更好的建议,但我认为问题已解决。
我正在开发一个需要平铺背景的迷你游戏,我从 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);
}
尽管我不知道这是否是最优化的方法,但它做得很好!当然,我愿意接受任何可以使这部分变得更好的建议,但我认为问题已解决。