ダンジョン生成(穴掘り編)

ayumegu(プログラマー)
よろしくお願いします。

最近Assetの紹介ばかりなので今回は何か作ってみたネタで。

前にダンジョン生成の部屋版をやったのですが今回は穴掘り編
今回のソースははとても簡単
しかし実際にプレイするとイラっとするから私は嫌いなマップです。
こういうのって全部探索しないと次に進めないタイプでして・・・。

参考サイトは下記

迷路自動生成アルゴリズム
迷路自動生成(穴掘り法編)

完成サンプル

基本的に下の参考サイトののActionscriptで書かれたコードをC#に直しただけですがこんな感じの作れます

考え方なども上記サイトに詳しく書かれているので割愛。

早速実装

ソースはこちら1ファイル
ちなみに画像周りは2dToolKit使っています
いや〜マップチップ系のやつは2dToolKitが便利すぎますはい

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Main : MonoBehaviour {

  private const int MAP_WIDTH = 39;
  private const int MAP_HEIGHT = 39;
  private const int TILE_SIZE = 32;
  private List<TileData> tileList;

  private const int IMG_ID_ROAD = 0;
  private const int IMG_ID_WALL = 10;

  public enum TileType
  {
      WALL = 0,
      ROAD,
  }

  void Start () {

      tileList = new List<TileData>();

      // 一度すべてを壁で初期化
      for(int y = 0; y < MAP_HEIGHT; y++)
      {
          for(int x = 0; x < MAP_WIDTH; x++)
          {
              TileData tile = new TileData();
              tile.tileType = TileType.WALL;
              tileList.Add(tile);
          }
      }

      // 開始位置設定(縦横ともに偶数値を設定)
      int posX = Random.Range(0, (MAP_WIDTH + 1) / 2) * 2;
      int posY = Random.Range(0, (MAP_HEIGHT + 1) / 2) * 2;

      DigTile(posX, posY);

      ShowTile();
  }
  
  private void DigTile(int posX, int posY)
  {
      Vector2[] dir = new Vector2[]
      {
          new Vector2(1, 0),
          new Vector2(-1, 0),
          new Vector2(0, 1),
          new Vector2(0, -1),
      };

      // シャッフル
      for (int t = 0; t < dir.Length; t++ )
      {
          Vector2 tmp = dir[t];
          int r = Random.Range(t, dir.Length);
          dir[t] = dir[r];
          dir[r] = tmp;
      }

      for(int i = 0; i < 4; i++)
      {
          int dx = (int)dir[i].x;
          int dy = (int)dir[i].y;

          if(posX + dx < 0 || MAP_WIDTH <= posX + dx || posY + dy < 0 || MAP_HEIGHT <= posY + dy) continue;

          // 2ます先が道でなければOK
          if (GetTile(posX + dx * 2, posY + dy * 2).tileType != TileType.ROAD)
          {
              SetTile(posX + dx, posY + dy, TileType.ROAD);
              SetTile(posX + dx * 2, posY + dy * 2, TileType.ROAD);

              DigTile(posX + dx * 2, posY + dy * 2);
          }
      }
  }

  private TileData GetTile(int x, int y)
  {
      return tileList[x + MAP_WIDTH * y];
  }

  private void SetTile(int x, int y, TileType type)
  {
      tileList[x + MAP_WIDTH * y].tileType = type;
  }

  private void CreateWall(int x, int y)
  {
      GameObject tileObj = Instantiate(Resources.Load("Prefabs/Tile")) as GameObject;
      tileObj.name = "Wall";
      tileObj.transform.position = new Vector3(x * TILE_SIZE * 0.01f, y * TILE_SIZE * 0.01f, 0);
      tileObj.GetComponent<tk2dSprite>().spriteId = IMG_ID_WALL;
  }
  
  private void ShowTile()
  {
      for(int y = 0; y < MAP_HEIGHT; y++)
      {
          for(int x = 0; x < MAP_WIDTH; x++)
          {
              GameObject tileObj = Instantiate(Resources.Load("Prefabs/Tile")) as GameObject;
              TileData tile = GetTile(x, y);
              if(tile.tileType == TileType.WALL)
              {
                  tileObj.GetComponent<tk2dSprite>().spriteId = IMG_ID_WALL;
              }
              if(tile.tileType == TileType.ROAD)
              {
                  tileObj.GetComponent<tk2dSprite>().spriteId = IMG_ID_ROAD;
              }
              tileObj.name = "Tile";
              tileObj.transform.position = new Vector3(x * TILE_SIZE * 0.01f, y * TILE_SIZE * 0.01f, 0);
          }
      }

      // 外側を壁にする
      for(int x = -1; x <= MAP_WIDTH; x++)
      {
          CreateWall(x, -1);
          CreateWall(x, MAP_HEIGHT);
      }

      for(int y = -1; y < MAP_HEIGHT; y++)
      {
          CreateWall(-1, y);
          CreateWall(MAP_WIDTH, y);
      }
  }

  public class TileData
  {
      public TileType tileType;
  }

  void Update () {
      
  }
}

注意点は
MAP_WIDTH,MAP_HEIGHT奇数にすること。
掘り始めるポイントは偶数にすることと参考サイトを見れば書いてありますが・・・。

それからそれから

ここから階段を設置してあとは行き止まりには宝箱ぐらい欲しいよね。
を反映した結果

こんな感じかしら。
行き止まりの判定はこんな感じに4方向とも道がなかったらというフラグを追加しただけ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool isRoadEnd = true;
      for(int i = 0; i < 4; i++)
      {
          int dx = (int)dir[i].x;
          int dy = (int)dir[i].y;

          if(posX + dx < 0 || MAP_WIDTH <= posX + dx || posY + dy < 0 || MAP_HEIGHT <= posY + dy) continue;

          // 2ます先が道でなければOK
          if (GetTile(posX + dx * 2, posY + dy * 2).tileType != TileType.ROAD)
          {
              SetTile(posX + dx, posY + dy, TileType.ROAD);
              SetTile(posX + dx * 2, posY + dy * 2, TileType.ROAD);
              isRoadEnd = false;

              DigTile(posX + dx * 2, posY + dy * 2);
          }
      }
      
      // 4方向とも行き止まり
      if(isRoadEnd)
      {
          // ご自由に  
      }

あとはモンスターを徘徊させたり、マップを3Dにしたりご自由に
今回はここまで〜