前回 Unityで作成したVR迷路ゲームをアップしました
制作段階をアップしたいところですが オブジェクトもスクリプトもそこそこの量になったので 重要な部分だけ記録しておきたいと思います
一つは迷路の作成部分
迷路を作るアルゴリズムはいくつかあります 棒倒し法 穴掘り法 壁埋め法 壁伸ばし法
丁寧にまとめられている方がいるので 私の拙い説明より見てもらった方がいいと思います
zenn.dev
理屈はなんとなーく分かるのですが それをコードにするだけの理解力がなかったので
こちらに公開されている壁伸ばし法のサンプルコードをコピペさせていただきました
algoful.com
ありがとうございます
まるっとコピペした後 仕様に合わせて変数とか戻り値を少しだけ変更しました
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class MazeCreator : MonoBehaviour { public int width; //迷路横幅 public int height; //迷路縦幅 public GameObject wallObj; //Wallプレファブ public GameObject startObj; //Startマークプレファブ public GameObject goalObj; //Goalマークプレファブ public GameObject goalPoint; //ゴール位置 public GameObject pointObj; //移動ポイント int[,] maze; //迷路配列 GameObject movePoint; //MovePointオブジェクト // 通路・壁情報 static int Path = 0; static int Wall = 1; // 乱数生成用 private System.Random Random; // 現在拡張中の壁情報を保持 private Stack<Cell> CurrentWallCells; // 壁の拡張を行う開始セルの情報 private List<Cell> StartCells; private enum Direction { Up = 0, Right = 1, Down = 2, Left = 3 } void Awake() { // 5未満のサイズや偶数では生成できない if (width < 5 || height < 5) throw new ArgumentOutOfRangeException(); if (width % 2 == 0) width++; if (height % 2 == 0) height++; // 迷路情報を初期化 maze = new int[width, height]; StartCells = new List<Cell>(); CurrentWallCells = new Stack<Cell>(); this.Random = new System.Random(); CreateMaze(); movePoint = GameObject.Find("MovePoint"); //迷路の表示 for (int i=0; i<width; i++) { for (int j=0; j<height; j++) { if (maze[i, j] == Wall) { GameObject obj = Instantiate(wallObj); obj.transform.position = new Vector3(i, 0, j); } else if (maze[i, j] == Path) { GameObject objP = Instantiate(pointObj); objP.transform.SetParent(movePoint.transform); objP.transform.position = new Vector3(i, GameManager.zombieY, j); } } } GameObject start = Instantiate(startObj); start.transform.position = new Vector3(-0.5f, 2f, 1f); //スタートポイント goalPoint = Instantiate(goalObj); goalPoint.transform.position = new Vector3(width , 2f, height - 2f); //ゴールポイント } //迷路作成 private void CreateMaze() { // 各マスの初期設定を行う for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 外周のみ壁にしておき、開始候補として保持 if (x == 0 || y == 0 || x == width - 1 || y == height - 1) { maze[x, y] = Wall; } else { maze[x, y] = Path; // 外周ではない偶数座標を壁伸ばし開始点にしておく if (x % 2 == 0 && y % 2 == 0) { // 開始候補座標 StartCells.Add(new Cell(x, y)); } } } } // 壁が拡張できなくなるまでループ while (StartCells.Count > 0) { // ランダムに開始セルを取得し、開始候補から削除 var index = Random.Next(StartCells.Count); var cell = StartCells[index]; StartCells.RemoveAt(index); var x = cell.X; var y = cell.Y; // すでに壁の場合は何もしない if (maze[x, y] == Path) { // 拡張中の壁情報を初期化 CurrentWallCells.Clear(); ExtendWall(x, y); } } //Debug.Log("Maze retuen"); maze[0, 1] = Path; maze[width - 1, height - 2] = Path; } // 指定座標から壁を生成拡張する private void ExtendWall(int x, int y) { // 伸ばすことができる方向(1マス先が通路で2マス先まで範囲内) // 2マス先が壁で自分自身の場合、伸ばせない var directions = new List<Direction>(); if (maze[x, y - 1] == Path && !IsCurrentWall(x, y - 2)) directions.Add(Direction.Up); if (maze[x + 1, y] == Path && !IsCurrentWall(x + 2, y)) directions.Add(Direction.Right); if (maze[x, y + 1] == Path && !IsCurrentWall(x, y + 2)) directions.Add(Direction.Down); if (maze[x - 1, y] == Path && !IsCurrentWall(x - 2, y)) directions.Add(Direction.Left); // ランダムに伸ばす(2マス) if (directions.Count > 0) { // 壁を作成(この地点から壁を伸ばす) SetWall(x, y); // 伸ばす先が通路の場合は拡張を続ける var isPath = false; var dirIndex = Random.Next(directions.Count); switch (directions[dirIndex]) { case Direction.Up: isPath = (maze[x, y - 2] == Path); SetWall(x, --y); SetWall(x, --y); break; case Direction.Right: isPath = (maze[x + 2, y] == Path); SetWall(++x, y); SetWall(++x, y); break; case Direction.Down: isPath = (maze[x, y + 2] == Path); SetWall(x, ++y); SetWall(x, ++y); break; case Direction.Left: isPath = (maze[x - 2, y] == Path); SetWall(--x, y); SetWall(--x, y); break; } if (isPath) { // 既存の壁に接続できていない場合は拡張続行 ExtendWall(x, y); } } else { // すべて現在拡張中の壁にぶつかる場合、バックして再開 var beforeCell = CurrentWallCells.Pop(); ExtendWall(beforeCell.X, beforeCell.Y); } } // 壁を拡張する private void SetWall(int x, int y) { maze[x, y] = Wall; if (x % 2 == 0 && y % 2 == 0) { CurrentWallCells.Push(new Cell(x, y)); } } // 拡張中の座標かどうか判定 private bool IsCurrentWall(int x, int y) { return CurrentWallCells.Contains(new Cell(x, y)); } // セル情報 private struct Cell { public int X { get; set; } public int Y { get; set; } public Cell(int x, int y) { this.X = x; this.Y = y; } } }
これで毎回違う迷路を作成することができるようになりました
迷路の縦幅 横幅も簡単に変更できるのでテスト用では9×7の迷路 ビルド用では19×17の迷路にしました
『アルゴリズムは美しい!』です
もう一つ ゾンビの移動にもアルゴリズムを用いたのですが これは次回に