spritecook-use-dual-grid-tilesets

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SpriteCook 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-tilesets

Concept

概念

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-right
Mask
0
renders nothing. Other masks map into the 4x4 atlas through this lookup:
ts
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
遮罩值
0
表示不渲染任何内容。其他遮罩值通过以下查找表映射到4x4的图集:
ts
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
    15
    as the filled interior tile and mask
    0
    as transparent/no draw.
  • 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位于绘制的逻辑单元格周围。
  • 对于像素艺术,需禁用平滑处理,使用最近邻缩放。
  • 将遮罩值
    15
    视为填充的内部tile,遮罩值
    0
    视为透明/不绘制。
  • 严格按照4x4网格、每个单元格尺寸与tile一致的方式使用生成的图集。
  • 请勿将该图集重新解读为普通的8邻域blob tileset;此映射是SpriteCook特有的双网格15片式布局。