概要
セル・オートマトン式に洞窟のようなマップを生成していくアルゴリズムを紹介します。
手順
(黒ピクセル(1)は壁、白ピクセル(0)は通路)
まず、マップを表す二次元配列を用意し、40%の確率で壁、60%の確率で通路を作るコードを記述します。
マップの周りには壁を敷き詰めておきます。
まず、マップを表す二次元配列を用意し、40%の確率で壁、60%の確率で通路を作るコードを記述します。
マップの周りには壁を敷き詰めておきます。
周りの壁以外の全タイルを走査し、
- 自分のタイルを含む、周りの3*3ピクセルで壁の数が幾つあるか
- 自分のタイルを含む、周りの5*5ピクセルで壁の数が幾つあるか
- (ただしマップ配列外や、5*5ピクセルの四隅は含めないとする)
を計算しておきます。
- 3*3の範囲内で壁の数が5つ以上あった
- 5*5の範囲内で壁の数が2つ以下しかなかった
のどちらかの条件を満たしていれば、現在走査しているタイルを壁に変換します。
ただし、変換を行うのは全てのタイル走査が終わった後です。
なので、別のtemp配列などを用意してそこに変換した値を入れておき、走査が終わったあとに値を移し替えるといいでしょう。
なので、別のtemp配列などを用意してそこに変換した値を入れておき、走査が終わったあとに値を移し替えるといいでしょう。
この処理を何回か繰り返せば、孤立した壁が少なくなり、壁が多いところの隙間に新しい壁が埋められて、洞窟のようなマップが出来上がります。
注意点
- 初期化時の40%の確率で壁にする
- 3*3の範囲内で壁の数が5つ以上あった
- 5*5の範囲内で壁の数が2つ以下しかなかった
の、(40%, 5つ, 2つ)の値は参考例なので、自分の好みにあうマップが生成されるまでパラメーターを調整する必要があります。
あと、このアルゴリズムでは、洞窟内の全ての通路(部屋)が繋がっているという保証はありません。
プログラム
| ... |
マップ配列を走査しているときに、配列内の値(タイル)が新しいものに変わるとまずいので、マップ走査中はtemp配列に値を入れることにします。
走査が終わったら、temp配列の値をマップ配列に移します。
走査が終わったら、temp配列の値をマップ配列に移します。
実行結果
flash on 2011-10-10 - wonderfl build flash online
クリックするたびに新しいマップを生成します。
検証用コード
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite
{
private const FLOOR:int = 0;
private const WALL:int = 1;
private const WIDTH:int = 64;
private const HEIGHT:int = 64;
private const ITERATIONS:int = 5;
private var fillProbability:Number = 0.4;
private var map:Array;
private var temp:Array;
private var bd:BitmapData;
public function Main()
{
bd = new BitmapData(WIDTH, HEIGHT, false, 0x0);
var bitmap:Bitmap = new Bitmap(bd);
bitmap.scaleX = bitmap.scaleY = 7;
addChild(bitmap);
stage.addEventListener(MouseEvent.CLICK, onMouseClick);
onMouseClick();
}
private function onMouseClick(event:MouseEvent = null):void
{
initMap();
for (var i:int = 0; i < ITERATIONS; i++)
{
generateMap();
}
for (var y:int = 0; y < HEIGHT; y++)
{
for (var x:int = 0; x < WIDTH; x++)
{
if (map[y][x] == FLOOR) bd.setPixel(x, y, 0xFFFFFF);
else bd.setPixel(x, y, 0x0);
}
}
}
private function initMap():void
{
map = [];
temp = [];
for (var y:int = 0; y < HEIGHT; y++)
{
map[y] = [];
for (var x:int = 0; x < WIDTH; x++)
{
map[y][x] = (Math.random() < fillProbability) ? WALL : FLOOR;
}
}
for (y = 0; y < HEIGHT; y++)
{
temp[y] = [];
for (x = 0; x < WIDTH; x++)
{
temp[y][x] = WALL;
}
}
for (y = 0; y < HEIGHT; y++)
{
map[y][0] = map[y][WIDTH - 1] = WALL;
}
for (x = 0; x < WIDTH; x++)
{
map[0][x] = map[HEIGHT -1][x] = WALL;
}
}
private function generateMap():void
{
for (var y:int = 1; y < HEIGHT - 1; y++)
{
for (var x:int = 1; x < WIDTH - 1; x++)
{
var count3_3:int = 0;
var count5_5:int = 0;
for (var yy:int = -1; yy <= 1; yy++)
{
for (var xx:int = -1; xx <= 1; xx++)
{
if (map[y + yy][x + xx] == WALL)
{
count3_3++;
}
}
}
for (yy = y - 2; yy <= y + 2; yy++)
{
for (xx = x - 2; xx <= x + 2; xx++)
{
if (Math.abs(yy - y) == 2 && Math.abs(xx - x) == 2)
{
continue;
}
if (yy < 0 || xx < 0 || yy >= HEIGHT || xx >= WIDTH)
{
continue;
}
if (map[yy][xx] == WALL)
{
count5_5++;
}
}
}
if (5 <= count3_3 || count5_5 <= 2)
{
temp[y][x] = WALL;
}
else
{
temp[y][x] = FLOOR;
}
}
}
for (y = 1; y < HEIGHT - 1; y++)
{
for (x = 1; x < WIDTH - 1; x++)
{
map[y][x] = temp[y][x];
}
}
}
}
}
このwikiの更新情報RSS