Finishing the Map Generator
MapGenerator
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public Map[] maps;
public int mapIndex;
public Transform tilePrefab;//瓦片预制体
public Transform obstaclePrefab;//障碍物预制体
public Transform navmeshMaskPrefab;//地图边界预制体
public Transform navmeshFloor;//地图
public Vector2 maxMapSize;//地图最大尺寸
[Range(0f, 1f)]
public float outlinePercent;//瓦片间隙
public float tileSize;//瓦片尺寸
List<Coord> allTileCoords;//所有瓦片坐标
Queue<Coord> shuffledTileCoords;//乱序后的瓦片坐标
Map currentMap;
void Start()
{
GenerateMap();
}
public void GenerateMap()
{
currentMap = maps[mapIndex];
System.Random prng = new System.Random(currentMap.seed);
GetComponent<BoxCollider>().size = new Vector3(currentMap.mapSize.x * tileSize, .05f, currentMap.mapSize.y * tileSize);
//记录所有瓦片位置
allTileCoords = new List<Coord>();
for (int x = 0; x < currentMap.mapSize.x; x++)
{
for (int y = 0; y < currentMap.mapSize.y; y++)
{
allTileCoords.Add(new Coord(x, y));
}
}
//乱序后的瓦片坐标存为队列
shuffledTileCoords = new Queue<Coord> (Utility.ShuffleArray(allTileCoords.ToArray(), currentMap.seed));
string holderName = "Generated Map";
if (transform.Find(holderName))
{
DestroyImmediate(transform.Find(holderName).gameObject);
}
Transform mapHolder = new GameObject(holderName).transform;//创建Generated Map集合瓦片
mapHolder.parent = transform;//将Generated Map父对象设置为为MapGenerator
//循环创建瓦片
for (int x = 0; x < currentMap.mapSize.x; x++)
{
for (int y = 0; y < currentMap.mapSize.y; y++)
{
Vector3 tilePosition = CoordToPosition(x,y);//居中
Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90)) as Transform;
newTile.localScale = Vector3.one * (1 - outlinePercent) * tileSize;
newTile.parent = mapHolder;//将瓦片父对象设置为Generated Map
}
}
//生成障碍物
bool[,] obstacleMap = new bool[(int) currentMap.mapSize.x, (int) currentMap.mapSize.y];//障碍物地图二维bool形地图
int obstacleCount = (int)( currentMap.mapSize.x * currentMap.mapSize.y * currentMap.obstaclePercent);//障碍物数量
int currentObstacleCount = 0;//当前障碍物数量
for (int i = 0; i < obstacleCount; i++)
{
//先默认找到的一处位置可生成障碍物
Coord randomCoord = GetRandomCoord();//获得队列中第一个的坐标
obstacleMap[randomCoord.x, randomCoord.y] = true;
currentObstacleCount++;
if (randomCoord != currentMap.mapCentre && MapIsFullyAccessible(obstacleMap, currentObstacleCount))//此处位置不为中心位置且能够生成障碍物
{
float obstacleHeight = Mathf.Lerp(currentMap.minObstacleHeight,currentMap.maxObstacleHeight,(float)prng.NextDouble());
Vector3 obstaclePosition = CoordToPosition(randomCoord.x,randomCoord.y);//坐标到实际坐标转换
Transform newObstacle = Instantiate(obstaclePrefab, obstaclePosition + Vector3.up * obstacleHeight/2, Quaternion.identity) as Transform;
newObstacle.parent = mapHolder;//将障碍物父对象设置为Generated Map
newObstacle.localScale = new Vector3((1 - outlinePercent) * tileSize, obstacleHeight, (1 - outlinePercent) * tileSize);
//随机高度,插值设置颜色
Renderer obstacleRender = newObstacle.GetComponent<Renderer>();
Material obstacleMaterial = new Material(obstacleRender.sharedMaterial);
float colourPercent = randomCoord.y / (float)currentMap.mapSize.y;
obstacleMaterial.color = Color.Lerp(currentMap.foregroundColour,currentMap.backgroundColour, colourPercent);
obstacleRender.sharedMaterial = obstacleMaterial;
}
else
{
obstacleMap[randomCoord.x,randomCoord.y] = false;
currentObstacleCount--;
}
}
//生成地图边界
//生成左边界
Transform maskLeft = Instantiate(navmeshMaskPrefab,Vector3.left * ( currentMap.mapSize.x + maxMapSize.x)/4f * tileSize, Quaternion.identity) as Transform;
maskLeft.parent = mapHolder;
maskLeft.localScale = new Vector3 ((maxMapSize.x - currentMap.mapSize.x) / 2f, 1, currentMap.mapSize.y) * tileSize;
//生成右边界
Transform maskRight = Instantiate(navmeshMaskPrefab, Vector3.right * ( currentMap.mapSize.x + maxMapSize.x) / 4f * tileSize, Quaternion.identity) as Transform;
maskRight.parent = mapHolder;
maskRight.localScale = new Vector3((maxMapSize.x - currentMap.mapSize.x) / 2f, 1, currentMap.mapSize.y) * tileSize;
//生成上边界
Transform maskTop = Instantiate(navmeshMaskPrefab, Vector3.forward * ( currentMap.mapSize.y + maxMapSize.y) / 4f * tileSize, Quaternion.identity) as Transform;
maskTop.parent = mapHolder;
maskTop.localScale = new Vector3(maxMapSize.x, 1, (maxMapSize.y - currentMap.mapSize.y) / 2f) * tileSize;
//生成下边界
Transform maskBottom = Instantiate(navmeshMaskPrefab, Vector3.back * ( currentMap.mapSize.y + maxMapSize.y) / 4f * tileSize, Quaternion.identity) as Transform;
maskBottom.parent = mapHolder;
maskBottom.localScale = new Vector3(maxMapSize.x, 1, (maxMapSize.y - currentMap.mapSize.y) / 2f) * tileSize;
navmeshFloor.localScale = new Vector3(maxMapSize.x, maxMapSize.y) * tileSize;
}
bool MapIsFullyAccessible(bool[,] obstacleMap, int currentObstacleCount)//采用四邻域洪水填充算法判断是否能够生成障碍物
{
bool[,] mapFlags = new bool[obstacleMap.GetLength(0),obstacleMap.GetLength(1)];
Queue<Coord> queue = new Queue<Coord>();//所有的坐标都会“筛选后”储存在这个队列中,一一检测
queue.Enqueue(currentMap.mapCentre);
mapFlags[currentMap.mapCentre.x,currentMap.mapCentre.y] = true;//中心点标记为【已检测】
int accessibleTileCount = 1;//中心点一直为可行走
while(queue.Count > 0 )//队列大于0,就一直检测下去
{
Coord tile = queue.Dequeue();//当前检测的需要被移除出来
for (int x = -1; x <= 1; x++)//检测相邻四周坐标点X轴
{
for (int y = -1; y <= 1; y++)//检测相邻四周坐标点Y轴
{
int neighbourX = tile.x + x;
int neighbourY = tile.y + y;
if (x == 0 || y == 0)//保证上下左右四个位置,排除对角线上的坐标位置
{
//防止相邻点超出地图临界位置
if (neighbourX >= 0 && neighbourX < obstacleMap.GetLength(0) && neighbourY >= 0 && neighbourY < obstacleMap.GetLength(1))
{
//保证相邻点:未被检测;未有障碍物
if (!mapFlags[neighbourX,neighbourY] && !obstacleMap[neighbourX,neighbourY])
{
mapFlags[neighbourX, neighbourY] = true;
queue.Enqueue(new Coord(neighbourX,neighbourY));
accessibleTileCount ++;
}
}
}
}
}
}
int targetAccessibleTileCount = (int)( currentMap.mapSize.x * currentMap.mapSize.y - currentObstacleCount);
return targetAccessibleTileCount == accessibleTileCount;
}
//坐标到实际坐标转换
Vector3 CoordToPosition(int x, int y)
{
return new Vector3(- currentMap.mapSize.x / 2f + 0.5f + x, 0, - currentMap.mapSize.y / 2f + 0.5f + y) * tileSize;
}
public Coord GetRandomCoord()
{
Coord randomCoord = shuffledTileCoords.Dequeue();//移除并返回在 Queue 的开头的对象。
shuffledTileCoords.Enqueue(randomCoord);//向 Queue 的末尾添加一个对象。
return randomCoord;
}
[System.Serializable]
public struct Coord
{
//Coord结构体存储坐标
public int x;
public int y;
public Coord(int _x, int _y)
{
x = _x;
y = _y;
}
public static bool operator ==(Coord a, Coord b) { return a.x == b.x&& a.y == b.y; }
public static bool operator !=(Coord a, Coord b) { return !(a == b); }
}
[System.Serializable]
public class Map
{
public Coord mapSize;//地图尺寸
[Range(0, 1)]
public float obstaclePercent;//障碍物比率
public int seed;
public float minObstacleHeight;//最低障碍物高度
public float maxObstacleHeight;//最高障碍物高度
public Color foregroundColour;//前景色
public Color backgroundColour;//后景色
public Coord mapCentre
{
get
{
return new Coord(mapSize.x / 2, mapSize.y / 2);
}
}
}
}
MapEditor
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(MapGenerator))]
public class MapEditor : Editor
{
//每一帧都会调用,方便实时查看制作的地图
public override void OnInspectorGUI()
{
MapGenerator map = target as MapGenerator;
if(DrawDefaultInspector())
{
map.GenerateMap();
}
if(GUILayout.Button("生成地图"))
{
map.GenerateMap();
}
}
}