主要思路
情况1.九宫格中随机选择7个方格出来必定可联通(可斜向联通)。
情况2.九宫格中随机选择7个方格出来大部分情况可联通,特殊情况时排除掉不可横纵联通那个方格(只可横纵联通)。
特殊情况:
该情况下红色格子无法与其他6个格子横纵联通。
解决方法:只联通6个黄色格子
文章将以情况2进行说明,情况1同理。
即先预制出一定数量的单个房间地图,然后随机选择7个方格去放置房间地图,再使用洗牌算法(非必须)保证选出的房间的不重复性,再使用四领域填充的洪水填充算法(BFS实现)进行房间格子的联通。
代码部分
1.洗牌算法:非必须,如果不介意格子会出现重复地图的可能性,可不需要使用。
/// <summary>
/// 洗牌算法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="_shuffleList">洗牌前list</param>
/// <returns>洗牌后list</returns>
public static List<T> ShuffleAlgotithm<T>(List<T> _shuffleList, int _startNum = 0)
{
for (int i = _startNum; i < _shuffleList.Count; i++)
{
int randomNum = UnityEngine.Random.Range(i, _shuffleList.Count);
//Debug.Log(randomNum);
T temp = _shuffleList[randomNum];
_shuffleList[randomNum] = _shuffleList[i];
_shuffleList[i] = temp;
}
return _shuffleList;
}
2.洪水填充算法(BFS):
using System;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : SingletonMono<MapGenerator> //这里只是通过继承实现的单例模式,删掉继承自己重写个单例模式就行
{
[Header("MAP")]
public float nineGridSpace; //地图间隔
[Header("COMMON")]
public bool previewLine; //辅助线,可删
public GameObject pointPrefab; //辅助标记,可删
[SerializeField] public List<MapData> actualGeneratorPointList = new List<MapData>();
private List<MapData> generatorPointList = new List<MapData>();
private MapData[] initialMap = new MapData[2]; //应对极端情况:设置一个备用点
private bool[,] MapActive = new bool[3, 3]; //是否激活
protected override void Awake()
{
base.Awake();
MapBuild();
MapConnect();//洪水填充,BFS
}
private void Start()
{
GameManager.GetInstance().MapFinal(nineGridSpace, actualGeneratorPointList);
}
private void MapConnect() //洪水填充主体部分
{
bool[,] MapFlag = new bool[3, 3]; //是否检测
MapFlag[(int)initialMap[0].x, (int)initialMap[0].y] = true; //初始点检测过了
Queue<MapData> queue = new Queue<MapData>();
queue.Enqueue(initialMap[0]); //初始点入队
actualGeneratorPointList.Add(initialMap[0]); //实际点位,极端情况会和GeneratorPointList不一样
while (queue.Count > 0)
{
MapData tempMap = queue.Dequeue();
bool isInitialOne = tempMap.x == initialMap[0].x && tempMap.y == initialMap[0].y;
for (int x = -1; x <= 1; x++) //获取相邻瓦片,四领域填充。左下上右
{
for (int y = -1; y <= 1; y++)
{
int neightborX = tempMap.x + x;
int neightborY = tempMap.y + y;
if (x == 0 || y == 0)
{
if (neightborX >= 0 && neightborX < 3
&& neightborY >= 0 && neightborY < 3)
{
if (MapFlag[neightborX, neightborY] == false && MapActive[neightborX, neightborY] == true) //没有检测过且已经激活
{
MapFlag[neightborX, neightborY] = true;
MapData mapData = new MapData(neightborX, neightborY);
queue.Enqueue(mapData); //添加到队列准备下次遍历
actualGeneratorPointList.Add(mapData);
if (previewLine)
{
Debug.DrawLine(new Vector3(tempMap.x, tempMap.y, 0) * nineGridSpace, new Vector3(neightborX, neightborY, 0) * nineGridSpace, Color.green, 1000);
}
}
}
if (isInitialOne && queue.Count == 0)
{
if (x == 1 && y == 0) //四方向检测完毕
{
actualGeneratorPointList.RemoveAt(0); //移除第一初始点
actualGeneratorPointList.Add(initialMap[1]); //放入第二初始点
MapFlag[(int)initialMap[1].x, (int)initialMap[1].y] = true; //初始点检测过了
queue.Enqueue(initialMap[1]);
}
}
}
}
}
}
}
/// <summary>
/// 九宫格点,再选7个点生成地图
/// </summary>
private void MapBuild()
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
generatorPointList.Add(new MapData(i, j));
}
}
generatorPointList = UtilityTools.ShuffleAlgotithm<MapData>(generatorPointList);
for (int i = 0; i < 7; i++)
{
MapData tempMap = generatorPointList[i];
if(previewLine)
{
GameObject go = Instantiate(pointPrefab, new Vector3(tempMap.x, tempMap.y, 0) * nineGridSpace, Quaternion.identity); //画线用
go.transform.SetParent(this.gameObject.transform);
go.name = tempMap.x + "," + tempMap.y;
}
MapActive[(int)tempMap.x, (int)tempMap.y] = true;
if (i <= 1) //将0号设置为第一初始点,1号生成第二初始点
{
initialMap[i] = tempMap;
}
}
}
[Serializable]
public struct MapData
{
public int x;
public int y;
public MapData(int _x, int _y)
{
this.x = _x;
this.y = _y;
}
}
}
3.管理脚本:
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class GameManager : SingletonMono<GameManager>
{
public List<Transform> realMapsList = new List<Transform>();
public string mapsPath;
public string startMapPath;
public string bossMapPath;
public void MapFinal(float _nineGridSpace, List<MapGenerator.MapData> _actualGeneratorPointList)
{
List<GameObject> mapPrefabs = new List<GameObject>(); //把地图资源加载到内存中,创建个public List直接拖地图也行
GameObject[] FG = Resources.LoadAll<GameObject>(mapsPath); //动态加载的部分,直接拖也行
foreach (var map in FG)
{
mapPrefabs.Add(map);
}
mapPrefabs = UtilityTools.ShuffleAlgotithm<GameObject>(mapPrefabs, 1); //调用洗牌
GameObject go = Instantiate(Resources.Load<GameObject>(startMapPath));
go.transform.position = new Vector3(_actualGeneratorPointList[0].x * _nineGridSpace, _actualGeneratorPointList[0].y * _nineGridSpace, 0);
realMapsList.Add(go.transform);
go.transform.SetParent(MapGenerator.GetInstance().gameObject.transform);
for (int i = 1, j = _actualGeneratorPointList.Count; i < j-1; i++) //用加载得到的资源对象,实例化游戏对象,实现游戏物体的动态加载
{
//初始房间
go = Instantiate(mapPrefabs[i % mapPrefabs.Count]);
go.transform.position = new Vector3(_actualGeneratorPointList[i].x * _nineGridSpace, _actualGeneratorPointList[i].y * _nineGridSpace, 0);
realMapsList.Add(go.transform);
go.transform.SetParent(MapGenerator.GetInstance().gameObject.transform);
}
go = Instantiate(Resources.Load<GameObject>(bossMapPath));
go.transform.position = new Vector3(_actualGeneratorPointList[_actualGeneratorPointList.Count-1].x * _nineGridSpace, _actualGeneratorPointList[_actualGeneratorPointList.Count - 1].y * _nineGridSpace, 0);
realMapsList.Add(go.transform);
go.transform.SetParent(MapGenerator.GetInstance().gameObject.transform);
}
}
效果演示
补充
1.上述代码是我直接从自己的工程文件拖出来的,可能没法直接使用,但是稍微改改绝对能用的
2.上述方法宫格大一点应该也可行,但是由于算法的原因,过大的话效率会变低。
3.本人水平有限,写的不是很好,只是想分享分享个人浅薄的见解,恳请原谅,谢谢