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 }
View Code

 

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

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

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 }
View Code

 

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

 

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

 

 

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

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

 

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

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

posted @ 2020-04-20 19:40  汐夜  阅读(3029)  评论(1编辑  收藏  举报