前回 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の迷路にしました
『アルゴリズムは美しい!』です
もう一つ ゾンビの移動にもアルゴリズムを用いたのですが これは次回に