45
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

〇〇勉強してみたAdvent Calendar 2017

Day 16

ローグライク自動生成(区域分割法)

Last updated at Posted at 2017-12-16

#はじめに
こちらはC++ (DXライブラリ)を使って、ローグライク的なダンジョンを作る記事です。
過去作品の掘り出し投稿となります。

〇〇勉強してみた Advent Calendar 2017に参加させていただきました。
今回は以前、ダンジョン生成を勉強してわかったことを書きだしていきます。

DXライブラリはこちらからダウンロードできます。
何か不備がありましたら指摘していただければ幸いです。

#概要
みなさん、"ローグライク"はご存知でしょうか?

何だか知らない人にとっては分かりにくいかもしれませんね。
超簡単に言うと、入るたびに形が変わるダンジョンを冒険するゲームです。

この記事では、ローグライク的なダンジョンの生成方法を考えていきます。

#手順
《 注意!! 》
画像はかなり前に筆者がダンジョン生成を作るために作ったプログラムをスクショしたものです。
余計な部分も表示されていますが、ご了承ください。

###マップを分割します
dng1.png

###分割した範囲内に部屋を作ります
dng2.png

###最後に通路で部屋同士を繋ぎます
dng3.png

###その他、様々な機能をつければゲームの完成!
dng4.png

#C89っぽいコード(昔のC言語風)
《 注意!! 》
このダンジョン生成のコードはかなり前に筆者が書いたものです。
何度も修正してきましたが、分かりにくい部分や不完全な部分も含まれています。
それを踏まえてご覧ください。

###メイン関数

Main.cpp

#include "Dxlib.h"
#include "DungeonMake.h"

#ifdef __ANDROID__
//Android版のコンパイルだったら android_main
int android_main() 
#else
//Windows版のコンパイルだったら WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
#endif
{
	//背景を灰色にする
	if (SetBackgroundColor(127, 127, 127)) return -1; 

	//logの出力を無しにする
	if (SetOutApplicationLogValidFlag(FALSE)) return -1; 

#ifndef __ANDROID__
	// Windows版のコンパイルだったら ChangeWindowMode を実行する
	if (ChangeWindowMode(TRUE)) return -1; 

	//タイトル
	if (SetMainWindowText("DungeonMap")) return -1;
#endif

	//ウィンドウサイズ
	if (SetGraphMode(512, 256, 32)) return -1; 

	//初期化処理
	if (DxLib_Init()) return -1; 

	//時間
	int time = 30;

	struct DungeonMap_RL dng; //ダンジョン

	struct MapData_RL maprl[MAPX_RLk][MAPY_RLk];

	while (ProcessMessage() == 0) {
		//0.5秒おきにダンジョンを生成
		if (time == 30) {
			if(ClearDrawScreen()) return -1;
			if (rogueLikeMapMake(&dng, maprl)) return -1;
			time = 0;

			for (int i = 0; i < MAPX_RLk; i++) {
				for (int j = 0; j < MAPY_RLk; j++) {
					if (!maprl[i][j].mapData) DrawBox(i * 8, j * 8, (i + 1) * 8, (j + 1) * 8, GetColor(0, 0, 0), TRUE);
				}
			}
		}
		//ESCキーで終了
		if (CheckHitKey(KEY_INPUT_ESCAPE)) break;

		//時間を増やす
		time++;
		ScreenFlip();
	}

	//終了処理
	if (DxLib_End()) return -1;
	return 0;
}

###ダンジョン生成関数

DungeonMake.cpp

#include "DungeonMake.h"

/*ローグライク生成関数*/
int rogueLikeMapMake(DungeonMap_RL* dng, MapData_RL maprl[][MAPY_RLk])
{

	for (int i = 0; i < MAPX_RLk; i++)
		for (int j = 0; j < MAPY_RLk; j++)
			maprl[i][j].mapData = 1;

	dng->mapDivCount = dng->divCountMin + GetRand(dng->divCountRand); //マップの区分け数 (部屋の個数) 0~nまでの部屋ID
	if (dng->mapDivCount > 7) return -1;

	dng->mapDiv[0][0] = (MAPX_RLk - 1); //マップの区分け初期サイズX終点 (マップの大きさX軸)
	dng->mapDiv[0][1] = (MAPY_RLk - 1); //マップの区分け初期サイズY終点 (マップの大きさY軸)
	dng->mapDiv[0][2] = 1; //マップの区分け初期サイズX始点 (マップの大きさX軸)
	dng->mapDiv[0][3] = 1; //マップの区分け初期サイズY始点 (マップの大きさY軸)

	dng->mapRoad[0][0] = 255;
	dng->mapRoad[0][1] = 255;

	/*マップを区分けしていく処理(区域を分割する処理)*/
	int divAfter;
	int count; //(0:X, 1:Y) X軸で分けるかY軸で分けるか決める

	for (int i = 1; i < dng->mapDivCount; i++) {

		//今まで作った区分けをランダムに指定(指定した区域をさらに区分けする)
		divAfter = GetRand(i - 1); 

		//指定した区域のXとYの長さによって、分割する向きを決める(長いほうを分割する)
		if (dng->mapDiv[divAfter][0] - dng->mapDiv[divAfter][2] > dng->mapDiv[divAfter][1] - dng->mapDiv[divAfter][3]) count = RL_COUNT_X;
		else count = RL_COUNT_Y;

		if (dng->mapDiv[divAfter][count] - dng->mapDiv[divAfter][count + 2] <  dng->divCountRand * 2 + 8) {
			int k = 0;
			for (int j = 1; j < dng->mapDivCount; j++) {
				if (dng->mapDiv[j][0] - dng->mapDiv[j][2] > k) {
					k = dng->mapDiv[j][0] - dng->mapDiv[j][2];
					divAfter = j;
					count = RL_COUNT_X;
				}
				if (dng->mapDiv[j][1] - dng->mapDiv[j][3] > k) {
					k = dng->mapDiv[j][1] - dng->mapDiv[j][3];
					divAfter = j;
					count = RL_COUNT_Y;
				}
			}
		}

		dng->mapRoad[i][0] = divAfter;
		dng->mapRoad[i][1] = count;

		for (int j = 1; j < i; j++) 
			if (dng->mapRoad[j][0] == divAfter) dng->mapRoad[j][0] = i;

		//count軸の設定
		dng->mapDiv[i][count] = dng->mapDiv[divAfter][count + 2] + (dng->mapDiv[divAfter][count] - dng->mapDiv[divAfter][count + 2]) / 3 + GetRand((dng->mapDiv[divAfter][count] - dng->mapDiv[divAfter][count + 2]) / 3); //0.軸の右端(iR)の座標(divAfter*R/3~2divAfter*R/3)
		dng->mapDiv[i][count + 2] = dng->mapDiv[divAfter][count + 2]; //0.軸の左端(iL)の座標(divAfterL)
		dng->mapDiv[divAfter][count + 2] = dng->mapDiv[i][count]; //divAfter軸の左端(divAfterL)の座標(iR)

		//countとは逆の軸の設定
		dng->mapDiv[i][abs(count - 1)] = dng->mapDiv[divAfter][abs(count - 1)]; //軸の右端(iR)の座標(divAfterR)
		dng->mapDiv[i][abs(count - 1) + 2] = dng->mapDiv[divAfter][abs(count - 1) + 2]; //軸の左端(iL)の座標(divAfterL)
	}

	/*部屋を生成する処理*/
	for (int i = 0; i < dng->mapDivCount; i++) {//区分け
		dng->mapRoomPlayer[i] = 0;//プレイヤー侵入初期化
		dng->mapRoom[i][2] = dng->mapDiv[i][2]; //区分けX始点をマップX始点へ代入
		dng->mapRoom[i][3] = dng->mapDiv[i][3]; //区分けY始点をマップY始点へ代入

		//X座標の部屋の長さを指定
		dng->mapRoom[i][0] = dng->mapDiv[i][2] + dng->divCountRand + GetRand(dng->roomLengthRandX);
		if (dng->mapDiv[i][0] - dng->mapDiv[i][2] < dng->mapRoom[i][0] - dng->mapRoom[i][2] + 5) {
			dng->mapRoom[i][0] = dng->mapDiv[i][0] - 4;
			if (dng->mapDiv[i][0] - dng->mapDiv[i][2] < dng->mapRoom[i][0] - dng->mapRoom[i][2] + 5) {
				dng->mapRoom[i][0] = dng->mapDiv[i][2] + 1;
			}
		}

		dng->mapRoom[i][1] = dng->mapDiv[i][3] + dng->roomLengthMinY + GetRand(dng->roomLengthRandY);
		if (dng->mapDiv[i][1] - dng->mapDiv[i][3] < dng->mapRoom[i][1] - dng->mapRoom[i][3] + 5) {
			dng->mapRoom[i][1] = dng->mapDiv[i][1] - 4;
			if (dng->mapDiv[i][1] - dng->mapDiv[i][3] < dng->mapRoom[i][1] - dng->mapRoom[i][3] + 5) {
				dng->mapRoom[i][1] = dng->mapDiv[i][3] + 1;
			}
		}

		if (dng->mapRoom[i][0] - dng->mapDiv[i][2] <= 1 || dng->mapRoom[i][1] - dng->mapDiv[i][3] <= 1) {
			dng->mapRoom[i][0] = dng->mapDiv[i][2] + 1;
			dng->mapRoom[i][1] = dng->mapDiv[i][3] + 1;
		}
		int l = GetRand(dng->mapDiv[i][0] - dng->mapRoom[i][0] - 5) + 2;
		int m = GetRand(dng->mapDiv[i][1] - dng->mapRoom[i][1] - 5) + 2;
		dng->mapRoom[i][0] += l;
		dng->mapRoom[i][2] += l;
		dng->mapRoom[i][1] += m;
		dng->mapRoom[i][3] += m;

		for (int j = dng->mapRoom[i][2]; j < dng->mapRoom[i][0]; j++) 
			for (int k = dng->mapRoom[i][3]; k < dng->mapRoom[i][1]; k++) 
				maprl[j][k].mapData = 0;
	}


	/*通路を生成する処理*/
	/*通路は2部屋間の細い道のことを指す。
	通路を作るために2部屋をそれぞれ前(Before)と後(After)で分ける。
	for文で全ての部屋をチェックし、前後の部屋を繋ぐ通路を作る。
	まず、前の通路を作り、次に後の通路を作る。
	最後に前と後の通路を繋げる。
	*/
	int roomAfter;
	for (int roomBefore = 0; roomBefore < dng->mapDivCount; roomBefore++) {
		roomAfter = dng->mapRoad[roomBefore][0];

		//X座標の通路
		switch (dng->mapRoad[roomBefore][1]) {
		case RL_COUNT_X:
			dng->mapRoad[roomBefore][2] = GetRand(dng->mapRoom[roomBefore][1] - dng->mapRoom[roomBefore][3] - 2); //前側の通路の位置
			dng->mapRoad[roomBefore][3] = GetRand(dng->mapRoom[roomAfter][1] - dng->mapRoom[roomAfter][3] - 2); //後側の通路の位置
																																	 //前の通路
			for (int j = dng->mapRoom[roomBefore][0]; j < dng->mapDiv[roomBefore][0]; j++) 
				maprl[j][dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][3]].mapData = 0; //通路をマップチップに線画

			//後の通路
			for (int j = dng->mapDiv[roomAfter][2]; j < dng->mapRoom[roomAfter][2]; j++) 
				maprl[j][dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][3]].mapData = 0; //通路をマップチップに線画

			//通路をつなぐ
			for (int j = dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][3]; j <= dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][3]; j++) 
				maprl[dng->mapDiv[roomBefore][0]][j].mapData = 0; //通路をマップチップに線画 2から5(上から下)
			for (int j = dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][3]; j <= dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][3]; j++) 
				maprl[dng->mapDiv[roomBefore][0]][j].mapData = 0; //通路をマップチップに線画 5から2(下から上)
			break;

		case RL_COUNT_Y:
			dng->mapRoad[roomBefore][2] = GetRand(dng->mapRoom[roomBefore][0] - dng->mapRoom[roomBefore][2] - 2); //前側の通路の位置
			dng->mapRoad[roomBefore][3] = GetRand(dng->mapRoom[roomAfter][0] - dng->mapRoom[roomAfter][2] - 2); //後側の通路の位置
																																	 //前の通路
			for (int j = dng->mapRoom[roomBefore][1]; j < dng->mapDiv[roomBefore][1]; j++) 
				maprl[dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][2]][j].mapData = 0; //通路をマップチップに線画

			//後の通路
			for (int j = dng->mapDiv[roomAfter][3]; j < dng->mapRoom[roomAfter][3]; j++) 
				maprl[dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][2]][j].mapData = 0; //通路をマップチップに線画

			//通路をつなぐ
			for (int j = dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][2]; j <= dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][2]; j++) 
				maprl[j][dng->mapDiv[roomBefore][1]].mapData = 0; //通路をマップチップに線画
			for (int j = dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][2]; j <= dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][2]; j++) 
				maprl[j][dng->mapDiv[roomBefore][1]].mapData = 0; //通路をマップチップに線画
			break;
		}

	}
	return 0;
}



###ダンジョンデータ

DungeonMake.h

#include "Dxlib.h"

/*マップ系データ*/
#define MAPX_RLk (64) //マップ縦サイズ
#define MAPY_RLk (32)	//マップ横サイズ
#define RL_COUNT_X (0)
#define RL_COUNT_Y (1)

struct DungeonMap_RL
{

	//生成される部屋の数 (正確に言うと生成される区域の数)
	unsigned char divCountMin = 3; //マップの区分け最小数
	unsigned char divCountRand = 4; //マップの区分け数加算

	//生成される部屋のサイズ
	unsigned char roomLengthMinX = 5; //部屋のX座標の最小サイズ
	unsigned char roomLengthMinY = 5; //部屋のY座標の最小サイズ
	unsigned char roomLengthRandX = 2; //部屋のX座標のサイズ加算
	unsigned char roomLengthRandY = 2; //部屋のY座標のサイズ加算

	unsigned char mapDivCount; //マップの区分け数 (部屋の個数) 0~nまでの部屋ID
	unsigned char mapDiv[8][4] = { 0 }; //マップの区域 [部屋ID][X終点 , Y終点 , X始点 , Y始点]
	unsigned char mapRoom[8][4] = { 0 }; //マップの部屋 [部屋ID][X終点 , Y終点 , X始点 , Y始点]
	unsigned char mapRoad[8][4] = { 0 }; //マップの道 [部屋ID(前)][繋がる先の部屋ID(後) , (0.X座標 , 1.Y座標) , (前)側の通路の位置 , (後)側の通路の位置]
	unsigned char mapRoomPlayer[8] = { 0 };

};

struct MapData_RL
{
	unsigned short mapData = 1; //マップ
};

int rogueLikeMapMake(DungeonMap_RL* dng, MapData_RL maprl[][MAPY_RLk]);

#最新コード(C++11)

###メイン関数

Source.cpp
#include <Dxlib.h>
#include "DungeonMake.hpp"

/*マップ系データ*/
const size_t MAPX_RLk = 64; //マップ縦サイズ
const size_t MAPY_RLk = 32;   //マップ横サイズ

//自作のマップデータを扱えます
class RogueLikeMap
{
public:
	RogueLikeMap(const size_t var_) :mapData(var_) {}
	RogueLikeMap() = default;

	//書き換え可能なマップ
	size_t mapData = 1;
};


#ifdef __ANDROID__
//Android版のコンパイルだったら android_main
int android_main()
#else
//Windows版のコンパイルだったら WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
#endif
{
	//背景を灰色にする
	if (SetBackgroundColor(127, 127, 127)) return -1;

	//logの出力を無しにする
	if (SetOutApplicationLogValidFlag(FALSE)) return -1;

#ifndef __ANDROID__
	// Windows版のコンパイルだったら ChangeWindowMode を実行する
	if (ChangeWindowMode(TRUE)) return -1;

	//タイトル
	if (SetMainWindowText("DungeonMap")) return -1;
#endif

	//ウィンドウサイズ
	if (SetGraphMode(512, 256, 32)) return -1;

	//初期化処理
	if (DxLib_Init()) return -1;

	//時間
	int time = 30;

	DungeonMap_RL dng; //ダンジョン
	std::vector<std::vector<RogueLikeMap>> maprl(MAPX_RLk, std::vector<RogueLikeMap>(MAPY_RLk, 0));

	while (!ProcessMessage() && !ScreenFlip()) {
		//ESCキーで終了
		if (CheckHitKey(KEY_INPUT_ESCAPE)) break;

		//0.5秒おきにダンジョンを生成 & 時間を増やす
		if (time++ != 30) continue;
		if (ClearDrawScreen()) return -1;
		if (rogueLikeMapMake(&dng, maprl)) return -1;

		time = 0;
		for (size_t i = 0; i < MAPX_RLk; ++i)
			for (size_t j = 0; j < MAPY_RLk; ++j)
				if (!maprl[i][j].mapData) DrawBox((int)(i << 3), (int)(j << 3), (int)((i + 1) << 3), (int)((j + 1) << 3), GetColor(0, 0, 0), TRUE);
	}

	//終了処理
	if (DxLib_End()) return -1;
	return 0;
}

###ダンジョン生成とデータ

DungeonMake.hpp
#pragma once
#include <cstdint>
#include <vector>

enum :int {
RL_COUNT_X,
RL_COUNT_Y
};

struct DungeonMap_RL
{

	//生成される部屋の数 (正確に言うと生成される区域の数)
	size_t divCountMin = 3; //マップの区分け最小数
	size_t divCountRand = 4; //マップの区分け数加算

	//生成される部屋のサイズ
	size_t roomLengthMinX = 5; //部屋のX座標の最小サイズ
	size_t roomLengthMinY = 5; //部屋のY座標の最小サイズ
	size_t roomLengthRandX = 2; //部屋のX座標のサイズ加算
	size_t roomLengthRandY = 2; //部屋のY座標のサイズ加算

	size_t mapDivCount{}; //マップの区分け数 (部屋の個数) 0~nまでの部屋ID
	size_t mapDiv[8][4]{}; //マップの区域 [部屋ID][X終点 , Y終点 , X始点 , Y始点]
	size_t mapRoom[8][4]{}; //マップの部屋 [部屋ID][X終点 , Y終点 , X始点 , Y始点]
	size_t mapRoad[8][4]{}; //マップの道 [部屋ID(前)][繋がる先の部屋ID(後) , (0.X座標 , 1.Y座標) , (前)側の通路の位置 , (後)側の通路の位置]
	size_t mapRoomPlayer[8]{};

};

//サンプル
class MapData_RL
{
public:
	MapData_RL(const size_t var_) :mapData(var_) {}
	MapData_RL() = default;
	size_t mapData = 1; //マップ
};

/*ローグライク生成関数*/
template <typename T>
int rogueLikeMapMake(DungeonMap_RL* const dng, T& maprl)
{
	if (maprl.size() == 0 || maprl.front().size() == 0) return -1;
	for (size_t i = 0; i < maprl.size(); ++i)
		for (size_t j = 0; j < maprl[i].size(); ++j)
			maprl[i][j].mapData = 1;

	dng->mapDivCount = dng->divCountMin + (size_t)GetRand((int)dng->divCountRand); //マップの区分け数 (部屋の個数) 0~nまでの部屋ID
	if (dng->mapDivCount > 7) return -1;

	dng->mapDiv[0][0] = (maprl.size() - 1); //マップの区分け初期サイズX終点 (マップの大きさX軸)
	dng->mapDiv[0][1] = (maprl.front().size() - 1); //マップの区分け初期サイズY終点 (マップの大きさY軸)
	dng->mapDiv[0][2] = 1; //マップの区分け初期サイズX始点 (マップの大きさX軸)
	dng->mapDiv[0][3] = 1; //マップの区分け初期サイズY始点 (マップの大きさY軸)

	dng->mapRoad[0][0] = 255;
	dng->mapRoad[0][1] = 255;

	/*マップを区分けしていく処理(区域を分割する処理)*/
	size_t divAfter;
	int count; //(0:X, 1:Y) X軸で分けるかY軸で分けるか決める

	for (size_t i = 1; i < dng->mapDivCount; ++i) {

		//今まで作った区分けをランダムに指定(指定した区域をさらに区分けする)
		divAfter = (size_t)GetRand((int)i - 1);

		//指定した区域のXとYの長さによって、分割する向きを決める(長いほうを分割する)
		if (dng->mapDiv[divAfter][0] - dng->mapDiv[divAfter][2] > dng->mapDiv[divAfter][1] - dng->mapDiv[divAfter][3]) count = RL_COUNT_X;
		else count = RL_COUNT_Y;

		if (dng->mapDiv[divAfter][count] - dng->mapDiv[divAfter][count + 2] < dng->divCountRand * 2 + 8) {
			size_t k = 0;
			for (size_t j = 1; j < dng->mapDivCount; ++j) {
				if (dng->mapDiv[j][0] - dng->mapDiv[j][2] > k) {
					k = dng->mapDiv[j][0] - dng->mapDiv[j][2];
					divAfter = j;
					count = RL_COUNT_X;
				}
				if (dng->mapDiv[j][1] - dng->mapDiv[j][3] > k) {
					k = dng->mapDiv[j][1] - dng->mapDiv[j][3];
					divAfter = j;
					count = RL_COUNT_Y;
				}
			}
		}

		dng->mapRoad[i][0] = divAfter;
		dng->mapRoad[i][1] = count;

		for (size_t j = 1; j < i; ++j)
			if (dng->mapRoad[j][0] == divAfter) dng->mapRoad[j][0] = i;

		//count軸の設定
		dng->mapDiv[i][count] = dng->mapDiv[divAfter][count + 2] + (dng->mapDiv[divAfter][count] - dng->mapDiv[divAfter][count + 2]) / 3 + (size_t)GetRand((int)((dng->mapDiv[divAfter][count] - dng->mapDiv[divAfter][count + 2]) / 3)); //0.軸の右端(iR)の座標(divAfter*R/3~2divAfter*R/3)
		dng->mapDiv[i][count + 2] = dng->mapDiv[divAfter][count + 2]; //0.軸の左端(iL)の座標(divAfterL)
		dng->mapDiv[divAfter][count + 2] = dng->mapDiv[i][count]; //divAfter軸の左端(divAfterL)の座標(iR)

		//countとは逆の軸の設定
		dng->mapDiv[i][abs(count - 1)] = dng->mapDiv[divAfter][abs(count - 1)]; //軸の右端(iR)の座標(divAfterR)
		dng->mapDiv[i][abs(count - 1) + 2] = dng->mapDiv[divAfter][abs(count - 1) + 2]; //軸の左端(iL)の座標(divAfterL)
	}

	/*部屋を生成する処理*/
	for (size_t i = 0; i < dng->mapDivCount; ++i) {//区分け
		dng->mapRoomPlayer[i] = 0;//プレイヤー侵入初期化
		dng->mapRoom[i][2] = dng->mapDiv[i][2]; //区分けX始点をマップX始点へ代入
		dng->mapRoom[i][3] = dng->mapDiv[i][3]; //区分けY始点をマップY始点へ代入

		//X座標の部屋の長さを指定
		dng->mapRoom[i][0] = dng->mapDiv[i][2] + dng->divCountRand + (size_t)GetRand((int)dng->roomLengthRandX);
		if (dng->mapDiv[i][0] - dng->mapDiv[i][2] < dng->mapRoom[i][0] - dng->mapRoom[i][2] + 5) {
			dng->mapRoom[i][0] = dng->mapDiv[i][0] - 4;
			if (dng->mapDiv[i][0] - dng->mapDiv[i][2] < dng->mapRoom[i][0] - dng->mapRoom[i][2] + 5) {
				dng->mapRoom[i][0] = dng->mapDiv[i][2] + 1;
			}
		}

		dng->mapRoom[i][1] = dng->mapDiv[i][3] + dng->roomLengthMinY + (size_t)GetRand((int)dng->roomLengthRandY);
		if (dng->mapDiv[i][1] - dng->mapDiv[i][3] < dng->mapRoom[i][1] - dng->mapRoom[i][3] + 5) {
			dng->mapRoom[i][1] = dng->mapDiv[i][1] - 4;
			if (dng->mapDiv[i][1] - dng->mapDiv[i][3] < dng->mapRoom[i][1] - dng->mapRoom[i][3] + 5) {
				dng->mapRoom[i][1] = dng->mapDiv[i][3] + 1;
			}
		}

		if (dng->mapRoom[i][0] - dng->mapDiv[i][2] <= 1 || dng->mapRoom[i][1] - dng->mapDiv[i][3] <= 1) {
			dng->mapRoom[i][0] = dng->mapDiv[i][2] + 1;
			dng->mapRoom[i][1] = dng->mapDiv[i][3] + 1;
		}
		size_t l = (size_t)GetRand((int)(dng->mapDiv[i][0] - dng->mapRoom[i][0] - 5)) + 2;
		size_t m = (size_t)GetRand((int)(dng->mapDiv[i][1] - dng->mapRoom[i][1] - 5)) + 2;
		dng->mapRoom[i][0] += l;
		dng->mapRoom[i][2] += l;
		dng->mapRoom[i][1] += m;
		dng->mapRoom[i][3] += m;

		for (size_t j = dng->mapRoom[i][2]; j < dng->mapRoom[i][0]; ++j)
			for (size_t k = dng->mapRoom[i][3]; k < dng->mapRoom[i][1]; ++k)
				maprl[j][k].mapData = 0;
	}


	/*通路を生成する処理*/
	/*通路は2部屋間の細い道のことを指す。
	通路を作るために2部屋をそれぞれ前(Before)と後(After)で分ける。
	for文で全ての部屋をチェックし、前後の部屋を繋ぐ通路を作る。
	まず、前の通路を作り、次に後の通路を作る。
	最後に前と後の通路を繋げる。
	*/
	size_t roomAfter;
	for (size_t roomBefore = 0; roomBefore < dng->mapDivCount; ++roomBefore) {
		roomAfter = dng->mapRoad[roomBefore][0];

		//X座標の通路
		switch (dng->mapRoad[roomBefore][1]) {
		case RL_COUNT_X:
			dng->mapRoad[roomBefore][2] = (size_t)GetRand((int)(dng->mapRoom[roomBefore][1] - dng->mapRoom[roomBefore][3] - 2)); //前側の通路の位置
			dng->mapRoad[roomBefore][3] = (size_t)GetRand((int)(dng->mapRoom[roomAfter][1] - dng->mapRoom[roomAfter][3] - 2)); //後側の通路の位置
																																	 //前の通路
			for (size_t j = dng->mapRoom[roomBefore][0]; j < dng->mapDiv[roomBefore][0]; ++j)
				maprl[j][dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][3]].mapData = 0; //通路をマップチップに線画

			//後の通路
			for (size_t j = dng->mapDiv[roomAfter][2]; j < dng->mapRoom[roomAfter][2]; ++j)
				maprl[j][dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][3]].mapData = 0; //通路をマップチップに線画

			//通路をつなぐ
			for (size_t j = dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][3]; j <= dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][3]; ++j)
				maprl[dng->mapDiv[roomBefore][0]][j].mapData = 0; //通路をマップチップに線画 2から5(上から下)
			for (size_t j = dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][3]; j <= dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][3]; ++j)
				maprl[dng->mapDiv[roomBefore][0]][j].mapData = 0; //通路をマップチップに線画 5から2(下から上)
			break;

		case RL_COUNT_Y:
			dng->mapRoad[roomBefore][2] = (size_t)GetRand((int)(dng->mapRoom[roomBefore][0] - dng->mapRoom[roomBefore][2] - 2)); //前側の通路の位置
			dng->mapRoad[roomBefore][3] = (size_t)GetRand((int)(dng->mapRoom[roomAfter][0] - dng->mapRoom[roomAfter][2] - 2)); //後側の通路の位置
																																	 //前の通路
			for (size_t j = dng->mapRoom[roomBefore][1]; j < dng->mapDiv[roomBefore][1]; ++j)
				maprl[dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][2]][j].mapData = 0; //通路をマップチップに線画

			//後の通路
			for (size_t j = dng->mapDiv[roomAfter][3]; j < dng->mapRoom[roomAfter][3]; ++j)
				maprl[dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][2]][j].mapData = 0; //通路をマップチップに線画

			//通路をつなぐ
			for (size_t j = dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][2]; j <= dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][2]; ++j)
				maprl[j][dng->mapDiv[roomBefore][1]].mapData = 0; //通路をマップチップに線画
			for (size_t j = dng->mapRoad[roomBefore][3] + dng->mapRoom[roomAfter][2]; j <= dng->mapRoad[roomBefore][2] + dng->mapRoom[roomBefore][2]; ++j)
				maprl[j][dng->mapDiv[roomBefore][1]].mapData = 0; //通路をマップチップに線画
			break;
		}

	}
	return 0;
}

#実行結果
実行してみるとこのようにアニメーションします。
1513454617561.gif

無事にダンジョンが生成出来たようです!

#さいごに
このプログラムはあまり複雑なダンジョンを生成しません。

ぜひ、記事を読んでくださったそこのあなた
多様なダンジョンを生成するプログラムに挑戦してみてください。

最後までお読みいただきありがとうございました。
「いいね!」していただけると、とても励みになります。

##ソースコードのライセンス

These codes are licensed under CC0.
CC0

ソースコードは自由に使用してください。

45
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?