Unity 随机地图房间通道生成
之前的博客中已经说了随机房间生成:
https://www.cnblogs.com/koshio0219/p/12604383.html
但实现房间生成只是整个地图生成最初最简单的一步。下面讨论如何随机生成连接房间的通道。
房间的通道一般要满足以下特性和特征:
1.保证所有随机生成的房间都可以通过通道从起点到达终点,不出现未连接或连接中断的情况。
2.通道在生成的过程中不能穿过房间内部。
3.考虑是简洁直接的连接方式,还是更为曲折的通道。
好了,现在眼前出现了很多随机生成的房间,那么问题是:到底哪两个房间需要连接一条通道呢,如果以最快的速度得出所有需要连接的通道列表呢?
这时,我们先不用将空间中生成的这些房间仅仅当前是房间来看待,实质上它们就是分布在空间中的一些点的集合,每一个点都能求出当前离它们最近的点是哪一个,
那么一个非常简单的算法是,我们可以记录两个列表:已经处于连接状态的点的列表(闭合列表),尚未取得连接的点的列表(开放列表)。
先随机一个点作为起点,连接离该起点最近的点,将起点和连接点置入闭合列表中,再以第二个点作为起点连接其最近的点,以此方式不断循环,直至所有的开放列表清空,所有通道的连接数据即全部计算完毕。
上面这样虽然可以很快得出所有连接的通道列表,但缺乏连接的随机性,也无法产生分支路径,游戏性很低。
因此,可以考虑给每个连接的点增加额外分支的概率,这样每个点就至少需要计算与它最近的两个点的位置,在概率的控制下其中一个作为主要路径链表中的值,另一个不连接或连接为分支置入闭合列表中。
生成之后的房间数据结构最终会是一个二叉树。
主要属性:
1 public bool bDebug = false; 2 public MapData MapData; 3 public RoomBuilder RoomBuilder; 4 public CrossBuilder CrossBuilder; 5 6 //所有生成的房间列表 7 public List<RoomTran> GenRooms = new List<RoomTran>(); 8 //当前未连接房间列表 9 public List<RoomTran> UnCrossRooms = new List<RoomTran>(); 10 11 private RoomTran FirstRoom; 12 private RoomTran LastRoom; 13 14 //额外路径概率 15 public float AnotherCrossProbability = .2f; 16 //死路概率 17 public float DeadCrossProbability = .5f; 18 //死路延长概率 19 public float DeadAwayProbability = .6f; 20 21 //死路房间节点列表(计算时排除) 22 List<RoomTran> DeadRooms = new List<RoomTran>(); 23 //每个房间连接其他房间的字典 24 Dictionary<RoomTran, List<RoomTran>> RoomCrossRooms = new Dictionary<RoomTran, List<RoomTran>>(); 25 //主通路 26 public LinkedList<RoomTran> MainCross = new LinkedList<RoomTran>(); 27 28 //结束断点列表 29 List<RoomTran> EndRooms = new List<RoomTran>(); 30 31 [HideInInspector] 32 public List<GameObject> CrossUnitInsts = new List<GameObject>(); 33 [HideInInspector] 34 public List<GameObject> RoomUnitInsts = new List<GameObject>();
地图数据结构:
1 using System.Collections.Generic; 2 using UnityEngine; 3 4 public class MapData : MonoBehaviour 5 { 6 public Vector3Int MapCenter; 7 8 public Dictionary<int, RoomData> RoomDataDic = new Dictionary<int, RoomData>(); 9 10 public List<CrossData> CrossDataList = new List<CrossData>(); 11 12 public Dictionary<int, List<int>> RoomCrossRoomsDic = new Dictionary<int, List<int>>(); 13 14 public List<Vector3> CrossUnitPos = new List<Vector3>(); 15 } 16 17 public struct CrossData 18 { 19 public int id1; 20 public int id2; 21 }
核心计算函数:
1 void CalNextCross(RoomTran nr, RoomTran br = null) 2 { 3 MainCross.AddLast(nr); 4 LRemove(UnCrossRooms, nr); 5 6 var fcl = FindLatelyRooms(nr); 7 8 if (fcl.FirstLately != null) 9 { 10 if (fcl.SecondLately != null) 11 { 12 if (Random.value < AnotherCrossProbability) 13 { 14 if (Random.value < DeadCrossProbability) 15 { 16 var dr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately; 17 var lr = dr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately; 18 19 LAdd(DeadRooms, dr); 20 21 AddItemToRoomDic(nr, lr, dr, br); 22 LRemove(UnCrossRooms, dr); 23 LRemove(UnCrossRooms, lr); 24 25 CalDeadCross(dr, nr); 26 CalNextCross(lr, nr); 27 } 28 else 29 { 30 var mr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately; 31 var or = mr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately; 32 AddItemToRoomDic(or, nr); 33 AddItemToRoomDic(nr, mr, or, br); 34 CalNextCross(mr, nr); 35 } 36 } 37 else 38 { 39 AddItemToRoomDic(nr, br, fcl.FirstLately); 40 CalNextCross(fcl.FirstLately, nr); 41 } 42 } 43 else 44 { 45 AddItemToRoomDic(nr, br, fcl.FirstLately); 46 CalNextCross(fcl.FirstLately, nr); 47 } 48 } 49 else 50 { 51 //计算结束 52 LastRoom = nr; 53 AddItemToRoomDic(nr, br); 54 LAdd(EndRooms, nr); 55 56 Debug.Log("生成房间数:" + GenRooms.Count); 57 Debug.Log("主路径房间数:" + MainCross.Count); 58 Debug.Log("分支房间数:" + DeadRooms.Count); 59 Debug.Log("端点房间数:" + EndRooms.Count); 60 61 //更新地图数据 62 UpdateMapData(); 63 //按顺序执行实际连接 64 CrossBuilder.StartCoroutine(CrossBuilder.GenCrosses()); 65 } 66 }
完整的MapSystem脚本:
1 using System.Collections.Generic; 2 using UnityEngine; 3 4 //这个脚本找出哪些房间之间需要互相连接通道 5 public class MapSystem : MonoBehaviour 6 { 7 public bool bDebug = false; 8 public MapData MapData; 9 public RoomBuilder RoomBuilder; 10 public CrossBuilder CrossBuilder; 11 12 //所有生成的房间列表 13 public List<RoomTran> GenRooms = new List<RoomTran>(); 14 //当前未连接房间列表 15 public List<RoomTran> UnCrossRooms = new List<RoomTran>(); 16 17 private RoomTran FirstRoom; 18 private RoomTran LastRoom; 19 20 //额外路径概率 21 public float AnotherCrossProbability = .2f; 22 //死路概率 23 public float DeadCrossProbability = .5f; 24 //死路延长概率 25 public float DeadAwayProbability = .6f; 26 27 //死路房间节点列表(计算时排除) 28 List<RoomTran> DeadRooms = new List<RoomTran>(); 29 //每个房间连接其他房间的字典 30 Dictionary<RoomTran, List<RoomTran>> RoomCrossRooms = new Dictionary<RoomTran, List<RoomTran>>(); 31 //主通路 32 public LinkedList<RoomTran> MainCross = new LinkedList<RoomTran>(); 33 34 //结束断点列表 35 List<RoomTran> EndRooms = new List<RoomTran>(); 36 37 [HideInInspector] 38 public List<GameObject> CrossUnitInsts = new List<GameObject>(); 39 [HideInInspector] 40 public List<GameObject> RoomUnitInsts = new List<GameObject>(); 41 42 //建筑单位标签 43 public const string S_TAG = "Unit"; 44 45 [HideInInspector] 46 public bool bAction = false; 47 48 public void Start() 49 { 50 CrossBuilder = GetComponent<CrossBuilder>(); 51 RoomBuilder = GetComponent<RoomBuilder>(); 52 } 53 54 void SetSeed(bool bDebug) 55 { 56 int seed; 57 if (bDebug) 58 { 59 seed = PlayerPrefs.GetInt("Seed"); 60 61 } 62 else 63 { 64 seed = (int)System.DateTime.Now.Ticks; 65 PlayerPrefs.SetInt("Seed", seed); 66 } 67 Random.InitState(seed); 68 } 69 70 public void RandRoomDatas() 71 { 72 if (RoomBuilder == null || MapData == null) 73 return; 74 75 bAction = true; 76 SetSeed(bDebug); 77 RoomBuilder.StartCoroutine(RoomBuilder.GenRooms(MapData.MapCenter, () => 78 { 79 CreatRoomData(); 80 RandRoomCrosses(); 81 })); 82 } 83 84 public void RandRoomCrosses() 85 { 86 if (GenRooms.Count <= 0) return; 87 88 FirstRoom = GenRooms[Random.Range(0, GenRooms.Count)]; 89 90 CalNextCross(FirstRoom); 91 } 92 93 void UpdateMapData() 94 { 95 foreach (var rd in MapData.RoomDataDic) 96 { 97 var rt = rd.Value.RoomTran; 98 if (RoomCrossRooms.ContainsKey(rt)) 99 { 100 var temp = new List<int>(); 101 foreach (var crt in RoomCrossRooms[rt]) 102 { 103 var id = GetRoomIdByRT(crt); 104 if (id > 0) 105 { 106 temp.Add(id); 107 108 rd.Value.CrossRooms.Add(MapData.RoomDataDic[id]); 109 110 var cd = new CrossData(); 111 cd.id1 = rd.Key; 112 cd.id2 = id; 113 if (!CrossDataContains(cd)) 114 MapData.CrossDataList.Add(cd); 115 } 116 } 117 MapData.RoomCrossRoomsDic.Add(rd.Key, temp); 118 } 119 120 if (MainCross.Contains(rt)) 121 { 122 rd.Value.bMainCrossRoom = true; 123 if (EndRooms.Contains(rt)) 124 { 125 rd.Value.BattleType = RoomBattleType.BossBattle; 126 } 127 } 128 129 if (EndRooms.Contains(rt)) 130 { 131 rd.Value.bEndRoom = true; 132 } 133 } 134 } 135 136 bool CrossDataContains(CrossData d) 137 { 138 foreach (var cd in MapData.CrossDataList) 139 { 140 if ((cd.id1 == d.id1 && cd.id2 == d.id2) || (cd.id1 == d.id2 && cd.id2 == d.id1)) 141 return true; 142 } 143 return false; 144 } 145 146 int GetRoomIdByRT(RoomTran rt) 147 { 148 foreach (var rd in MapData.RoomDataDic) 149 { 150 if (rd.Value.RoomTran == rt) 151 return rd.Key; 152 } 153 return -1; 154 } 155 156 void CreatRoomData() 157 { 158 for (int i = 1; i < GenRooms.Count + 1; i++) 159 { 160 var rd = new RoomData(); 161 rd.Id = i; 162 rd.RoomTran = GenRooms[i - 1]; 163 rd.BattleType = RoomBattleType.NormalBattle; 164 if (rd.Id == 1) 165 rd.BattleType = RoomBattleType.Rest; 166 rd.CrossRooms = new List<RoomData>(); 167 rd.Monsters = new List<GameObject>(); 168 rd.bEndRoom = false; 169 rd.bMainCrossRoom = false; 170 171 MapData.RoomDataDic.Add(rd.Id, rd); 172 } 173 } 174 175 void CalNextCross(RoomTran nr, RoomTran br = null) 176 { 177 MainCross.AddLast(nr); 178 LRemove(UnCrossRooms, nr); 179 180 var fcl = FindLatelyRooms(nr); 181 182 if (fcl.FirstLately != null) 183 { 184 if (fcl.SecondLately != null) 185 { 186 if (Random.value < AnotherCrossProbability) 187 { 188 if (Random.value < DeadCrossProbability) 189 { 190 var dr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately; 191 var lr = dr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately; 192 193 LAdd(DeadRooms, dr); 194 195 AddItemToRoomDic(nr, lr, dr, br); 196 LRemove(UnCrossRooms, dr); 197 LRemove(UnCrossRooms, lr); 198 199 CalDeadCross(dr, nr); 200 CalNextCross(lr, nr); 201 } 202 else 203 { 204 var mr = Random.value < .5f ? fcl.FirstLately : fcl.SecondLately; 205 var or = mr == fcl.FirstLately ? fcl.SecondLately : fcl.FirstLately; 206 AddItemToRoomDic(or, nr); 207 AddItemToRoomDic(nr, mr, or, br); 208 CalNextCross(mr, nr); 209 } 210 } 211 else 212 { 213 AddItemToRoomDic(nr, br, fcl.FirstLately); 214 CalNextCross(fcl.FirstLately, nr); 215 } 216 } 217 else 218 { 219 AddItemToRoomDic(nr, br, fcl.FirstLately); 220 CalNextCross(fcl.FirstLately, nr); 221 } 222 } 223 else 224 { 225 //计算结束 226 LastRoom = nr; 227 AddItemToRoomDic(nr, br); 228 LAdd(EndRooms, nr); 229 230 Debug.Log("生成房间数:" + GenRooms.Count); 231 Debug.Log("主路径房间数:" + MainCross.Count); 232 Debug.Log("分支房间数:" + DeadRooms.Count); 233 Debug.Log("端点房间数:" + EndRooms.Count); 234 235 //更新地图数据 236 UpdateMapData(); 237 //按顺序执行实际连接 238 CrossBuilder.StartCoroutine(CrossBuilder.GenCrosses()); 239 } 240 } 241 242 CrossLately FindLatelyRooms(RoomTran tar) 243 { 244 var cl = new CrossLately(); 245 float firstSqrdis = Mathf.Infinity; 246 float secondSqrdis = Mathf.Infinity; 247 248 foreach (var room in UnCrossRooms) 249 { 250 var rc = new Vector3(room.CenterPos.x, room.PosY, room.CenterPos.y); 251 var tc = new Vector3(tar.CenterPos.x, tar.PosY, tar.CenterPos.y); 252 float sqrdis = (rc - tc).sqrMagnitude; 253 254 if (sqrdis < firstSqrdis) 255 { 256 firstSqrdis = sqrdis; 257 cl.FirstLately = room; 258 } 259 else if (sqrdis < secondSqrdis) 260 { 261 secondSqrdis = sqrdis; 262 cl.SecondLately = room; 263 } 264 } 265 return cl; 266 } 267 268 //计算死路,此处死路没有分支 269 void CalDeadCross(RoomTran fdr, RoomTran bdr) 270 { 271 var temp = FindLatelyRooms(fdr); 272 if (temp.FirstLately != null && Random.value < DeadAwayProbability) 273 { 274 LRemove(UnCrossRooms, temp.FirstLately); 275 LAdd(DeadRooms, temp.FirstLately); 276 AddItemToRoomDic(fdr, temp.FirstLately, bdr); 277 CalDeadCross(temp.FirstLately, fdr); 278 } 279 else 280 { 281 //彻底死亡 282 LRemove(UnCrossRooms, fdr); 283 LAdd(DeadRooms, fdr); 284 AddItemToRoomDic(fdr, bdr); 285 LAdd(EndRooms, fdr); 286 } 287 } 288 289 void LRemove<T>(List<T> list, T item) 290 { 291 if (list.Contains(item)) 292 { 293 list.Remove(item); 294 } 295 } 296 297 void LAdd<T>(List<T> list, T item) 298 { 299 if (!list.Contains(item)) 300 { 301 list.Add(item); 302 } 303 } 304 305 void AddItemToRoomDic(RoomTran key, RoomTran item1, RoomTran item2 = null, RoomTran item3 = null) 306 { 307 if (RoomCrossRooms.ContainsKey(key)) 308 { 309 if (item1 != null) 310 if (!RoomCrossRooms[key].Contains(item1)) 311 RoomCrossRooms[key].Add(item1); 312 if (item2 != null) 313 if (!RoomCrossRooms[key].Contains(item2)) 314 RoomCrossRooms[key].Add(item2); 315 if (item3 != null) 316 if (!RoomCrossRooms[key].Contains(item3)) 317 RoomCrossRooms[key].Add(item3); 318 } 319 else 320 { 321 RoomCrossRooms.Add(key, new List<RoomTran>()); 322 AddItemToRoomDic(key, item1, item2, item3); 323 } 324 } 325 326 public bool RayCast(Vector3 ori, Vector3 dir, float mD) 327 { 328 Ray ray = new Ray(ori, dir); 329 RaycastHit info; 330 if (Physics.Raycast(ray, out info, mD)) 331 { 332 if (info.transform.tag == S_TAG) 333 return true; 334 } 335 return false; 336 } 337 338 public GameObject InsSetPos(GameObject prefab,Vector3 pos, Transform parent = null) 339 { 340 var ins = Instantiate(prefab); 341 ins.transform.position = pos; 342 ins.transform.parent = parent; 343 return ins; 344 } 345 346 public RoomTran GetRoomByPos(Vector3 pos) 347 { 348 foreach (var room in GenRooms) 349 { 350 var to = new Vector3(room.Length, RoomBuilder.FixedUnitHeight, room.Width) * .5f; 351 352 var centerPos = new Vector3(room.CenterPos.x, room.PosY, room.CenterPos.y); 353 var ned = centerPos - to; 354 var fod = centerPos + to; 355 356 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) 357 { 358 return room; 359 } 360 } 361 return null; 362 } 363 364 public ExCross GetExCross(Vector3 strPos, Vector3 tarPos) 365 { 366 var ec = new ExCross(); 367 var rt = GetRoomByPos(strPos); 368 if (rt != null) 369 { 370 var to = new Vector3(rt.Length - 1, RoomBuilder.FixedUnitHeight - 1, rt.Width - 1) * .5f; 371 372 var centerPos = new Vector3(rt.CenterPos.x, rt.PosY, rt.CenterPos.y); 373 var ned = centerPos - to; 374 var fod = centerPos + to; 375 if (strPos.x == tarPos.x) 376 { 377 if (Random.value < .5f) 378 { 379 ec.pos1 = strPos.z < tarPos.z ? new Vector3(ned.x - 1, strPos.y, strPos.z - 1) : new Vector3(ned.x - 1, strPos.y, strPos.z + 1); 380 ec.pos2 = strPos.z < tarPos.z ? ec.pos1 + new Vector3(0, 0, rt.Width + 1) : ec.pos1 - new Vector3(0, 0, rt.Width + 1); 381 ec.pos3 = new Vector3(strPos.x, strPos.y, ec.pos2.z); 382 } 383 else 384 { 385 ec.pos1 = strPos.z < tarPos.z ? new Vector3(fod.x + 1, strPos.y, strPos.z - 1) : new Vector3(fod.x + 1, strPos.y, strPos.z + 1); 386 ec.pos2 = strPos.z < tarPos.z ? ec.pos1 + new Vector3(0, 0, rt.Width + 1) : ec.pos1 - new Vector3(0, 0, rt.Width + 1); 387 ec.pos3 = new Vector3(strPos.x, strPos.y, ec.pos2.z); 388 } 389 390 } 391 else if (strPos.z == tarPos.z) 392 { 393 if (Random.value < .5f) 394 { 395 ec.pos1 = strPos.x < tarPos.x ? new Vector3(strPos.x - 1, strPos.y, ned.z - 1) : new Vector3(strPos.x + 1, strPos.y, ned.z - 1); 396 ec.pos2 = strPos.x < tarPos.x ? ec.pos1 + new Vector3(rt.Length + 1, 0, 0) : ec.pos1 - new Vector3(rt.Length + 1, 0, 0); 397 ec.pos3 = new Vector3(ec.pos2.x, strPos.y, strPos.z); 398 } 399 else 400 { 401 ec.pos1 = strPos.x < tarPos.x ? new Vector3(strPos.x - 1, strPos.y, fod.z + 1) : new Vector3(strPos.x + 1, strPos.y, fod.z + 1); 402 ec.pos2 = strPos.x < tarPos.x ? ec.pos1 + new Vector3(rt.Length + 1, 0, 0) : ec.pos1 - new Vector3(rt.Length + 1, 0, 0); 403 ec.pos3 = new Vector3(ec.pos2.x, strPos.y, strPos.z); 404 } 405 } 406 } 407 return ec; 408 } 409 410 public void OnClickReset() 411 { 412 if (bAction) 413 return; 414 415 if (CrossUnitInsts.Count == 0 && RoomUnitInsts.Count == 0) 416 return; 417 418 for(int i = 0; i < CrossUnitInsts.Count; i++) 419 { 420 Destroy(CrossUnitInsts[i]); 421 } 422 423 for (int i = 0; i < RoomUnitInsts.Count; i++) 424 { 425 Destroy(RoomUnitInsts[i]); 426 } 427 428 ClearAll(); 429 RandRoomDatas(); 430 } 431 432 public void ClearAll() 433 { 434 GenRooms.Clear(); 435 UnCrossRooms.Clear(); 436 DeadRooms.Clear(); 437 EndRooms.Clear(); 438 MainCross.Clear(); 439 RoomCrossRooms.Clear(); 440 RoomUnitInsts.Clear(); 441 CrossUnitInsts.Clear(); 442 443 MapData.RoomDataDic.Clear(); 444 MapData.RoomCrossRoomsDic.Clear(); 445 MapData.CrossDataList.Clear(); 446 MapData.CrossUnitPos.Clear(); 447 } 448 } 449 450 public struct CrossLately 451 { 452 public RoomTran FirstLately; 453 public RoomTran SecondLately; 454 } 455 456 public struct ExCross 457 { 458 public Vector3 pos1; 459 public Vector3 pos2; 460 public Vector3 pos3; 461 }
在计算完所有的房间连接通路后,更新地图数据,随后再根据地图数据中的通路情况进行实际通路连接。
实际连接的过程中很多都是数学问题,需要单独分析两个房间的位置关系,基本分析模式如下:
1.两个房间是否位于同一层,如果不是,是否有重叠区域
(通过分析边缘坐标的极限值来判断交叠情况,例如当其中一个房间任意一个轴向的最小值大于目标房间对应轴向的最大值或该轴向的最大值小于目标房间轴向的最小值时,认为两个房间有重叠的轴向区域,否则在该轴向上无重叠)
2.如果两个房间位于同一层,或本来就只生成单层的地图,考虑这两个房间是否共边,共边和不共边的连接方式是有区别的
3.考虑在连接的过程中遭遇其他房间或障碍物时如何处理,是绕过障碍物前进还是断开连接,如何绕开障碍物
最简单的就是共边的情况:
这时可以选择共边范围内的任意一条直线作为连接方式(上图绿色区域),这里注意临界点应该排除在外(因为实际情况下房间因为有墙壁的厚度,最外边的一格大小是没办法形成通路的)
判断共边与否的方式就是计算临界值:
1 Vector2 CheckCommonSide(float axis1, int edge1, float axis2, int edge2) 2 { 3 var max1 = axis1 + (edge1 - 1) * .5f - 1; 4 var min1 = axis1 - (edge1 - 1) * .5f + 1; 5 var max2 = axis2 + (edge2 - 1) * .5f - 1; 6 var min2 = axis2 - (edge2 - 1) * .5f + 1; 7 8 return new Vector2(max1 > max2 ? max2 : max1, min1 > min2 ? min1 : min2); 9 }
如上,如果返回的值中y>x,则对应轴向不共边(这里之所以给边的长度计算时-1考虑房间的墙壁厚度单位)
第二种情况,不共边但在同一层:
这时就会有两种接近最短路线的折线连接方式,首先我们依然需要找出两个房间最靠近的那四个临界值的点,在判断出无共边区域后选取其中一对坐标进行L形折线连接,L形的折线连接实质上就是两段直线连接,只不过增加了一个中间点。
但上面的考虑有时是过于理想的情况,事实上在随机生成的房间中很容易出现连接路径被其他房间阻挡的情况,最直接的处理方式显然是直接断开,反正通过断开的房间通路过渡也是可以达到目标的,但下面要考虑的是如何绕过目标的做法,
具体来说也分为两种情况:
1.一段直线过程中有其他房间,这样可以考虑直接取得障碍房间左侧或右侧的两个点进行过渡:
1 void CrossAround(Vector3 pos, Vector3 max, ExCross cps) 2 { 3 if (cps.pos1 != Vector3.zero && cps.pos2 != Vector3.zero && cps.pos3 != Vector3.zero) 4 { 5 LineTwoPos(pos, cps.pos1); 6 LineTwoPos(cps.pos1, cps.pos2); 7 LineTwoPos(cps.pos2, cps.pos3); 8 LineTwoPos(cps.pos3, max); 9 } 10 }
下面这种情况稍微复杂一点,就是:
2.折线的中点在障碍物房间内,这时,简单的直线绕道是不可取的
需要计算障碍物房间四个角中最合适的一个边缘角进行过渡:
1 void LShapePos(Vector3 pos1, Vector3 pos2, Vector3 cp) 2 { 3 //判定折线中间点是否位于其他房间内,若位于,则需要重新寻路 4 var rt = MapSystem.GetRoomByPos(cp); 5 if (ObstacleCrossType == ObstacleCrossType.Detour && rt != null) 6 { 7 var to = new Vector2(rt.Length + 1, rt.Width + 1) * .5f; 8 9 var ned = rt.CenterPos - to; 10 var fod = rt.CenterPos + to; 11 12 Vector3[] v4 = new Vector3[4]; 13 v4[0] = new Vector3(ned.x, cp.y, ned.y); 14 v4[1] = new Vector3(ned.x, cp.y, fod.y); 15 v4[2] = new Vector3(fod.x, cp.y, ned.y); 16 v4[3] = new Vector3(fod.x, cp.y, fod.y); 17 18 var minx = pos1.x < pos2.x ? pos1.x : pos2.x; 19 var maxx = minx == pos1.x ? pos2.x : pos1.x; 20 var minz = pos1.z < pos2.z ? pos1.z : pos2.z; 21 var maxz = minz == pos1.z ? pos2.z : pos1.z; 22 23 for (int i = 0; i < v4.Length; i++) 24 { 25 if (v4[i].x > minx && v4[i].x < maxx && v4[i].z > minz && v4[i].z < maxz) 26 { 27 var ncp1 = new Vector3(cp.x, cp.y, v4[i].z); 28 var ncp2 = new Vector3(v4[i].x, cp.y, cp.z); 29 30 var pos1cp = ncp1.x == pos1.x || ncp1.z == pos1.z ? ncp1 : ncp2; 31 var pos2cp = pos1cp == ncp1 ? ncp2 : ncp1; 32 33 LShapePos(pos1, v4[i], pos1cp); 34 LShapePos(v4[i], pos2, pos2cp); 35 return; 36 } 37 } 38 } 39 40 LineTwoPos(pos1, cp); 41 LineTwoPos(pos2, cp); 42 }
判断这四个角中哪一个点为最合适点,其实就是要判断哪个点位于这条L折线组成的矩形的范围之内,最终只会有一个点满足该情况。
下面是完整的连接脚本:
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using System.IO; 5 6 public enum ObstacleCrossType 7 { 8 Break, 9 Detour 10 } 11 12 //这个脚本执行各个房间通道连接的具体方法 13 public class CrossBuilder : MonoBehaviour 14 { 15 public GameObject CrossUnit; 16 public ObstacleCrossType ObstacleCrossType; 17 18 private MapSystem MapSystem; 19 20 Vector3Int Dx = new Vector3Int(1, 0, 0); 21 Vector3Int Dy = new Vector3Int(0, 1, 0); 22 Vector3Int Dz = new Vector3Int(0, 0, 1); 23 24 private int UnitHeight; 25 26 private void Start() 27 { 28 MapSystem = GetComponent<MapSystem>(); 29 UnitHeight = MapSystem.RoomBuilder.FixedUnitHeight; 30 } 31 32 public IEnumerator GenCrosses() 33 { 34 var fr = MapSystem.MainCross.First.Value; 35 var lr = MapSystem.MainCross.Last.Value; 36 37 var frcp = new Vector3(fr.CenterPos.x, fr.PosY, fr.CenterPos.y); 38 var lrcp = new Vector3(lr.CenterPos.x, lr.PosY, lr.CenterPos.y); 39 40 Debug.Log("First: " + frcp); 41 Debug.Log("Last: " + lrcp); 42 43 foreach (var cd in MapSystem.MapData.CrossDataList) 44 { 45 CrossTwoRoom(cd.id1, cd.id2); 46 yield return null; 47 } 48 MapSystem.bAction = false; 49 } 50 51 void CrossTwoRoom(int id1, int id2) 52 { 53 var from = MapSystem.MapData.RoomDataDic[id1].RoomTran; 54 var to = MapSystem.MapData.RoomDataDic[id2].RoomTran; 55 56 var frcp = new Vector3(from.CenterPos.x, from.PosY, from.CenterPos.y); 57 var trcp = new Vector3(to.CenterPos.x, to.PosY, to.CenterPos.y); 58 59 Debug.DrawLine(frcp, trcp, Color.red); 60 Debug.Log("F:" + frcp + " " + "To: " + trcp); 61 62 if (from.PosY == to.PosY) 63 { 64 //同一层 65 SameYCross(from, to); 66 } 67 else 68 { 69 //不同层 70 var rangex = CheckCommonSide(frcp.x, from.Length, trcp.x, to.Length); 71 var rangez = CheckCommonSide(frcp.z, from.Width, trcp.z, to.Width); 72 if (rangex.x >= rangex.y && rangez.x >= rangez.y) 73 { 74 //有重叠区域 75 var rx = EdgeRandom(rangex.y, rangex.x); 76 var rz = EdgeRandom(rangez.y, rangez.x); 77 78 var fy = from.PosY - (UnitHeight - 1) * .5f + 1; 79 var ty = to.PosY - (UnitHeight - 1) * .5f + 1; 80 81 var miny = fy < ty ? fy : ty; 82 var maxy = miny == fy ? ty : fy; 83 84 for (float i = miny; i < maxy + 1; i++) 85 { 86 InsSetPos(new Vector3(rx, i, rz)); 87 } 88 } 89 else 90 { 91 //无重叠区域 92 var cp = SameYCross(from, to); 93 94 var fy = from.PosY - (UnitHeight - 1) * .5f + 1; 95 var ty = to.PosY - (UnitHeight - 1) * .5f + 1; 96 97 var miny = fy < ty ? fy : ty; 98 var maxy = miny == fy ? ty : fy; 99 100 for (float i = miny; i < maxy + 1; i++) 101 { 102 InsSetPos(new Vector3(cp.x, i, cp.z)); 103 } 104 } 105 } 106 } 107 108 Vector3 SameYCross(RoomTran from, RoomTran to) 109 { 110 //Test 111 if(from.CenterPos==new Vector2Int(2,21)&&to.CenterPos==new Vector2Int(19, -23)) 112 { 113 114 } 115 116 var frcp = new Vector3(from.CenterPos.x, from.PosY, from.CenterPos.y); 117 var trcp = new Vector3(to.CenterPos.x, to.PosY, to.CenterPos.y); 118 119 var sr = Random.value < .5f ? from : to; 120 var or = sr == from ? to : from; 121 var ry = sr.PosY - (UnitHeight - 1) * .5f + 1; 122 123 var rangex = CheckCommonSide(frcp.x, from.Length, trcp.x, to.Length); 124 var rangez = CheckCommonSide(frcp.z, from.Width, trcp.z, to.Width); 125 126 Vector3 pos1; 127 Vector3 pos2; 128 129 if (rangex.y > rangex.x) 130 { 131 if (rangez.y > rangez.x) 132 { 133 //无公共边范围 134 var fxmax = frcp.x + (from.Length - 1) * .5f - 1; 135 var fzmax = frcp.z + (from.Width - 1) * .5f - 1; 136 137 if (fxmax == rangex.x) 138 { 139 if (fzmax == rangez.x) 140 { 141 //to在from的右上 142 var fe = Random.value < .5f ? fxmax : fzmax; 143 if (fe == fxmax) 144 { 145 pos1 = new Vector3(fe + 1, ry, fzmax); 146 pos2 = new Vector3(rangex.y, ry, rangez.y - 1); 147 //临界值判断 148 if (pos1.x >= pos2.x) 149 { 150 pos2 = new Vector3(pos1.x + 1, ry, pos2.z); 151 } 152 153 if (pos1.z >= pos2.z) 154 { 155 pos1 = new Vector3(pos1.x, ry, pos2.z - 1); 156 } 157 158 var cp = new Vector3(pos2.x, ry, pos1.z); 159 LShapePos(pos1, pos2, cp); 160 } 161 else 162 { 163 pos1 = new Vector3(fxmax, ry, fe + 1); 164 pos2 = new Vector3(rangex.y - 1, ry, rangez.y); 165 166 if (pos1.x >= pos2.x) 167 { 168 pos1 = new Vector3(pos2.x - 1, ry, pos1.z); 169 } 170 171 if (pos1.z >= pos2.z) 172 { 173 pos2 = new Vector3(pos2.x, ry, pos1.z + 1); 174 } 175 176 var cp = new Vector3(pos1.x, ry, pos2.z); 177 LShapePos(pos1, pos2, cp); 178 } 179 } 180 else 181 { 182 //to在from的右下 183 var fe = Random.value < .5f ? fxmax : rangez.y; 184 if (fe == fxmax) 185 { 186 pos1 = new Vector3(fe + 1, ry, rangez.y); 187 pos2 = new Vector3(rangex.y, ry, rangez.x + 1); 188 189 if (pos1.x >= pos2.x) 190 { 191 pos2 = new Vector3(pos1.x + 1, ry, pos2.z); 192 } 193 194 if (pos1.z <= pos2.z) 195 { 196 pos1 = new Vector3(pos1.x, ry, pos2.z + 1); 197 } 198 199 var cp = new Vector3(pos2.x, ry, pos1.z); 200 LShapePos(pos1, pos2, cp); 201 } 202 else 203 { 204 pos1 = new Vector3(rangex.x, ry, fe - 1); 205 pos2 = new Vector3(rangex.y - 1, ry, rangez.x); 206 207 if (pos1.x >= pos2.x) 208 { 209 pos1 = new Vector3(pos2.x - 1, ry, pos1.z); 210 } 211 212 if (pos1.z <= pos2.z) 213 { 214 pos2 = new Vector3(pos2.x, ry, pos1.z - 1); 215 } 216 217 var cp = new Vector3(pos1.x, ry, pos2.z); 218 LShapePos(pos1, pos2, cp); 219 } 220 } 221 } 222 else 223 { 224 if (fzmax == rangez.x) 225 { 226 //to在from的左上 227 var fe = Random.value < .5f ? rangex.y : fzmax; 228 if (fe == fzmax) 229 { 230 pos1 = new Vector3(rangex.y, ry, fe + 1); 231 pos2 = new Vector3(rangex.x + 1, ry, rangez.y); 232 233 if (pos1.x <= pos2.x) 234 { 235 pos1 = new Vector3(pos2.x + 1, ry, pos1.z); 236 } 237 238 if (pos1.z >= pos2.z) 239 { 240 pos2 = new Vector3(pos2.x, ry, pos1.z + 1); 241 } 242 243 var cp = new Vector3(pos1.x, ry, pos2.z); 244 LShapePos(pos1, pos2, cp); 245 } 246 else 247 { 248 pos1 = new Vector3(fe - 1, ry, rangez.x); 249 pos2 = new Vector3(rangex.x, ry, rangez.y - 1); 250 251 if (pos1.x <= pos2.x) 252 { 253 pos2 = new Vector3(pos1.x - 1, ry, pos2.z); 254 } 255 256 if (pos1.z >= pos2.z) 257 { 258 pos1 = new Vector3(pos1.x, ry, pos2.z - 1); 259 } 260 261 var cp = new Vector3(pos2.x, ry, pos1.z); 262 LShapePos(pos1, pos2, cp); 263 } 264 } 265 else 266 { 267 //to在from的左下 268 var fe = Random.value < .5f ? rangex.y : rangez.y; 269 if (fe == rangex.y) 270 { 271 pos1 = new Vector3(fe - 1, ry, rangez.y); 272 pos2 = new Vector3(rangex.x, ry, rangez.x + 1); 273 274 if (pos1.x <= pos2.x) 275 { 276 pos2 = new Vector3(pos1.x - 1, ry, pos2.z); 277 } 278 if (pos1.z <= pos2.z) 279 { 280 pos1 = new Vector3(pos1.x, ry, pos2.z + 1); 281 } 282 283 var cp = new Vector3(pos2.x, ry, pos1.z); 284 LShapePos(pos1, pos2, cp); 285 } 286 else 287 { 288 pos1 = new Vector3(rangex.y, ry, fe - 1); 289 pos2 = new Vector3(rangex.x + 1, ry, rangez.x); 290 291 if (pos1.x <= pos2.x) 292 { 293 pos1 = new Vector3(pos2.x + 1, ry, pos1.z); 294 } 295 if (pos1.z <= pos2.z) 296 { 297 pos2 = new Vector3(pos2.x, ry, pos1.z - 1); 298 } 299 300 var cp = new Vector3(pos1.x, ry, pos2.z); 301 LShapePos(pos1, pos2, cp); 302 } 303 } 304 } 305 } 306 else 307 { 308 var rz = EdgeRandom(rangez.y, rangez.x); 309 var rx1 = rangex.x + 1; 310 var rx2 = rangex.y - 1; 311 312 pos1 = new Vector3(rx1, ry, rz); 313 pos2 = new Vector3(rx2, ry, rz); 314 315 LineTwoPos(pos1, pos2); 316 } 317 } 318 else 319 { 320 var rx = EdgeRandom(rangex.y, rangex.x); 321 var rz1 = rangez.x + 1; 322 var rz2 = rangez.y - 1; 323 324 pos1 = new Vector3(rx, ry, rz1); 325 pos2 = new Vector3(rx, ry, rz2); 326 327 LineTwoPos(pos1, pos2); 328 } 329 return PosOfRoom(pos1, pos2, or); 330 } 331 332 Vector3 PosOfRoom(Vector3 pos1, Vector3 pos2, RoomTran room) 333 { 334 var lmax = room.CenterPos.x + (room.Length - 1) * .5f; 335 var lmin = room.CenterPos.x - (room.Length - 1) * .5f; 336 var wmax = room.CenterPos.y + (room.Width - 1) * .5f; 337 var wmin = room.CenterPos.y - (room.Width - 1) * .5f; 338 339 if (pos1.x >= lmin && pos1.x <= lmax && pos1.z >= wmin && pos1.z <= wmax) 340 return pos1; 341 else 342 return pos2; 343 } 344 345 Vector2 CheckCommonSide(float axis1, int edge1, float axis2, int edge2) 346 { 347 var max1 = axis1 + (edge1 - 1) * .5f - 1; 348 var min1 = axis1 - (edge1 - 1) * .5f + 1; 349 var max2 = axis2 + (edge2 - 1) * .5f - 1; 350 var min2 = axis2 - (edge2 - 1) * .5f + 1; 351 352 return new Vector2(max1 > max2 ? max2 : max1, min1 > min2 ? min1 : min2); 353 } 354 355 float EdgeRandom(float min, float max) 356 { 357 float cut = .5f; 358 var diff = max - min; 359 if (diff % 1 == 0) 360 cut = 1f; 361 int c = (int)(diff / cut); 362 363 return Random.Range(0, c + 1) * cut + min; 364 } 365 366 void LineTwoPos(Vector3 pos1, Vector3 pos2) 367 { 368 if (pos1.y == pos2.y) 369 { 370 if (pos1.x == pos2.x) 371 { 372 var min = pos1.z < pos2.z ? pos1 : pos2; 373 var max = min.z == pos1.z ? pos2 : pos1; 374 375 var diff = max.z - min.z; 376 377 if (diff % 1 == 0) 378 { 379 for (float i = min.z; i <= max.z; i++) 380 { 381 var pos = new Vector3(min.x, min.y, i); 382 383 //绕路判定 384 if (Mathf.Abs(pos.z - max.z) > 2) 385 { 386 if (RayCast(pos, Dz, 1)) 387 { 388 var posex = new Vector3(pos.x, pos.y, pos.z + 1); 389 var cps = MapSystem.GetExCross(posex, max); 390 switch (ObstacleCrossType) 391 { 392 case ObstacleCrossType.Break: 393 CrossBreak(pos, max, cps); 394 InsSetPos(posex); 395 break; 396 case ObstacleCrossType.Detour: 397 CrossAround(pos, max, cps); 398 break; 399 } 400 return; 401 } 402 } 403 InsSetPos(pos); 404 } 405 } 406 else 407 { 408 for (float i = min.z; i < max.z; i++) 409 { 410 var pos = new Vector3(pos1.x, pos1.y, i); 411 412 //绕路判定 413 if (Mathf.Abs(pos.z - max.z) > 2) 414 { 415 if (RayCast(pos, Dz, 1)) 416 { 417 var posex = new Vector3(pos.x, pos.y, pos.z + 1); 418 var cps = MapSystem.GetExCross(posex, max); 419 switch (ObstacleCrossType) 420 { 421 case ObstacleCrossType.Break: 422 CrossBreak(pos, max, cps); 423 InsSetPos(posex); 424 break; 425 case ObstacleCrossType.Detour: 426 CrossAround(pos, max, cps); 427 break; 428 } 429 return; 430 } 431 } 432 InsSetPos(pos); 433 } 434 InsSetPos(max); 435 } 436 } 437 else if (pos1.z == pos2.z) 438 { 439 var min = pos1.x < pos2.x ? pos1 : pos2; 440 var max = min.x == pos1.x ? pos2 : pos1; 441 442 var diff = max.x - min.x; 443 444 if (diff % 1 == 0) 445 { 446 for (float i = min.x; i <= max.x; i++) 447 { 448 var pos = new Vector3(i, min.y, min.z); 449 //绕路判定 450 if (Mathf.Abs(pos.x - max.x) > 2) 451 { 452 if (RayCast(pos, Dx, 1)) 453 { 454 var posex = new Vector3(pos.x + 1, pos.y, pos.z); 455 var cps = MapSystem.GetExCross(posex, max); 456 switch (ObstacleCrossType) 457 { 458 case ObstacleCrossType.Break: 459 CrossBreak(pos, max, cps); 460 InsSetPos(posex); 461 break; 462 case ObstacleCrossType.Detour: 463 CrossAround(pos, max, cps); 464 break; 465 } 466 return; 467 } 468 } 469 InsSetPos(pos); 470 } 471 } 472 else 473 { 474 for (float i = min.x; i < max.x; i++) 475 { 476 var pos = new Vector3(i, pos1.y, pos1.z); 477 //绕路判定 478 if (Mathf.Abs(pos.x - max.x) > 2) 479 { 480 if (RayCast(pos, Dx, 1)) 481 { 482 var posex = new Vector3(pos.x + 1, pos.y, pos.z); 483 var cps = MapSystem.GetExCross(posex, max); 484 switch (ObstacleCrossType) 485 { 486 case ObstacleCrossType.Break: 487 CrossBreak(pos, max, cps); 488 InsSetPos(posex); 489 break; 490 case ObstacleCrossType.Detour: 491 CrossAround(pos, max, cps); 492 break; 493 } 494 return; 495 } 496 } 497 InsSetPos(pos); 498 } 499 InsSetPos(max); 500 } 501 } 502 } 503 } 504 505 void LShapePos(Vector3 pos1, Vector3 pos2, Vector3 cp) 506 { 507 //判定折线中间点是否位于其他房间内,若位于,则需要重新寻路 508 var rt = MapSystem.GetRoomByPos(cp); 509 if (ObstacleCrossType == ObstacleCrossType.Detour && rt != null) 510 { 511 var to = new Vector2(rt.Length + 1, rt.Width + 1) * .5f; 512 513 var ned = rt.CenterPos - to; 514 var fod = rt.CenterPos + to; 515 516 Vector3[] v4 = new Vector3[4]; 517 v4[0] = new Vector3(ned.x, cp.y, ned.y); 518 v4[1] = new Vector3(ned.x, cp.y, fod.y); 519 v4[2] = new Vector3(fod.x, cp.y, ned.y); 520 v4[3] = new Vector3(fod.x, cp.y, fod.y); 521 522 var minx = pos1.x < pos2.x ? pos1.x : pos2.x; 523 var maxx = minx == pos1.x ? pos2.x : pos1.x; 524 var minz = pos1.z < pos2.z ? pos1.z : pos2.z; 525 var maxz = minz == pos1.z ? pos2.z : pos1.z; 526 527 for (int i = 0; i < v4.Length; i++) 528 { 529 if (v4[i].x > minx && v4[i].x < maxx && v4[i].z > minz && v4[i].z < maxz) 530 { 531 var ncp1 = new Vector3(cp.x, cp.y, v4[i].z); 532 var ncp2 = new Vector3(v4[i].x, cp.y, cp.z); 533 534 var pos1cp = ncp1.x == pos1.x || ncp1.z == pos1.z ? ncp1 : ncp2; 535 var pos2cp = pos1cp == ncp1 ? ncp2 : ncp1; 536 537 LShapePos(pos1, v4[i], pos1cp); 538 LShapePos(v4[i], pos2, pos2cp); 539 return; 540 } 541 } 542 } 543 544 LineTwoPos(pos1, cp); 545 LineTwoPos(pos2, cp); 546 } 547 548 void CrossAround(Vector3 pos, Vector3 max, ExCross cps) 549 { 550 if (cps.pos1 != Vector3.zero && cps.pos2 != Vector3.zero && cps.pos3 != Vector3.zero) 551 { 552 LineTwoPos(pos, cps.pos1); 553 LineTwoPos(cps.pos1, cps.pos2); 554 LineTwoPos(cps.pos2, cps.pos3); 555 LineTwoPos(cps.pos3, max); 556 } 557 } 558 559 void CrossBreak(Vector3 pos, Vector3 max, ExCross cps) 560 { 561 if (cps.pos1 != Vector3.zero && cps.pos2 != Vector3.zero && cps.pos3 != Vector3.zero) 562 { 563 InsSetPos(pos); 564 LineTwoPos(cps.pos3, max); 565 } 566 } 567 568 bool RayCast(Vector3 ori, Vector3 dir, float mD) 569 { 570 return MapSystem.RayCast(ori, dir, mD); 571 } 572 573 void InsSetPos(Vector3 pos, Transform parent = null) 574 { 575 var temp = MapSystem.MapData.CrossUnitPos; 576 if (!temp.Contains(pos)) 577 { 578 temp.Add(pos); 579 MapSystem.CrossUnitInsts.Add(MapSystem.InsSetPos(CrossUnit, pos, parent)); 580 MapSystem.MapData.CrossUnitPos = temp; 581 } 582 } 583 }
最终效果展示:(单层,绕过障碍)
与上面同一个随机种子(断开):
关于随机种子的介绍和用法可以详见之前写的另一篇博客:
https://www.cnblogs.com/koshio0219/p/12514825.html
下面给出打包出的unity package工具包以供参考: