【Godot】穴掘り法によるダンジョンの自動生成

この記事は穴掘り法によるマップ(壁)の自動生成を Godot Engine で実装したサンプルを紹介するページです

穴掘り法によるダンジョンの自動生成

Roguelike (ローグライクゲーム) や、 Dungeon crawler (3DダンジョンRPG) のお手軽な自動生成ダンジョンとして使用できると思います。

穴掘り法の実装例

以下、穴掘り法を実装したサンプルコードです

extends Node2D

# マップの広さ
const MAP_WIDTH = 16
const MAP_HEIGHT = 9

# tilemap上の値
const TILE_WALL = 0

# _arrayでの値
const ARRAY_OUT_OF_RANGE = -1 # 領域外
const ARRAY_PASSAGE = 0 # 通路
const ARRAY_WALL = 1 # 影

# 2次元配列管理
class Array2D:
	var _width = 0
	var _height = 0
	var _pool = []
	func _init(w:int, h:int) -> void:
		_width = w
		_height = h
		for j in range(h):
			for i in range(w):
				_pool.append(0)
	func fill(v:int) -> void:
		for j in range(_height):
			for i in range(_width):
				_pool[i + j * _width] = v
	func setv(i:int, j:int, v:int) -> void:
		if i < 0 or i >= _width:
			return # 領域外
		if j < 0 or j >= _height:
			return # 領域外
		_pool[i + j * _width] = v
	func getv(i:int, j:int) -> int:
		if i < 0 or i >= _width:
			return ARRAY_OUT_OF_RANGE # 領域外
		if j < 0 or j >= _height:
			return ARRAY_OUT_OF_RANGE # 領域外
		return _pool[i + j * _width]
	func dump() -> void:
		print("[array2d]")
		for j in range(_height):
			var s = ""
			for i in range(_width):
				s += "%d, "%getv(i, j)
			print(s)


var _array = Array2D.new(MAP_WIDTH, MAP_HEIGHT)
onready var tilemap = $TileMap

func _ready() -> void:
	_regenerate()
	_redraw()

# ダンジョンを生成する
func _regenerate() -> void:
	_array.fill(ARRAY_WALL)
	
	# 開始地点を決める
	var xstart = randi()%MAP_WIDTH
	var ystart = randi()%MAP_HEIGHT
	
	# 穴掘り開始
	_dig(xstart, ystart)
	
	# デバッグ用に作成したリストを出力する
	_array.dump()

# 穴を掘る
func _dig(x:int, y:int) -> void:
	# 開始地点を掘る
	_array.setv(x, y, ARRAY_PASSAGE)
	
	# 掘れるかどうかを判定する方向
	var dir_list = [
		Vector2(-1, 0),
		Vector2(0, -1),
		Vector2(1, 0),
		Vector2(0, 1)
	]
	
	# シャッフル
	dir_list.shuffle()
	
	for dir in dir_list:
		var dx = dir.x
		var dy = dir.y
		
		if _array.getv(x + dx*2, y + dy*2) == ARRAY_WALL:
			# 2マス先が壁なので掘れる
			_array.setv(x+dx, y+dy, 0)
			
			# 次の穴を掘る
			_dig(x + dx*2, y + dy*2)

# タイルマップに反映する
func _redraw() -> void:
	tilemap.clear()
	for j in range(MAP_HEIGHT):
		for i in range(MAP_WIDTH):
			if _array.getv(i, j) == ARRAY_WALL:
				tilemap.set_cell(i, j, 0)

# 再生性ボタンを押した
func _on_ButtonRedraw_pressed() -> void:
	_regenerate()
	_redraw()

穴掘り法の考え方については関連ページのコードをそのまま使用しています。

穴掘り法によるダンジョンの自動生成

少し気をつけるところとしては、穴掘りで使用する配列を「Array2D」クラスで管理し、内部で使用する値を「ARRAY_*」定数で扱っていることとなります。

完成プロジェクト

今回作成したプロジェクトファイルを添付しておきます。