spritecook-use-dual-grid-tilesets
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpriteCook Dual-Grid 15-Piece Tilesets
SpriteCook 双网格15片式Tilesets
Use this skill when implementing a top-down 15-piece SpriteCook tileset in a game engine or custom renderer.
This skill is about using a generated tileset. For generation, use .
spritecook-generate-tilesets当你在游戏引擎或自定义渲染器中实现自上而下的15片式SpriteCook tileset时,可使用本技能。
本技能专注于已生成tileset的使用。如需生成tileset,请使用。
spritecook-generate-tilesetsConcept
概念
A dual-grid tileset stores terrain as logical painted cells, then renders visible tiles between those cells. Each rendered tile is chosen from the four logical cells around it.
For each rendered tile, sample:
- top-left logical cell
- top-right logical cell
- bottom-left logical cell
- bottom-right logical cell
That four-cell pattern becomes a 4-bit mask. The mask chooses one frame from the 4x4 15-piece atlas.
双网格tileset将地形存储为逻辑绘制单元格,然后在这些单元格之间渲染可见的tile。每个待渲染的tile由其周围的四个逻辑单元格决定。
对于每个待渲染的tile,需采样以下四个单元格:
- 左上逻辑单元格
- 右上逻辑单元格
- 左下逻辑单元格
- 右下逻辑单元格
这四个单元格的状态会形成一个4位遮罩(mask)。该遮罩将从4x4的15片式图集(atlas)中选择一帧图像。
Mask Order
遮罩顺序
Use SpriteCook's current mask bit order:
ts
let mask = 0
if (filled(x - 1, y - 1)) mask |= 1 // top-left
if (filled(x, y - 1)) mask |= 2 // top-right
if (filled(x - 1, y )) mask |= 4 // bottom-left
if (filled(x, y )) mask |= 8 // bottom-rightMask renders nothing. Other masks map into the 4x4 atlas through this lookup:
0ts
const frameByMask = [
-1,
15,
8,
9,
0,
11,
14,
7,
13,
4,
1,
10,
3,
2,
5,
6,
]The atlas cell is:
ts
const frame = frameByMask[mask]
if (frame < 0) return null
const atlasColumn = frame % 4
const atlasRow = Math.floor(frame / 4)请使用SpriteCook当前的遮罩位顺序:
ts
let mask = 0
if (filled(x - 1, y - 1)) mask |= 1 // top-left
if (filled(x, y - 1)) mask |= 2 // top-right
if (filled(x - 1, y )) mask |= 4 // bottom-left
if (filled(x, y )) mask |= 8 // bottom-right遮罩值表示不渲染任何内容。其他遮罩值通过以下查找表映射到4x4的图集:
0ts
const frameByMask = [
-1,
15,
8,
9,
0,
11,
14,
7,
13,
4,
1,
10,
3,
2,
5,
6,
]图集单元格的计算方式如下:
ts
const frame = frameByMask[mask]
if (frame < 0) return null
const atlasColumn = frame % 4
const atlasRow = Math.floor(frame / 4)Renderer Pseudocode
渲染器伪代码
ts
type CellMap = boolean[] // width * height logical terrain cells
function cellIndex(x: number, y: number, columns: number) {
return y * columns + x
}
function isFilled(cells: CellMap, x: number, y: number, columns: number, rows: number) {
return x >= 0 && y >= 0 && x < columns && y < rows && cells[cellIndex(x, y, columns)] === true
}
function dualGridMask(cells: CellMap, x: number, y: number, columns: number, rows: number) {
let mask = 0
if (isFilled(cells, x - 1, y - 1, columns, rows)) mask |= 1
if (isFilled(cells, x, y - 1, columns, rows)) mask |= 2
if (isFilled(cells, x - 1, y, columns, rows)) mask |= 4
if (isFilled(cells, x, y, columns, rows)) mask |= 8
return mask
}
function atlasCellForMask(mask: number) {
const frameByMask = [-1, 15, 8, 9, 0, 11, 14, 7, 13, 4, 1, 10, 3, 2, 5, 6]
const frame = frameByMask[Math.max(0, Math.min(15, Math.floor(mask)))]
if (frame < 0) return null
return { column: frame % 4, row: Math.floor(frame / 4) }
}
function renderDualGrid(ctx, atlasImage, cells: CellMap, columns: number, rows: number, tileSize: number) {
for (let y = 0; y <= rows; y += 1) {
for (let x = 0; x <= columns; x += 1) {
const mask = dualGridMask(cells, x, y, columns, rows)
const atlasCell = atlasCellForMask(mask)
if (!atlasCell) continue
ctx.drawImage(
atlasImage,
atlasCell.column * tileSize,
atlasCell.row * tileSize,
tileSize,
tileSize,
x * tileSize,
y * tileSize,
tileSize,
tileSize,
)
}
}
}ts
type CellMap = boolean[] // width * height logical terrain cells
function cellIndex(x: number, y: number, columns: number) {
return y * columns + x
}
function isFilled(cells: CellMap, x: number, y: number, columns: number, rows: number) {
return x >= 0 && y >= 0 && x < columns && y < rows && cells[cellIndex(x, y, columns)] === true
}
function dualGridMask(cells: CellMap, x: number, y: number, columns: number, rows: number) {
let mask = 0
if (isFilled(cells, x - 1, y - 1, columns, rows)) mask |= 1
if (isFilled(cells, x, y - 1, columns, rows)) mask |= 2
if (isFilled(cells, x - 1, y, columns, rows)) mask |= 4
if (isFilled(cells, x, y, columns, rows)) mask |= 8
return mask
}
function atlasCellForMask(mask: number) {
const frameByMask = [-1, 15, 8, 9, 0, 11, 14, 7, 13, 4, 1, 10, 3, 2, 5, 6]
const frame = frameByMask[Math.max(0, Math.min(15, Math.floor(mask)))]
if (frame < 0) return null
return { column: frame % 4, row: Math.floor(frame / 4) }
}
function renderDualGrid(ctx, atlasImage, cells: CellMap, columns: number, rows: number, tileSize: number) {
for (let y = 0; y <= rows; y += 1) {
for (let x = 0; x <= columns; x += 1) {
const mask = dualGridMask(cells, x, y, columns, rows)
const atlasCell = atlasCellForMask(mask)
if (!atlasCell) continue
ctx.drawImage(
atlasImage,
atlasCell.column * tileSize,
atlasCell.row * tileSize,
tileSize,
tileSize,
x * tileSize,
y * tileSize,
tileSize,
tileSize,
)
}
}
}Practical Notes
实用注意事项
- Render one more tile column and row than the logical map size because rendered tiles sit around painted logical cells.
- For pixel art, disable smoothing and use nearest-neighbor scaling.
- Treat mask as the filled interior tile and mask
15as transparent/no draw.0 - Use the generated atlas exactly as a 4x4 grid of equal tile-sized cells.
- Do not reinterpret the sheet as a normal 8-neighbor blob tileset; this mapping is specifically SpriteCook's dual-grid 15-piece layout.
- 渲染的tile列数和行数要比逻辑地图尺寸多1,因为渲染的tile位于绘制的逻辑单元格周围。
- 对于像素艺术,需禁用平滑处理,使用最近邻缩放。
- 将遮罩值视为填充的内部tile,遮罩值
15视为透明/不绘制。0 - 严格按照4x4网格、每个单元格尺寸与tile一致的方式使用生成的图集。
- 请勿将该图集重新解读为普通的8邻域blob tileset;此映射是SpriteCook特有的双网格15片式布局。