以前的博客中已經說了隨機房間生成:html
http://www.javashuo.com/article/p-tjoeikus-bh.html算法
但實現房間生成只是整個地圖生成最初最簡單的一步。下面討論如何隨機生成鏈接房間的通道。數據結構
房間的通道通常要知足如下特性和特徵:dom
1.保證全部隨機生成的房間均可以經過通道從起點到達終點,不出現未鏈接或鏈接中斷的狀況。ide
2.通道在生成的過程當中不能穿過房間內部。函數
3.考慮是簡潔直接的鏈接方式,仍是更爲曲折的通道。工具
好了,如今眼前出現了不少隨機生成的房間,那麼問題是:到底哪兩個房間須要鏈接一條通道呢,若是以最快的速度得出全部須要鏈接的通道列表呢?ui
這時,咱們先不用將空間中生成的這些房間僅僅當前是房間來看待,實質上它們就是分佈在空間中的一些點的集合,每個點都能求出當前離它們最近的點是哪個,spa
那麼一個很是簡單的算法是,咱們能夠記錄兩個列表:已經處於鏈接狀態的點的列表(閉合列表),還沒有取得鏈接的點的列表(開放列表)。code
先隨機一個點做爲起點,鏈接離該起點最近的點,將起點和鏈接點置入閉合列表中,再以第二個點做爲起點鏈接其最近的點,以此方式不斷循環,直至全部的開放列表清空,全部通道的鏈接數據即所有計算完畢。
上面這樣雖然能夠很快得出全部鏈接的通道列表,但缺少鏈接的隨機性,也沒法產生分支路徑,遊戲性很低。
所以,能夠考慮給每一個鏈接的點增長額外分支的機率,這樣每一個點就至少須要計算與它最近的兩個點的位置,在機率的控制下其中一個做爲主要路徑鏈表中的值,另外一個不鏈接或鏈接爲分支置入閉合列表中。
生成以後的房間數據結構最終會是一個二叉樹。
主要屬性:
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 }
最終效果展現:(單層,繞過障礙)
與上面同一個隨機種子(斷開):
關於隨機種子的介紹和用法能夠詳見以前寫的另外一篇博客:
http://www.javashuo.com/article/p-fhqvfbbf-bq.html
下面給出打包出的unity package工具包以供參考:
https://files.cnblogs.com/files/koshio0219/RMTool.zip