Chapter 37

オートタイル

miku
miku
2021.11.15に更新

マップデータが入った二次元配列を用意して、その配列の値をもとに一つのタイルマップ画像を使用してタイルを配置すると上記のようになる。
(0 = 非表示, 1 = 表示)

ここでオートタイルというアルゴリズムを使用すると並べ方に応じてタイル間の繋がりを自然に見せることができる。

アルゴリズム

タイルの周りにある4方向、つまり上下左右に 1, 2, 4, 8 の番号を振っておく。これはどのように振ってもらっても構わない。

解説では下記のように方向を振る。

方向
1
2
4
8

二次元配列の全ての要素を走査する。その際、自分の周りにある4方向の要素に 1 が入っていたら、先程決めた方向の値に変換して足し合わせる。

下記が値の例となる。

  • 上にタイルがある場合: 1
  • 右にタイルがある場合: 2
  • 上と右にタイルがある場合: 1 + 2 = 3

すべてのパターンは下記のとおり。

方向 計算式
無し 0 0
1 1
2 2
上 + 右 1 + 2 3
4 4
上 + 下 1 + 4 5
右 + 下 2 + 4 6
上 + 右 + 下 1 + 2 + 4 7
8 8
上 + 左 1 + 8 9
右 + 左 2 + 8 10
上 + 右 + 左 1 + 2 + 8 11
下 + 左 4 + 8 12
上 + 下 + 左 1 + 4 + 8 13
右 + 下 + 左 2 + 4 + 8 14
上 + 右 + 下 + 左 1 + 2 + 4 + 8 15

上記の16パターンに対応したタイル画像を用意する。左から順番に値0の画像、値1の画像、値2の画像…と並べていく。

例えば左側2番目、つまり値1に対応した画像は、上方向だけ繋がっているパターンなので、上方向だけ開けた画像にしなければならない。

あとはタイル画像から値に応じたものを切り出して描画する。

オートタイルのコード例

const CellType = {
  None: 0,
  Wall: 1,
};

const chipWidth = 8;
const chipHeight = 8;
const tileWidth = 40;
const tileHeight = 40;
const cellWidth = 4;
const cellHeight = 5;
let chip;
let map;

function preload() {
  chip = loadImage("0.png");
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  noSmooth();

  map = [];
  for (let i = 0; i < cellWidth * cellHeight; i++) {
    map[i] = random([CellType.None, CellType.Wall]);
  }

  for (let y = 0; y < cellHeight; y++) {
    for (let x = 0; x < cellWidth; x++) {
      if (getCell(x, y) === CellType.Wall) {
        updateTile(x, y);
      }
    }
  }
}

function getCell(x, y) {
  return map[y * cellWidth + x];
}

function updateTile(x, y) {
  let index = 0;
  if (0 <= y - 1 && getCell(x, y - 1) === CellType.Wall) {
    index += 1;
  }
  if (x + 1 < cellWidth && getCell(x + 1, y) === CellType.Wall) {
    index += 2;
  }
  if (y + 1 < cellHeight && getCell(x, y + 1) === CellType.Wall) {
    index += 4;
  }
  if (0 <= x - 1 && getCell(x - 1, y) === CellType.Wall) {
    index += 8;
  }

  copy(chip, index * chipWidth, 0, chipWidth, chipHeight, x * tileWidth, y * tileHeight, tileWidth, tileHeight);
}

作例の全体のコードは上記の通り。

map = [];
for (let i = 0; i < cellWidth * cellHeight; i++) {
  map[i] = random([CellType.None, CellType.Wall]);
}

二次元配列を用意して、01 のどちらかをランダムに格納する。作例では 0None1Wall という風に定数化している。

for (let y = 0; y < cellHeight; y++) {
  for (let x = 0; x < cellWidth; x++) {
    if (getCell(x, y) === CellType.Wall) {
      updateTile(x, y);
    }
  }
}

配列の中身を一つ一つ見ていき、中身が Wall ならば描画対象にする。あとの処理は updateTile() に任せるため座標を指定する。

let index = 0;
if (0 <= y - 1 && getCell(x, y - 1) === CellType.Wall) {
  index += 1;
}
if (x + 1 < cellWidth && getCell(x + 1, y) === CellType.Wall) {
  index += 2;
}
if (y + 1 < cellHeight && getCell(x, y + 1) === CellType.Wall) {
  index += 4;
}
if (0 <= x - 1 && getCell(x - 1, y) === CellType.Wall) {
  index += 8;
}

上下左右を調べ、配列外ではなく、かつ壁であった場合は index に方向固有の数を足していく。

copy(chip, index * chipWidth, 0, chipWidth, chipHeight, x * tileWidth, y * tileHeight, tileWidth, tileHeight);

あとは index に応じたタイル画像を描画すればいい。