前回の続きです
ringogames.hatenablog.com
②で解説したアルゴリズムをもとに実際にUnity上で迷路を作成するプロジェクトを作成しました。
以下にプロジェクトをアップしています。
https://gitlab.com/ringogames2022/kruskalalgorithmdungeon
制作環境
で開発しています。
サンプルシーンを開いて実行すると以下のように迷路が作成されます。
この迷路は毎回ランダムで生成されるようになっています。
シーン上に用意されたDungeonGeneratorが迷路生成用アルゴリズムのメイン部分となっています。
- WallObject
- DungeonTile
- Width、Height
- StartPoint
- TileLength
今回のサンプルでは、WallとTileの幅は3mとなっています。
壁はX軸方向正に3mの幅としています。
基本的に、②で解説したアルゴリズムをDungeonGenerator.csのGenerateDungeon関数で実現してます。
以下の部分はまず、すべてのエッジ部分に壁を置いている操作になるため、あまり難しい部分はありません。
public void GenerateDungeon(int columnCount, int rowCount)
{
m_tiles = new List<DungeonTile>();
for(int i= 0; i < rowCount; ++i)
{
for(int j=0; j < columnCount; ++j)
{
Vector3 pos = new Vector3(i * m_tileLength, 0, j * m_tileLength);
Quaternion quat = Quaternion.identity;
m_tiles.Add(new DungeonTile(pos, quat));
}
}
foreach(DungeonTile tile in m_tiles)
{
Instantiate(m_tileObject, tile.Pos, tile.Quat);
}
List<DungeonEdge> bounderyEdges = new List<DungeonEdge>();
for(int i = 0; i < columnCount; ++i)
{
Vector3 pos = m_startPoint + new Vector3(i * m_tileLength, 0, 0);
Quaternion quat = Quaternion.Euler(0, 0, 0);
bounderyEdges.Add(new DungeonEdge(pos, quat));
}
for(int i = 0; i < columnCount; ++i)
{
Vector3 pos = m_startPoint + new Vector3(i * m_tileLength, 0, rowCount * m_tileLength);
Quaternion quat = Quaternion.Euler(0, 0, 0);
bounderyEdges.Add(new DungeonEdge(pos, quat));
}
for(int i = 0; i < rowCount; ++i)
{
Vector3 pos = m_startPoint + new Vector3(0, 0, i * m_tileLength);
Quaternion quat = Quaternion.Euler(0, -90, 0);
bounderyEdges.Add(new DungeonEdge(pos, quat));
}
for(int i = 0; i < rowCount; ++i)
{
Vector3 pos = m_startPoint + new Vector3(columnCount * m_tileLength, 0, i * m_tileLength);
Quaternion quat = Quaternion.Euler(0, -90, 0);
bounderyEdges.Add(new DungeonEdge(pos, quat));
}
foreach(DungeonEdge edge in bounderyEdges)
{
Instantiate(m_wallObject, edge.Pos, edge.Quat);
}
List<DungeonEdge> innerEdges = new List<DungeonEdge>();
int tileIndex = 0;
for (int i = 0; i < rowCount; ++i)
{
for (int j = 1; j < columnCount; ++j)
{
Vector3 pos = m_startPoint + new Vector3(j * m_tileLength, 0, i * m_tileLength);
Quaternion quat = Quaternion.Euler(0, -90, 0);
DungeonEdge edge = new DungeonEdge(pos, quat);
edge.NeigboringTileLeft = m_tiles[tileIndex];
edge.NeigboringTileRight = m_tiles[tileIndex + 1];
innerEdges.Add(edge);
tileIndex += 1;
}
tileIndex += 1;
}
tileIndex = 0;
for (int i = 1; i < rowCount; ++i)
{
for (int j = 0; j < columnCount; ++j)
{
Vector3 pos = m_startPoint + new Vector3(j * m_tileLength, 0, i * m_tileLength);
Quaternion quat = Quaternion.Euler(0, 0, 0);
DungeonEdge edge = new DungeonEdge(pos, quat);
edge.NeigboringTileLeft = m_tiles[tileIndex];
edge.NeigboringTileRight = m_tiles[tileIndex + columnCount];
innerEdges.Add(edge);
tileIndex += 1;
}
}
それに続く以下の部分で、削除対象のエッジをはさむタイルのグループを調べ、
- 同一グループであれば、エッジは削除しない
- 異なるグループであれば、エッジを削除したのち、そのタイルを同一グループにする
ということを行っています。
m_edges = new List<DungeonEdge>();
while (true)
{
if(innerEdges.Count == 0)
{
break;
}
int edgeIndex = Random.Range(0, innerEdges.Count);
var targetEdge = innerEdges[edgeIndex];
innerEdges.RemoveAt(edgeIndex);
var leftTile = targetEdge.NeigboringTileLeft;
var rightTile = targetEdge.NeigboringTileRight;
if(leftTile.TopParent() == rightTile.TopParent())
{
m_edges.Add(targetEdge);
}
else
{
rightTile.TopParent().Parent = leftTile;
}
}
foreach(DungeonEdge edge in m_edges)
{
Instantiate(m_wallObject, edge.Pos, edge.Quat);
}
非常に簡単なアルゴリズムで、プロシージャルな迷路を作成することができました。
今後、このアルゴリズムを使用して、ゲーム作成を行っていきます。