【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_*」定数で扱っていることとなります。

完成プロジェクト

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