質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

247閲覧

マップ自動生成時に発生する孤立部屋について

PECTONG

総合スコア3

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

1クリップ

投稿2024/04/13 12:18

編集2024/04/13 15:58

実現したいこと

マップ自動生成時、繋がらない部屋がある。

発生している問題・分からないこと

現在ローグライク(ローグライト)のゲームをUnityを使って作成中なのですが、分割法を使ったマップ自動生成の際に↓のように部屋が全て繋がっていないパターンが現れて困っています。
イメージ説明
解決方法のアルゴリズムはなんとなく理解できたんですが(おそらく、
https://note.com/motibe_tsukuru/n/nbe75bb690bcc 
の項目4/3の部分が該当する解決方法だと思われます)具体的なソースコードがわかりません。
ご教授何卒よろしくお願いします。
※下記に記載しているソースコードは
https://www.kurage.net/game-dev/187
こちらで使用されているものをそのまま利用しています。

追記

以下、分割線を表示した状態の画像になります。

イメージ説明

追記2

以下、生成確率を決めるコードを削除した状態での繋がらない部屋の画像になります。

イメージ説明

該当のソースコード

C#

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4 5public class MapGenerator 6{ 7 8 private const int MINIMUM_RANGE_WIDTH = 15; 9 10 private int mapSizeX; 11 private int mapSizeY; 12 private int maxRoom; 13 14 private List<Range> roomList = new List<Range>(); 15 private List<Range> rangeList = new List<Range>(); 16 private List<Range> passList = new List<Range>(); 17 private List<Range> roomPassList = new List<Range>(); 18 19 private bool isGenerated = false; 20 21 public int[,] GenerateMap(int mapSizeX, int mapSizeY, int maxRoom) 22 { 23 this.mapSizeX = mapSizeX; 24 this.mapSizeY = mapSizeY; 25 26 int[,] map = new int[mapSizeX, mapSizeY]; 27 28 CreateRange(maxRoom); 29 CreateRoom(); 30 31 // ここまでの結果を一度配列に反映する 32 foreach (Range pass in passList) 33 { 34 for (int x = pass.Start.X; x <= pass.End.X; x++) 35 { 36 for (int y = pass.Start.Y; y <= pass.End.Y; y++) 37 { 38 map[x, y] = 1; 39 } 40 } 41 } 42 foreach (Range roomPass in roomPassList) 43 { 44 for (int x = roomPass.Start.X; x <= roomPass.End.X; x++) 45 { 46 for (int y = roomPass.Start.Y; y <= roomPass.End.Y; y++) 47 { 48 map[x, y] = 1; 49 } 50 } 51 } 52 foreach (Range room in roomList) 53 { 54 for (int x = room.Start.X; x <= room.End.X; x++) 55 { 56 for (int y = room.Start.Y; y <= room.End.Y; y++) 57 { 58 map[x, y] = 1; 59 } 60 } 61 } 62 63 TrimPassList(ref map); 64 65 return map; 66 } 67 68 public void CreateRange(int maxRoom) 69 { 70 // 区画のリストの初期値としてマップ全体を入れる 71 rangeList.Add(new Range(0, 0, mapSizeX - 1, mapSizeY - 1)); 72 73 bool isDevided; 74 do 75 { 76 // 縦 → 横 の順番で部屋を区切っていく。一つも区切らなかったら終了 77 isDevided = DevideRange(false); 78 isDevided = DevideRange(true) || isDevided; 79 80 // もしくは最大区画数を超えたら終了 81 if (rangeList.Count >= maxRoom) 82 { 83 break; 84 } 85 } while (isDevided); 86 87 } 88 89 public bool DevideRange(bool isVertical) 90 { 91 bool isDevided = false; 92 93 // 区画ごとに切るかどうか判定する 94 List<Range> newRangeList = new List<Range>(); 95 foreach (Range range in rangeList) 96 { 97 // これ以上分割できない場合はスキップ 98 if (isVertical && range.GetWidthY() < MINIMUM_RANGE_WIDTH * 2 + 1) 99 { 100 continue; 101 } 102 else if (!isVertical && range.GetWidthX() < MINIMUM_RANGE_WIDTH * 2 + 1) 103 { 104 continue; 105 } 106 107 System.Threading.Thread.Sleep(1); 108 109 // 40%の確率で分割しない 110 // ただし、区画の数が1つの時は必ず分割する 111 if (rangeList.Count > 1 && RogueUtils.RandomJadge(0.4f)) 112 { 113 continue; 114 } 115 116 // 長さから最少の区画サイズ2つ分を引き、残りからランダムで分割位置を決める 117 int length = isVertical ? range.GetWidthY() : range.GetWidthX(); 118 int margin = length - MINIMUM_RANGE_WIDTH * 2; 119 int baseIndex = isVertical ? range.Start.Y : range.Start.X; 120 int devideIndex = baseIndex + MINIMUM_RANGE_WIDTH + RogueUtils.GetRandomInt(1, margin) - 1; 121 122 // 分割された区画の大きさを変更し、新しい区画を追加リストに追加する 123 // 同時に、分割した境界を通路として保存しておく 124 Range newRange = new Range(); 125 if (isVertical) 126 { 127 passList.Add(new Range(range.Start.X, devideIndex, range.End.X, devideIndex)); 128 newRange = new Range(range.Start.X, devideIndex + 1, range.End.X, range.End.Y); 129 range.End.Y = devideIndex - 1; 130 } 131 else 132 { 133 passList.Add(new Range(devideIndex, range.Start.Y, devideIndex, range.End.Y)); 134 newRange = new Range(devideIndex + 1, range.Start.Y, range.End.X, range.End.Y); 135 range.End.X = devideIndex - 1; 136 } 137 138 // 追加リストに新しい区画を退避する。 139 newRangeList.Add(newRange); 140 141 isDevided = true; 142 } 143 144 // 追加リストに退避しておいた新しい区画を追加する。 145 rangeList.AddRange(newRangeList); 146 147 return isDevided; 148 } 149 150 private void CreateRoom() 151 { 152 // 部屋のない区画が偏らないようにリストをシャッフルする 153 rangeList.Sort((a, b) => RogueUtils.GetRandomInt(0, 1) - 1); 154 155 // 1区画あたり1部屋を作っていく。作らない区画もあり。 156 foreach (Range range in rangeList) 157 { 158 System.Threading.Thread.Sleep(1); 159 // 30%の確率で部屋を作らない 160 // ただし、最大部屋数の半分に満たない場合は作る 161 if (roomList.Count > maxRoom / 2 && RogueUtils.RandomJadge(0.3f)) 162 { 163 continue; 164 } 165 166 // 猶予を計算 167 int marginX = range.GetWidthX() - MINIMUM_RANGE_WIDTH + 1; 168 int marginY = range.GetWidthY() - MINIMUM_RANGE_WIDTH + 1; 169 170 // 開始位置を決定 171 int randomX = RogueUtils.GetRandomInt(1, marginX); 172 int randomY = RogueUtils.GetRandomInt(1, marginY); 173 174 // 座標を算出 175 int startX = range.Start.X + randomX; 176 int endX = range.End.X - RogueUtils.GetRandomInt(0, (marginX - randomX)) - 1; 177 int startY = range.Start.Y + randomY; 178 int endY = range.End.Y - RogueUtils.GetRandomInt(0, (marginY - randomY)) - 1; 179 180 // 部屋リストへ追加 181 Range room = new Range(startX, startY, endX, endY); 182 roomList.Add(room); 183 184 // 通路を作る 185 CreatePass(range, room); 186 } 187 } 188 189 private void CreatePass(Range range, Range room) 190 { 191 List<int> directionList = new List<int>(); 192 if (range.Start.X != 0) 193 { 194 // Xマイナス方向 195 directionList.Add(0); 196 } 197 if (range.End.X != mapSizeX - 1) 198 { 199 // Xプラス方向 200 directionList.Add(1); 201 } 202 if (range.Start.Y != 0) 203 { 204 // Yマイナス方向 205 directionList.Add(2); 206 } 207 if (range.End.Y != mapSizeY - 1) 208 { 209 // Yプラス方向 210 directionList.Add(3); 211 } 212 213 // 通路の有無が偏らないよう、リストをシャッフルする 214 directionList.Sort((a, b) => RogueUtils.GetRandomInt(0, 1) - 1); 215 216 bool isFirst = true; 217 foreach (int direction in directionList) 218 { 219 System.Threading.Thread.Sleep(1); 220 // 80%の確率で通路を作らない 221 // ただし、まだ通路がない場合は必ず作る 222 if (!isFirst && RogueUtils.RandomJadge(0.8f)) 223 { 224 continue; 225 } 226 else 227 { 228 isFirst = false; 229 } 230 231 // 向きの判定 232 int random; 233 switch (direction) 234 { 235 case 0: // Xマイナス方向 236 random = room.Start.Y + RogueUtils.GetRandomInt(1, room.GetWidthY()) - 1; 237 roomPassList.Add(new Range(range.Start.X, random, room.Start.X - 1, random)); 238 break; 239 240 case 1: // Xプラス方向 241 random = room.Start.Y + RogueUtils.GetRandomInt(1, room.GetWidthY()) - 1; 242 roomPassList.Add(new Range(room.End.X + 1, random, range.End.X, random)); 243 break; 244 245 case 2: // Yマイナス方向 246 random = room.Start.X + RogueUtils.GetRandomInt(1, room.GetWidthX()) - 1; 247 roomPassList.Add(new Range(random, range.Start.Y, random, room.Start.Y - 1)); 248 break; 249 250 case 3: // Yプラス方向 251 random = room.Start.X + RogueUtils.GetRandomInt(1, room.GetWidthX()) - 1; 252 roomPassList.Add(new Range(random, room.End.Y + 1, random, range.End.Y)); 253 break; 254 } 255 } 256 257 } 258 259 private void TrimPassList(ref int[,] map) 260 { 261 // どの部屋通路からも接続されなかった通路を削除する 262 for (int i = passList.Count - 1; i >= 0; i--) 263 { 264 Range pass = passList[i]; 265 266 bool isVertical = pass.GetWidthY() > 1; 267 268 // 通路が部屋通路から接続されているかチェック 269 bool isTrimTarget = true; 270 if (isVertical) 271 { 272 int x = pass.Start.X; 273 for (int y = pass.Start.Y; y <= pass.End.Y; y++) 274 { 275 if (map[x - 1, y] == 1 || map[x + 1, y] == 1) 276 { 277 isTrimTarget = false; 278 break; 279 } 280 } 281 } 282 else 283 { 284 int y = pass.Start.Y; 285 for (int x = pass.Start.X; x <= pass.End.X; x++) 286 { 287 if (map[x, y - 1] == 1 || map[x, y + 1] == 1) 288 { 289 isTrimTarget = false; 290 break; 291 } 292 } 293 } 294 295 // 削除対象となった通路を削除する 296 if (isTrimTarget) 297 { 298 passList.Remove(pass); 299 300 // マップ配列からも削除 301 if (isVertical) 302 { 303 int x = pass.Start.X; 304 for (int y = pass.Start.Y; y <= pass.End.Y; y++) 305 { 306 map[x, y] = 0; 307 } 308 } 309 else 310 { 311 int y = pass.Start.Y; 312 for (int x = pass.Start.X; x <= pass.End.X; x++) 313 { 314 map[x, y] = 0; 315 } 316 } 317 } 318 } 319 320 // 外周に接している通路を別の通路との接続点まで削除する 321 // 上下基準 322 for (int x = 0; x < mapSizeX - 1; x++) 323 { 324 if (map[x, 0] == 1) 325 { 326 for (int y = 0; y < mapSizeY; y++) 327 { 328 if (map[x - 1, y] == 1 || map[x + 1, y] == 1) 329 { 330 break; 331 } 332 map[x, y] = 0; 333 } 334 } 335 if (map[x, mapSizeY - 1] == 1) 336 { 337 for (int y = mapSizeY - 1; y >= 0; y--) 338 { 339 if (map[x - 1, y] == 1 || map[x + 1, y] == 1) 340 { 341 break; 342 } 343 map[x, y] = 0; 344 } 345 } 346 } 347 // 左右基準 348 for (int y = 0; y < mapSizeY - 1; y++) 349 { 350 if (map[0, y] == 1) 351 { 352 for (int x = 0; x < mapSizeY; x++) 353 { 354 if (map[x, y - 1] == 1 || map[x, y + 1] == 1) 355 { 356 break; 357 } 358 map[x, y] = 0; 359 } 360 } 361 if (map[mapSizeX - 1, y] == 1) 362 { 363 for (int x = mapSizeX - 1; x >= 0; x--) 364 { 365 if (map[x, y - 1] == 1 || map[x, y + 1] == 1) 366 { 367 break; 368 } 369 map[x, y] = 0; 370 } 371 } 372 } 373 } 374 375}

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

解決方法と思しきアルゴリズムは見つかったが、具体的なソースコードはわかりませんでした。

補足

使用エンジン:Unity 2022.3.21f1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Refrain

2024/04/13 15:06

途中の分割用の通路が残っている画像は生成できますか? 可能であれば追加をお願いしたいです。
PECTONG

2024/04/13 15:33

コメントありがとうございます。ご指定いただいた、分割用の通路が残っている画像を質問に追記させていただきました。ご確認いただけると幸いです。
Refrain

2024/04/13 15:44

回答の方に書きましたが、30%から外れた際に部屋が生成されない→区切り線に道を伸ばせない→区切り線同士が繋がらないといった症状かと思われます。
guest

回答1

0

ベストアンサー

部屋の生成確率が30%で設定されているようですが、その30%から外れた際に左から2列目の区画のように部屋が生成されず、区切り線同士を繋ぐことができないのかと思います。
試しに100%生成で全ての部屋が繋がるかのトライで上記仮説のようになっているのかの確認をお願いしたいです。

投稿2024/04/13 15:36

Refrain

総合スコア532

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

PECTONG

2024/04/13 15:58

回答ありがとうございます!試しに100%生成の場合と、生成確率を決定するコードを削除した場合で生成してみましたがやはり部屋が繋がらないパターンが出現しました……。 その際の画像も追記にアップロードしておきます。
PECTONG

2024/04/13 16:07

自己解決しました……通路の生成確率も80%に指定されていて、それが原因で繋がらない部屋が発生してしまっていたようです。 重ねて回答ありがとうございました。おかげさまで問題が解決しました。
Refrain

2024/04/13 17:11

それはまた…(笑) 思わぬところで引っ掛かるものですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問