之前的博客中已经说了随机房间生成:

https://www.cnblogs.com/koshio0219/p/12604383.html

但实现房间生成只是整个地图生成最初最简单的一步。下面讨论如何随机生成连接房间的通道。

房间的通道一般要满足以下特性和特征:

1.保证所有随机生成的房间都可以通过通道从起点到达终点,不出现未连接或连接中断的情况。

2.通道在生成的过程中不能穿过房间内部。

3.考虑是简洁直接的连接方式,还是更为曲折的通道。

好了,现在眼前出现了很多随机生成的房间,那么问题是:到底哪两个房间需要连接一条通道呢,如果以最快的速度得出所有需要连接的通道列表呢?

这时,我们先不用将空间中生成的这些房间仅仅当前是房间来看待,实质上它们就是分布在空间中的一些点的集合,每一个点都能求出当前离它们最近的点是哪一个,

那么一个非常简单的算法是,我们可以记录两个列表:已经处于连接状态的点的列表(闭合列表),尚未取得连接的点的列表(开放列表)。

先随机一个点作为起点,连接离该起点最近的点,将起点和连接点置入闭合列表中,再以第二个点作为起点连接其最近的点,以此方式不断循环,直至所有的开放列表清空,所有通道的连接数据即全部计算完毕。

上面这样虽然可以很快得出所有连接的通道列表,但缺乏连接的随机性,也无法产生分支路径,游戏性很低。

因此,可以考虑给每个连接的点增加额外分支的概率,这样每个点就至少需要计算与它最近的两个点的位置,在概率的控制下其中一个作为主要路径链表中的值,另一个不连接或连接为分支置入闭合列表中。

生成之后的房间数据结构最终会是一个二叉树。

主要属性:

     public bool bDebug = false;
public MapData MapData;
public RoomBuilder RoomBuilder;
public CrossBuilder CrossBuilder; //所有生成的房间列表
public List<RoomTran> GenRooms = new List<RoomTran>();
//当前未连接房间列表
public List<RoomTran> UnCrossRooms = new List<RoomTran>(); private RoomTran FirstRoom;
private RoomTran LastRoom; //额外路径概率
public float AnotherCrossProbability = .2f;
//死路概率
public float DeadCrossProbability = .5f;
//死路延长概率
public float DeadAwayProbability = .6f; //死路房间节点列表(计算时排除)
List<RoomTran> DeadRooms = new List<RoomTran>();
//每个房间连接其他房间的字典
Dictionary<RoomTran, List<RoomTran>> RoomCrossRooms = new Dictionary<RoomTran, List<RoomTran>>();
//主通路
public LinkedList<RoomTran> MainCross = new LinkedList<RoomTran>(); //结束断点列表
List<RoomTran> EndRooms = new List<RoomTran>(); [HideInInspector]
public List<GameObject> CrossUnitInsts = new List<GameObject>();
[HideInInspector]
public List<GameObject> RoomUnitInsts = new List<GameObject>();

地图数据结构:

 using System.Collections.Generic;
using UnityEngine; public class MapData : MonoBehaviour
{
public Vector3Int MapCenter; public Dictionary<int, RoomData> RoomDataDic = new Dictionary<int, RoomData>(); public List<CrossData> CrossDataList = new List<CrossData>(); public Dictionary<int, List<int>> RoomCrossRoomsDic = new Dictionary<int, List<int>>(); public List<Vector3> CrossUnitPos = new List<Vector3>();
} public struct CrossData
{
public int id1;
public int id2;
}

核心计算函数:

     void CalNextCross(RoomTran nr, RoomTran br = null)
{
MainCross.AddLast(nr);
LRemove(UnCrossRooms, nr); var fcl = FindLatelyRooms(nr); if (fcl.FirstLately != null)
{
if (fcl.SecondLately != null)
{
if (Random.value < AnotherCrossProbability)
{
if (Random.value < DeadCrossProbability)
{
var dr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately;
var lr = dr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately; LAdd(DeadRooms, dr); AddItemToRoomDic(nr, lr, dr, br);
LRemove(UnCrossRooms, dr);
LRemove(UnCrossRooms, lr); CalDeadCross(dr, nr);
CalNextCross(lr, nr);
}
else
{
var mr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately;
var or = mr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately;
AddItemToRoomDic(or, nr);
AddItemToRoomDic(nr, mr, or, br);
CalNextCross(mr, nr);
}
}
else
{
AddItemToRoomDic(nr, br, fcl.FirstLately);
CalNextCross(fcl.FirstLately, nr);
}
}
else
{
AddItemToRoomDic(nr, br, fcl.FirstLately);
CalNextCross(fcl.FirstLately, nr);
}
}
else
{
//计算结束
LastRoom = nr;
AddItemToRoomDic(nr, br);
LAdd(EndRooms, nr); Debug.Log("生成房间数:" + GenRooms.Count);
Debug.Log("主路径房间数:" + MainCross.Count);
Debug.Log("分支房间数:" + DeadRooms.Count);
Debug.Log("端点房间数:" + EndRooms.Count); //更新地图数据
UpdateMapData();
//按顺序执行实际连接
CrossBuilder.StartCoroutine(CrossBuilder.GenCrosses());
}
}

完整的MapSystem脚本:

 using System.Collections.Generic;
using UnityEngine; //这个脚本找出哪些房间之间需要互相连接通道
public class MapSystem : MonoBehaviour
{
public bool bDebug = false;
public MapData MapData;
public RoomBuilder RoomBuilder;
public CrossBuilder CrossBuilder; //所有生成的房间列表
public List<RoomTran> GenRooms = new List<RoomTran>();
//当前未连接房间列表
public List<RoomTran> UnCrossRooms = new List<RoomTran>(); private RoomTran FirstRoom;
private RoomTran LastRoom; //额外路径概率
public float AnotherCrossProbability = .2f;
//死路概率
public float DeadCrossProbability = .5f;
//死路延长概率
public float DeadAwayProbability = .6f; //死路房间节点列表(计算时排除)
List<RoomTran> DeadRooms = new List<RoomTran>();
//每个房间连接其他房间的字典
Dictionary<RoomTran, List<RoomTran>> RoomCrossRooms = new Dictionary<RoomTran, List<RoomTran>>();
//主通路
public LinkedList<RoomTran> MainCross = new LinkedList<RoomTran>(); //结束断点列表
List<RoomTran> EndRooms = new List<RoomTran>(); [HideInInspector]
public List<GameObject> CrossUnitInsts = new List<GameObject>();
[HideInInspector]
public List<GameObject> RoomUnitInsts = new List<GameObject>(); //建筑单位标签
public const string S_TAG = "Unit"; [HideInInspector]
public bool bAction = false; public void Start()
{
CrossBuilder = GetComponent<CrossBuilder>();
RoomBuilder = GetComponent<RoomBuilder>();
} void SetSeed(bool bDebug)
{
int seed;
if (bDebug)
{
seed = PlayerPrefs.GetInt("Seed"); }
else
{
seed = (int)System.DateTime.Now.Ticks;
PlayerPrefs.SetInt("Seed", seed);
}
Random.InitState(seed);
} public void RandRoomDatas()
{
if (RoomBuilder == null || MapData == null)
return; bAction = true;
SetSeed(bDebug);
RoomBuilder.StartCoroutine(RoomBuilder.GenRooms(MapData.MapCenter, () =>
{
CreatRoomData();
RandRoomCrosses();
}));
} public void RandRoomCrosses()
{
if (GenRooms.Count <= ) return; FirstRoom = GenRooms[Random.Range(, GenRooms.Count)]; CalNextCross(FirstRoom);
} void UpdateMapData()
{
foreach (var rd in MapData.RoomDataDic)
{
var rt = rd.Value.RoomTran;
if (RoomCrossRooms.ContainsKey(rt))
{
var temp = new List<int>();
foreach (var crt in RoomCrossRooms[rt])
{
var id = GetRoomIdByRT(crt);
if (id > )
{
temp.Add(id); rd.Value.CrossRooms.Add(MapData.RoomDataDic[id]); var cd = new CrossData();
cd.id1 = rd.Key;
cd.id2 = id;
if (!CrossDataContains(cd))
MapData.CrossDataList.Add(cd);
}
}
MapData.RoomCrossRoomsDic.Add(rd.Key, temp);
} if (MainCross.Contains(rt))
{
rd.Value.bMainCrossRoom = true;
if (EndRooms.Contains(rt))
{
rd.Value.BattleType = RoomBattleType.BossBattle;
}
} if (EndRooms.Contains(rt))
{
rd.Value.bEndRoom = true;
}
}
} bool CrossDataContains(CrossData d)
{
foreach (var cd in MapData.CrossDataList)
{
if ((cd.id1 == d.id1 && cd.id2 == d.id2) || (cd.id1 == d.id2 && cd.id2 == d.id1))
return true;
}
return false;
} int GetRoomIdByRT(RoomTran rt)
{
foreach (var rd in MapData.RoomDataDic)
{
if (rd.Value.RoomTran == rt)
return rd.Key;
}
return -;
} void CreatRoomData()
{
for (int i = ; i < GenRooms.Count + ; i++)
{
var rd = new RoomData();
rd.Id = i;
rd.RoomTran = GenRooms[i - ];
rd.BattleType = RoomBattleType.NormalBattle;
if (rd.Id == )
rd.BattleType = RoomBattleType.Rest;
rd.CrossRooms = new List<RoomData>();
rd.Monsters = new List<GameObject>();
rd.bEndRoom = false;
rd.bMainCrossRoom = false; MapData.RoomDataDic.Add(rd.Id, rd);
}
} void CalNextCross(RoomTran nr, RoomTran br = null)
{
MainCross.AddLast(nr);
LRemove(UnCrossRooms, nr); var fcl = FindLatelyRooms(nr); if (fcl.FirstLately != null)
{
if (fcl.SecondLately != null)
{
if (Random.value < AnotherCrossProbability)
{
if (Random.value < DeadCrossProbability)
{
var dr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately;
var lr = dr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately; LAdd(DeadRooms, dr); AddItemToRoomDic(nr, lr, dr, br);
LRemove(UnCrossRooms, dr);
LRemove(UnCrossRooms, lr); CalDeadCross(dr, nr);
CalNextCross(lr, nr);
}
else
{
var mr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately;
var or = mr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately;
AddItemToRoomDic(or, nr);
AddItemToRoomDic(nr, mr, or, br);
CalNextCross(mr, nr);
}
}
else
{
AddItemToRoomDic(nr, br, fcl.FirstLately);
CalNextCross(fcl.FirstLately, nr);
}
}
else
{
AddItemToRoomDic(nr, br, fcl.FirstLately);
CalNextCross(fcl.FirstLately, nr);
}
}
else
{
//计算结束
LastRoom = nr;
AddItemToRoomDic(nr, br);
LAdd(EndRooms, nr); Debug.Log("生成房间数:" + GenRooms.Count);
Debug.Log("主路径房间数:" + MainCross.Count);
Debug.Log("分支房间数:" + DeadRooms.Count);
Debug.Log("端点房间数:" + EndRooms.Count); //更新地图数据
UpdateMapData();
//按顺序执行实际连接
CrossBuilder.StartCoroutine(CrossBuilder.GenCrosses());
}
} CrossLately FindLatelyRooms(RoomTran tar)
{
var cl = new CrossLately();
float firstSqrdis = Mathf.Infinity;
float secondSqrdis = Mathf.Infinity; foreach (var room in UnCrossRooms)
{
var rc = new Vector3(room.CenterPos.x, room.PosY, room.CenterPos.y);
var tc = new Vector3(tar.CenterPos.x, tar.PosY, tar.CenterPos.y);
float sqrdis = (rc - tc).sqrMagnitude; if (sqrdis < firstSqrdis)
{
firstSqrdis = sqrdis;
cl.FirstLately = room;
}
else if (sqrdis < secondSqrdis)
{
secondSqrdis = sqrdis;
cl.SecondLately = room;
}
}
return cl;
} //计算死路,此处死路没有分支
void CalDeadCross(RoomTran fdr, RoomTran bdr)
{
var temp = FindLatelyRooms(fdr);
if (temp.FirstLately != null && Random.value < DeadAwayProbability)
{
LRemove(UnCrossRooms, temp.FirstLately);
LAdd(DeadRooms, temp.FirstLately);
AddItemToRoomDic(fdr, temp.FirstLately, bdr);
CalDeadCross(temp.FirstLately, fdr);
}
else
{
//彻底死亡
LRemove(UnCrossRooms, fdr);
LAdd(DeadRooms, fdr);
AddItemToRoomDic(fdr, bdr);
LAdd(EndRooms, fdr);
}
} void LRemove<T>(List<T> list, T item)
{
if (list.Contains(item))
{
list.Remove(item);
}
} void LAdd<T>(List<T> list, T item)
{
if (!list.Contains(item))
{
list.Add(item);
}
} void AddItemToRoomDic(RoomTran key, RoomTran item1, RoomTran item2 = null, RoomTran item3 = null)
{
if (RoomCrossRooms.ContainsKey(key))
{
if (item1 != null)
if (!RoomCrossRooms[key].Contains(item1))
RoomCrossRooms[key].Add(item1);
if (item2 != null)
if (!RoomCrossRooms[key].Contains(item2))
RoomCrossRooms[key].Add(item2);
if (item3 != null)
if (!RoomCrossRooms[key].Contains(item3))
RoomCrossRooms[key].Add(item3);
}
else
{
RoomCrossRooms.Add(key, new List<RoomTran>());
AddItemToRoomDic(key, item1, item2, item3);
}
} public bool RayCast(Vector3 ori, Vector3 dir, float mD)
{
Ray ray = new Ray(ori, dir);
RaycastHit info;
if (Physics.Raycast(ray, out info, mD))
{
if (info.transform.tag == S_TAG)
return true;
}
return false;
} public GameObject InsSetPos(GameObject prefab,Vector3 pos, Transform parent = null)
{
var ins = Instantiate(prefab);
ins.transform.position = pos;
ins.transform.parent = parent;
return ins;
} public RoomTran GetRoomByPos(Vector3 pos)
{
foreach (var room in GenRooms)
{
var to = new Vector3(room.Length, RoomBuilder.FixedUnitHeight, room.Width) * .5f; var centerPos = new Vector3(room.CenterPos.x, room.PosY, room.CenterPos.y);
var ned = centerPos - to;
var fod = centerPos + to; if (pos.x >= ned.x && pos.x <= fod.x && pos.y >= ned.y && pos.y <= fod.y && pos.z >= ned.z && pos.z <= fod.z)
{
return room;
}
}
return null;
} public ExCross GetExCross(Vector3 strPos, Vector3 tarPos)
{
var ec = new ExCross();
var rt = GetRoomByPos(strPos);
if (rt != null)
{
var to = new Vector3(rt.Length - , RoomBuilder.FixedUnitHeight - , rt.Width - ) * .5f; var centerPos = new Vector3(rt.CenterPos.x, rt.PosY, rt.CenterPos.y);
var ned = centerPos - to;
var fod = centerPos + to;
if (strPos.x == tarPos.x)
{
if (Random.value < .5f)
{
ec.pos1 = strPos.z < tarPos.z ? new Vector3(ned.x - , strPos.y, strPos.z - ) : new Vector3(ned.x - , strPos.y, strPos.z + );
ec.pos2 = strPos.z < tarPos.z ? ec.pos1 + new Vector3(, , rt.Width + ) : ec.pos1 - new Vector3(, , rt.Width + );
ec.pos3 = new Vector3(strPos.x, strPos.y, ec.pos2.z);
}
else
{
ec.pos1 = strPos.z < tarPos.z ? new Vector3(fod.x + , strPos.y, strPos.z - ) : new Vector3(fod.x + , strPos.y, strPos.z + );
ec.pos2 = strPos.z < tarPos.z ? ec.pos1 + new Vector3(, , rt.Width + ) : ec.pos1 - new Vector3(, , rt.Width + );
ec.pos3 = new Vector3(strPos.x, strPos.y, ec.pos2.z);
} }
else if (strPos.z == tarPos.z)
{
if (Random.value < .5f)
{
ec.pos1 = strPos.x < tarPos.x ? new Vector3(strPos.x - , strPos.y, ned.z - ) : new Vector3(strPos.x + , strPos.y, ned.z - );
ec.pos2 = strPos.x < tarPos.x ? ec.pos1 + new Vector3(rt.Length + , , ) : ec.pos1 - new Vector3(rt.Length + , , );
ec.pos3 = new Vector3(ec.pos2.x, strPos.y, strPos.z);
}
else
{
ec.pos1 = strPos.x < tarPos.x ? new Vector3(strPos.x - , strPos.y, fod.z + ) : new Vector3(strPos.x + , strPos.y, fod.z + );
ec.pos2 = strPos.x < tarPos.x ? ec.pos1 + new Vector3(rt.Length + , , ) : ec.pos1 - new Vector3(rt.Length + , , );
ec.pos3 = new Vector3(ec.pos2.x, strPos.y, strPos.z);
}
}
}
return ec;
} public void OnClickReset()
{
if (bAction)
return; if (CrossUnitInsts.Count == && RoomUnitInsts.Count == )
return; for(int i = ; i < CrossUnitInsts.Count; i++)
{
Destroy(CrossUnitInsts[i]);
} for (int i = ; i < RoomUnitInsts.Count; i++)
{
Destroy(RoomUnitInsts[i]);
} ClearAll();
RandRoomDatas();
} public void ClearAll()
{
GenRooms.Clear();
UnCrossRooms.Clear();
DeadRooms.Clear();
EndRooms.Clear();
MainCross.Clear();
RoomCrossRooms.Clear();
RoomUnitInsts.Clear();
CrossUnitInsts.Clear(); MapData.RoomDataDic.Clear();
MapData.RoomCrossRoomsDic.Clear();
MapData.CrossDataList.Clear();
MapData.CrossUnitPos.Clear();
}
} public struct CrossLately
{
public RoomTran FirstLately;
public RoomTran SecondLately;
} public struct ExCross
{
public Vector3 pos1;
public Vector3 pos2;
public Vector3 pos3;
}

在计算完所有的房间连接通路后,更新地图数据,随后再根据地图数据中的通路情况进行实际通路连接。

实际连接的过程中很多都是数学问题,需要单独分析两个房间的位置关系,基本分析模式如下:

1.两个房间是否位于同一层,如果不是,是否有重叠区域

(通过分析边缘坐标的极限值来判断交叠情况,例如当其中一个房间任意一个轴向的最小值大于目标房间对应轴向的最大值或该轴向的最大值小于目标房间轴向的最小值时,认为两个房间有重叠的轴向区域,否则在该轴向上无重叠)

2.如果两个房间位于同一层,或本来就只生成单层的地图,考虑这两个房间是否共边,共边和不共边的连接方式是有区别的

3.考虑在连接的过程中遭遇其他房间或障碍物时如何处理,是绕过障碍物前进还是断开连接,如何绕开障碍物

最简单的就是共边的情况:

这时可以选择共边范围内的任意一条直线作为连接方式(上图绿色区域),这里注意临界点应该排除在外(因为实际情况下房间因为有墙壁的厚度,最外边的一格大小是没办法形成通路的)

判断共边与否的方式就是计算临界值:

     Vector2 CheckCommonSide(float axis1, int edge1, float axis2, int edge2)
{
var max1 = axis1 + (edge1 - ) * .5f - ;
var min1 = axis1 - (edge1 - ) * .5f + ;
var max2 = axis2 + (edge2 - ) * .5f - ;
var min2 = axis2 - (edge2 - ) * .5f + ; return new Vector2(max1 > max2 ? max2 : max1, min1 > min2 ? min1 : min2);
}

如上,如果返回的值中y>x,则对应轴向不共边(这里之所以给边的长度计算时-1考虑房间的墙壁厚度单位)

第二种情况,不共边但在同一层:

这时就会有两种接近最短路线的折线连接方式,首先我们依然需要找出两个房间最靠近的那四个临界值的点,在判断出无共边区域后选取其中一对坐标进行L形折线连接,L形的折线连接实质上就是两段直线连接,只不过增加了一个中间点。

但上面的考虑有时是过于理想的情况,事实上在随机生成的房间中很容易出现连接路径被其他房间阻挡的情况,最直接的处理方式显然是直接断开,反正通过断开的房间通路过渡也是可以达到目标的,但下面要考虑的是如何绕过目标的做法,

具体来说也分为两种情况:

1.一段直线过程中有其他房间,这样可以考虑直接取得障碍房间左侧或右侧的两个点进行过渡:

     void CrossAround(Vector3 pos, Vector3 max, ExCross cps)
{
if (cps.pos1 != Vector3.zero && cps.pos2 != Vector3.zero && cps.pos3 != Vector3.zero)
{
LineTwoPos(pos, cps.pos1);
LineTwoPos(cps.pos1, cps.pos2);
LineTwoPos(cps.pos2, cps.pos3);
LineTwoPos(cps.pos3, max);
}
}

下面这种情况稍微复杂一点,就是:

2.折线的中点在障碍物房间内,这时,简单的直线绕道是不可取的

需要计算障碍物房间四个角中最合适的一个边缘角进行过渡:

     void LShapePos(Vector3 pos1, Vector3 pos2, Vector3 cp)
{
//判定折线中间点是否位于其他房间内,若位于,则需要重新寻路
var rt = MapSystem.GetRoomByPos(cp);
if (ObstacleCrossType == ObstacleCrossType.Detour && rt != null)
{
var to = new Vector2(rt.Length + , rt.Width + ) * .5f; var ned = rt.CenterPos - to;
var fod = rt.CenterPos + to; Vector3[] v4 = new Vector3[];
v4[] = new Vector3(ned.x, cp.y, ned.y);
v4[] = new Vector3(ned.x, cp.y, fod.y);
v4[] = new Vector3(fod.x, cp.y, ned.y);
v4[] = new Vector3(fod.x, cp.y, fod.y); var minx = pos1.x < pos2.x ? pos1.x : pos2.x;
var maxx = minx == pos1.x ? pos2.x : pos1.x;
var minz = pos1.z < pos2.z ? pos1.z : pos2.z;
var maxz = minz == pos1.z ? pos2.z : pos1.z; for (int i = ; i < v4.Length; i++)
{
if (v4[i].x > minx && v4[i].x < maxx && v4[i].z > minz && v4[i].z < maxz)
{
var ncp1 = new Vector3(cp.x, cp.y, v4[i].z);
var ncp2 = new Vector3(v4[i].x, cp.y, cp.z); var pos1cp = ncp1.x == pos1.x || ncp1.z == pos1.z ? ncp1 : ncp2;
var pos2cp = pos1cp == ncp1 ? ncp2 : ncp1; LShapePos(pos1, v4[i], pos1cp);
LShapePos(v4[i], pos2, pos2cp);
return;
}
}
} LineTwoPos(pos1, cp);
LineTwoPos(pos2, cp);
}

判断这四个角中哪一个点为最合适点,其实就是要判断哪个点位于这条L折线组成的矩形的范围之内,最终只会有一个点满足该情况。

下面是完整的连接脚本:

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO; public enum ObstacleCrossType
{
Break,
Detour
} //这个脚本执行各个房间通道连接的具体方法
public class CrossBuilder : MonoBehaviour
{
public GameObject CrossUnit;
public ObstacleCrossType ObstacleCrossType; private MapSystem MapSystem; Vector3Int Dx = new Vector3Int(, , );
Vector3Int Dy = new Vector3Int(, , );
Vector3Int Dz = new Vector3Int(, , ); private int UnitHeight; private void Start()
{
MapSystem = GetComponent<MapSystem>();
UnitHeight = MapSystem.RoomBuilder.FixedUnitHeight;
} public IEnumerator GenCrosses()
{
var fr = MapSystem.MainCross.First.Value;
var lr = MapSystem.MainCross.Last.Value; var frcp = new Vector3(fr.CenterPos.x, fr.PosY, fr.CenterPos.y);
var lrcp = new Vector3(lr.CenterPos.x, lr.PosY, lr.CenterPos.y); Debug.Log("First: " + frcp);
Debug.Log("Last: " + lrcp); foreach (var cd in MapSystem.MapData.CrossDataList)
{
CrossTwoRoom(cd.id1, cd.id2);
yield return null;
}
MapSystem.bAction = false;
} void CrossTwoRoom(int id1, int id2)
{
var from = MapSystem.MapData.RoomDataDic[id1].RoomTran;
var to = MapSystem.MapData.RoomDataDic[id2].RoomTran; var frcp = new Vector3(from.CenterPos.x, from.PosY, from.CenterPos.y);
var trcp = new Vector3(to.CenterPos.x, to.PosY, to.CenterPos.y); Debug.DrawLine(frcp, trcp, Color.red);
Debug.Log("F:" + frcp + " " + "To: " + trcp); if (from.PosY == to.PosY)
{
//同一层
SameYCross(from, to);
}
else
{
//不同层
var rangex = CheckCommonSide(frcp.x, from.Length, trcp.x, to.Length);
var rangez = CheckCommonSide(frcp.z, from.Width, trcp.z, to.Width);
if (rangex.x >= rangex.y && rangez.x >= rangez.y)
{
//有重叠区域
var rx = EdgeRandom(rangex.y, rangex.x);
var rz = EdgeRandom(rangez.y, rangez.x); var fy = from.PosY - (UnitHeight - ) * .5f + ;
var ty = to.PosY - (UnitHeight - ) * .5f + ; var miny = fy < ty ? fy : ty;
var maxy = miny == fy ? ty : fy; for (float i = miny; i < maxy + ; i++)
{
InsSetPos(new Vector3(rx, i, rz));
}
}
else
{
//无重叠区域
var cp = SameYCross(from, to); var fy = from.PosY - (UnitHeight - ) * .5f + ;
var ty = to.PosY - (UnitHeight - ) * .5f + ; var miny = fy < ty ? fy : ty;
var maxy = miny == fy ? ty : fy; for (float i = miny; i < maxy + ; i++)
{
InsSetPos(new Vector3(cp.x, i, cp.z));
}
}
}
} Vector3 SameYCross(RoomTran from, RoomTran to)
{
//Test
if(from.CenterPos==new Vector2Int(,)&&to.CenterPos==new Vector2Int(, -))
{ } var frcp = new Vector3(from.CenterPos.x, from.PosY, from.CenterPos.y);
var trcp = new Vector3(to.CenterPos.x, to.PosY, to.CenterPos.y); var sr = Random.value < .5f ? from : to;
var or = sr == from ? to : from;
var ry = sr.PosY - (UnitHeight - ) * .5f + ; var rangex = CheckCommonSide(frcp.x, from.Length, trcp.x, to.Length);
var rangez = CheckCommonSide(frcp.z, from.Width, trcp.z, to.Width); Vector3 pos1;
Vector3 pos2; if (rangex.y > rangex.x)
{
if (rangez.y > rangez.x)
{
//无公共边范围
var fxmax = frcp.x + (from.Length - ) * .5f - ;
var fzmax = frcp.z + (from.Width - ) * .5f - ; if (fxmax == rangex.x)
{
if (fzmax == rangez.x)
{
//to在from的右上
var fe = Random.value < .5f ? fxmax : fzmax;
if (fe == fxmax)
{
pos1 = new Vector3(fe + , ry, fzmax);
pos2 = new Vector3(rangex.y, ry, rangez.y - );
//临界值判断
if (pos1.x >= pos2.x)
{
pos2 = new Vector3(pos1.x + , ry, pos2.z);
} if (pos1.z >= pos2.z)
{
pos1 = new Vector3(pos1.x, ry, pos2.z - );
} var cp = new Vector3(pos2.x, ry, pos1.z);
LShapePos(pos1, pos2, cp);
}
else
{
pos1 = new Vector3(fxmax, ry, fe + );
pos2 = new Vector3(rangex.y - , ry, rangez.y); if (pos1.x >= pos2.x)
{
pos1 = new Vector3(pos2.x - , ry, pos1.z);
} if (pos1.z >= pos2.z)
{
pos2 = new Vector3(pos2.x, ry, pos1.z + );
} var cp = new Vector3(pos1.x, ry, pos2.z);
LShapePos(pos1, pos2, cp);
}
}
else
{
//to在from的右下
var fe = Random.value < .5f ? fxmax : rangez.y;
if (fe == fxmax)
{
pos1 = new Vector3(fe + , ry, rangez.y);
pos2 = new Vector3(rangex.y, ry, rangez.x + ); if (pos1.x >= pos2.x)
{
pos2 = new Vector3(pos1.x + , ry, pos2.z);
} if (pos1.z <= pos2.z)
{
pos1 = new Vector3(pos1.x, ry, pos2.z + );
} var cp = new Vector3(pos2.x, ry, pos1.z);
LShapePos(pos1, pos2, cp);
}
else
{
pos1 = new Vector3(rangex.x, ry, fe - );
pos2 = new Vector3(rangex.y - , ry, rangez.x); if (pos1.x >= pos2.x)
{
pos1 = new Vector3(pos2.x - , ry, pos1.z);
} if (pos1.z <= pos2.z)
{
pos2 = new Vector3(pos2.x, ry, pos1.z - );
} var cp = new Vector3(pos1.x, ry, pos2.z);
LShapePos(pos1, pos2, cp);
}
}
}
else
{
if (fzmax == rangez.x)
{
//to在from的左上
var fe = Random.value < .5f ? rangex.y : fzmax;
if (fe == fzmax)
{
pos1 = new Vector3(rangex.y, ry, fe + );
pos2 = new Vector3(rangex.x + , ry, rangez.y); if (pos1.x <= pos2.x)
{
pos1 = new Vector3(pos2.x + , ry, pos1.z);
} if (pos1.z >= pos2.z)
{
pos2 = new Vector3(pos2.x, ry, pos1.z + );
} var cp = new Vector3(pos1.x, ry, pos2.z);
LShapePos(pos1, pos2, cp);
}
else
{
pos1 = new Vector3(fe - , ry, rangez.x);
pos2 = new Vector3(rangex.x, ry, rangez.y - ); if (pos1.x <= pos2.x)
{
pos2 = new Vector3(pos1.x - , ry, pos2.z);
} if (pos1.z >= pos2.z)
{
pos1 = new Vector3(pos1.x, ry, pos2.z - );
} var cp = new Vector3(pos2.x, ry, pos1.z);
LShapePos(pos1, pos2, cp);
}
}
else
{
//to在from的左下
var fe = Random.value < .5f ? rangex.y : rangez.y;
if (fe == rangex.y)
{
pos1 = new Vector3(fe - , ry, rangez.y);
pos2 = new Vector3(rangex.x, ry, rangez.x + ); if (pos1.x <= pos2.x)
{
pos2 = new Vector3(pos1.x - , ry, pos2.z);
}
if (pos1.z <= pos2.z)
{
pos1 = new Vector3(pos1.x, ry, pos2.z + );
} var cp = new Vector3(pos2.x, ry, pos1.z);
LShapePos(pos1, pos2, cp);
}
else
{
pos1 = new Vector3(rangex.y, ry, fe - );
pos2 = new Vector3(rangex.x + , ry, rangez.x); if (pos1.x <= pos2.x)
{
pos1 = new Vector3(pos2.x + , ry, pos1.z);
}
if (pos1.z <= pos2.z)
{
pos2 = new Vector3(pos2.x, ry, pos1.z - );
} var cp = new Vector3(pos1.x, ry, pos2.z);
LShapePos(pos1, pos2, cp);
}
}
}
}
else
{
var rz = EdgeRandom(rangez.y, rangez.x);
var rx1 = rangex.x + ;
var rx2 = rangex.y - ; pos1 = new Vector3(rx1, ry, rz);
pos2 = new Vector3(rx2, ry, rz); LineTwoPos(pos1, pos2);
}
}
else
{
var rx = EdgeRandom(rangex.y, rangex.x);
var rz1 = rangez.x + ;
var rz2 = rangez.y - ; pos1 = new Vector3(rx, ry, rz1);
pos2 = new Vector3(rx, ry, rz2); LineTwoPos(pos1, pos2);
}
return PosOfRoom(pos1, pos2, or);
} Vector3 PosOfRoom(Vector3 pos1, Vector3 pos2, RoomTran room)
{
var lmax = room.CenterPos.x + (room.Length - ) * .5f;
var lmin = room.CenterPos.x - (room.Length - ) * .5f;
var wmax = room.CenterPos.y + (room.Width - ) * .5f;
var wmin = room.CenterPos.y - (room.Width - ) * .5f; if (pos1.x >= lmin && pos1.x <= lmax && pos1.z >= wmin && pos1.z <= wmax)
return pos1;
else
return pos2;
} Vector2 CheckCommonSide(float axis1, int edge1, float axis2, int edge2)
{
var max1 = axis1 + (edge1 - ) * .5f - ;
var min1 = axis1 - (edge1 - ) * .5f + ;
var max2 = axis2 + (edge2 - ) * .5f - ;
var min2 = axis2 - (edge2 - ) * .5f + ; return new Vector2(max1 > max2 ? max2 : max1, min1 > min2 ? min1 : min2);
} float EdgeRandom(float min, float max)
{
float cut = .5f;
var diff = max - min;
if (diff % == )
cut = 1f;
int c = (int)(diff / cut); return Random.Range(, c + ) * cut + min;
} void LineTwoPos(Vector3 pos1, Vector3 pos2)
{
if (pos1.y == pos2.y)
{
if (pos1.x == pos2.x)
{
var min = pos1.z < pos2.z ? pos1 : pos2;
var max = min.z == pos1.z ? pos2 : pos1; var diff = max.z - min.z; if (diff % == )
{
for (float i = min.z; i <= max.z; i++)
{
var pos = new Vector3(min.x, min.y, i); //绕路判定
if (Mathf.Abs(pos.z - max.z) > )
{
if (RayCast(pos, Dz, ))
{
var posex = new Vector3(pos.x, pos.y, pos.z + );
var cps = MapSystem.GetExCross(posex, max);
switch (ObstacleCrossType)
{
case ObstacleCrossType.Break:
CrossBreak(pos, max, cps);
InsSetPos(posex);
break;
case ObstacleCrossType.Detour:
CrossAround(pos, max, cps);
break;
}
return;
}
}
InsSetPos(pos);
}
}
else
{
for (float i = min.z; i < max.z; i++)
{
var pos = new Vector3(pos1.x, pos1.y, i); //绕路判定
if (Mathf.Abs(pos.z - max.z) > )
{
if (RayCast(pos, Dz, ))
{
var posex = new Vector3(pos.x, pos.y, pos.z + );
var cps = MapSystem.GetExCross(posex, max);
switch (ObstacleCrossType)
{
case ObstacleCrossType.Break:
CrossBreak(pos, max, cps);
InsSetPos(posex);
break;
case ObstacleCrossType.Detour:
CrossAround(pos, max, cps);
break;
}
return;
}
}
InsSetPos(pos);
}
InsSetPos(max);
}
}
else if (pos1.z == pos2.z)
{
var min = pos1.x < pos2.x ? pos1 : pos2;
var max = min.x == pos1.x ? pos2 : pos1; var diff = max.x - min.x; if (diff % == )
{
for (float i = min.x; i <= max.x; i++)
{
var pos = new Vector3(i, min.y, min.z);
//绕路判定
if (Mathf.Abs(pos.x - max.x) > )
{
if (RayCast(pos, Dx, ))
{
var posex = new Vector3(pos.x + , pos.y, pos.z);
var cps = MapSystem.GetExCross(posex, max);
switch (ObstacleCrossType)
{
case ObstacleCrossType.Break:
CrossBreak(pos, max, cps);
InsSetPos(posex);
break;
case ObstacleCrossType.Detour:
CrossAround(pos, max, cps);
break;
}
return;
}
}
InsSetPos(pos);
}
}
else
{
for (float i = min.x; i < max.x; i++)
{
var pos = new Vector3(i, pos1.y, pos1.z);
//绕路判定
if (Mathf.Abs(pos.x - max.x) > )
{
if (RayCast(pos, Dx, ))
{
var posex = new Vector3(pos.x + , pos.y, pos.z);
var cps = MapSystem.GetExCross(posex, max);
switch (ObstacleCrossType)
{
case ObstacleCrossType.Break:
CrossBreak(pos, max, cps);
InsSetPos(posex);
break;
case ObstacleCrossType.Detour:
CrossAround(pos, max, cps);
break;
}
return;
}
}
InsSetPos(pos);
}
InsSetPos(max);
}
}
}
} void LShapePos(Vector3 pos1, Vector3 pos2, Vector3 cp)
{
//判定折线中间点是否位于其他房间内,若位于,则需要重新寻路
var rt = MapSystem.GetRoomByPos(cp);
if (ObstacleCrossType == ObstacleCrossType.Detour && rt != null)
{
var to = new Vector2(rt.Length + , rt.Width + ) * .5f; var ned = rt.CenterPos - to;
var fod = rt.CenterPos + to; Vector3[] v4 = new Vector3[];
v4[] = new Vector3(ned.x, cp.y, ned.y);
v4[] = new Vector3(ned.x, cp.y, fod.y);
v4[] = new Vector3(fod.x, cp.y, ned.y);
v4[] = new Vector3(fod.x, cp.y, fod.y); var minx = pos1.x < pos2.x ? pos1.x : pos2.x;
var maxx = minx == pos1.x ? pos2.x : pos1.x;
var minz = pos1.z < pos2.z ? pos1.z : pos2.z;
var maxz = minz == pos1.z ? pos2.z : pos1.z; for (int i = ; i < v4.Length; i++)
{
if (v4[i].x > minx && v4[i].x < maxx && v4[i].z > minz && v4[i].z < maxz)
{
var ncp1 = new Vector3(cp.x, cp.y, v4[i].z);
var ncp2 = new Vector3(v4[i].x, cp.y, cp.z); var pos1cp = ncp1.x == pos1.x || ncp1.z == pos1.z ? ncp1 : ncp2;
var pos2cp = pos1cp == ncp1 ? ncp2 : ncp1; LShapePos(pos1, v4[i], pos1cp);
LShapePos(v4[i], pos2, pos2cp);
return;
}
}
} LineTwoPos(pos1, cp);
LineTwoPos(pos2, cp);
} void CrossAround(Vector3 pos, Vector3 max, ExCross cps)
{
if (cps.pos1 != Vector3.zero && cps.pos2 != Vector3.zero && cps.pos3 != Vector3.zero)
{
LineTwoPos(pos, cps.pos1);
LineTwoPos(cps.pos1, cps.pos2);
LineTwoPos(cps.pos2, cps.pos3);
LineTwoPos(cps.pos3, max);
}
} void CrossBreak(Vector3 pos, Vector3 max, ExCross cps)
{
if (cps.pos1 != Vector3.zero && cps.pos2 != Vector3.zero && cps.pos3 != Vector3.zero)
{
InsSetPos(pos);
LineTwoPos(cps.pos3, max);
}
} bool RayCast(Vector3 ori, Vector3 dir, float mD)
{
return MapSystem.RayCast(ori, dir, mD);
} void InsSetPos(Vector3 pos, Transform parent = null)
{
var temp = MapSystem.MapData.CrossUnitPos;
if (!temp.Contains(pos))
{
temp.Add(pos);
MapSystem.CrossUnitInsts.Add(MapSystem.InsSetPos(CrossUnit, pos, parent));
MapSystem.MapData.CrossUnitPos = temp;
}
}
}

最终效果展示:(单层,绕过障碍)

与上面同一个随机种子(断开):

关于随机种子的介绍和用法可以详见之前写的另一篇博客:

https://www.cnblogs.com/koshio0219/p/12514825.html

下面给出打包出的unity package工具包以供参考:

https://files.cnblogs.com/files/koshio0219/RMTool.zip

Unity 随机地图房间通道生成的更多相关文章

  1. Unity 随机房间地图生成

    无论是在迷宫还是类似于地牢的游戏地图中,利用程序来生成每次都不一样的地图是一件叫人兴奋不已的事. 这时我们需要解决两个非常重要的随机事件: 1.在一定范围内随机出各不相同但又不能互相重叠的房间 2.优 ...

  2. 星际SC地图制作中生成随机位置,也包括所有需要随机的效果

    星际SC地图制作中生成随机位置,也包括所有需要随机的效果 利用单位 kakaru T 开头那个, kakaru是随机变化位置 注意kakaru的放置位置和占用格子大小,kakaru周围放上LOCATI ...

  3. Unity随机随学

    1.什么是渲染管道? 是指在显示器上为了显示出图像而经过的一系列必要操作.渲染管道中的步骤很多,都要将几何物体从一个坐标系中变换到另一个坐标系中去. 主要步骤有: 本地坐标->视图坐标-> ...

  4. 【百度地图API】建立全国银行位置查询系统(四)——如何利用百度地图的数据生成自己的标注

    原文:[百度地图API]建立全国银行位置查询系统(四)--如何利用百度地图的数据生成自己的标注 摘要: 上一章留个悬念,"如果自己没有地理坐标的数据库,应该怎样制作银行的分布地图呢?&quo ...

  5. Unity中使用柏林噪声生成地图

    孙广东  2017.3.27 http://blog.csdn.NET/u010019717 主要是利用Unity的 Mathf.PerlinNoise   函数(柏林噪声)的不同寻常的功能. htt ...

  6. Unity3d 随机地图生成

    2D解析图: 3D地形: 嘿嘿.

  7. unity工具IGamesTools之批量生成帧动画

    unity工具IGamesTools批量生成帧动画,可批量的将指定文件夹下的帧动画图片自动生成对应的资源文件(Animation,AnimationController,Prefabs) unity工 ...

  8. cocos2d-x3.6 连连看随机地图实现

    我的博客:http://blog.csdn.net/dawn_moon 这一节来讲地图初始化实现. 连连看地图初始化有非常多实现方式,大概会有下面几种: 每一格的位置随机取图片放上去 随机取图片放到随 ...

  9. python随机图片验证码的生成

    Python生成随机验证码,需要使用PIL模块. 安装: 1 pip3 install pillow 基本使用 1. 创建图片 1 2 3 4 5 6 7 8 9 from PIL import Im ...

随机推荐

  1. MySQL学习(5)

    三 触发器 对某个表进行某种操作(如:增删改查),希望触发某个动作,可以使用触发器. 1.创建触发器 create trigger trigger1_before_insert_tb1 before ...

  2. OpenCV-Python ORB(面向快速和旋转的BRIEF) | 四十三

    目标 在本章中,我们将了解ORB的基础知识 理论 作为OpenCV的狂热者,关于ORB的最重要的事情是它来自" OpenCV Labs".该算法由Ethan Rublee,Vinc ...

  3. 如何测试Linux命令运行时间?

    良许在工作中,写过一个 Shell 脚本,这个脚本可以从 4 个 NTP 服务器轮流获取时间,然后将最可靠的时间设置为系统时间. 因为我们对于时间的要求比较高,需要在短时间内就获取到正确的时间.所以我 ...

  4. SpringBoot系列(二)入门知识

    SpringBoot系列(二)入门知识 往期推荐 SpringBoot系列(一)idea新建springboot项目 引言 本来新建springboot项目应该放在入门知识这一章的,但是由于新建spr ...

  5. SpringBoot环境搭建及第一个程序运行(详细!)

    spring boot简介 spring boot框架抛弃了繁琐的xml配置过程,采用大量的默认配置简化我们的开发过程. 所以采用Spring boot可以非常容易和快速地创建基于Spring 框架的 ...

  6. Material Design 组件之 AppBarLayout

    AppBarLayout 是一个垂直方向的 LinearLayout,它实现了许多符合 Material Design 设计规范的状态栏应该具有的功能,比如滚动手势. AppBarLayout 一般直 ...

  7. 读者来信 | 设置HBase TTL必须先disable表吗?(已解决)

    今日有朋友加好友与我探讨一些问题,我觉得这些问题倒挺有价值的:于是就想在本公众号开设一个问答专栏,方便技术交流与分享,专栏名就定为:<读者来信>.如遇到本人能力有限难以解决的问题,该贴将会 ...

  8. supervisor 的使用 (fastcgi管理)

    本文主要介绍 supervisor 对 fastcgi 进程的管理 fastcgi 进程的管理 在php 中,php-fpm 有主进程来管理和维护子进程的数量.但是并不是所有的服务都有类似的主进程来做 ...

  9. M - 诡异的楼梯 HDU - 1180(BFS + 在某个点等待一下 / 重复走该点)

    M - 诡异的楼梯 HDU - 1180 Hogwarts正式开学以后,Harry发现在Hogwarts里,某些楼梯并不是静止不动的,相反,他们每隔一分钟就变动一次方向. 比如下面的例子里,一开始楼梯 ...

  10. WEB安全——XML注入

    浅析XML注入 认识XML DTD XML注入 XPath注入 XSL和XSLT注入 前言前段时间学习了.net,通过更改XML让连接数据库变得更方便,简单易懂,上手无压力,便对XML注入这块挺感兴趣 ...