1. 程式人生 > >Unity 遍歷敵人——使用四叉樹空間分區

Unity 遍歷敵人——使用四叉樹空間分區

所在 for 黑點 對象 如果 init using rect blog

最近看了《遊戲編程模式》這本書,裏面有一篇空間分區的文章,看了心裏癢癢,決定去嘗試實現一下。文章後面會給出整個學習參考的鏈接。

實現的效果如下,我們有一個很大的場景,場景有許許多多的敵人。紅色的點代表是玩家,黑色的點代表是敵人。在這樣的一個大量敵人的情景下,我們不可能在玩家或敵人尋找身邊的攻擊對象時窮盡所有的對象。因為我們要建立空間分區,只遍歷某個對應區的對象。在圖下中,紅點中遍歷紅框中的黑點對象,其他一律不遍歷。

技術分享

接下來我直接放代碼了,主要采用了四叉樹,如果對於一些不懂的地方斷點調試下就可以了,並沒有涉及到很高深的算法,不要想得太難。

其中也實現了紅點在分區邊緣遍歷另外一個區的敵人的漏洞。

技術分享
 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 
 5 /// <summary>
 6 /// Rect的擴展方法類
 7 /// </summary>
 8 public static class RectExtension
 9 {
10     /// <summary>
11     /// 計算點到Rect的border的距離,若點在Rect內則返回0
12     /// </summary>
13     /// <param name="rect"></param>
14 /// <param name="pos"></param> 15 /// <returns></returns> 16 public static float PointToBorderDistance(this Rect rect, Vector2 pos) 17 { 18 float xdisance; 19 float ydisance; 20 21 if (rect.x <= pos.x && pos.x <= rect.xMax) 22
{ 23 xdisance = 0; 24 } 25 else 26 { 27 xdisance = Mathf.Min((Mathf.Abs(pos.x - rect.width)), (pos.x - rect.x)); 28 } 29 30 if (rect.y <= pos.y && pos.y <= rect.yMax) 31 { 32 ydisance = 0; 33 } 34 else 35 { 36 ydisance = Mathf.Min((Mathf.Abs(pos.y - rect.height)), (pos.y - rect.y)); 37 } 38 39 return xdisance * xdisance + ydisance * ydisance; 40 } 41 }
RectExtension 技術分享
 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 
 5 /// <summary>
 6 /// 葉子節點類,負責坐標與數據的映射
 7 /// </summary>
 8 /// <typeparam name="T"></typeparam>
 9 public class QuadTreeLeaf<T>
10 {
11     private Vector2 pos;
12     private T refObject;
13 
14     public QuadTreeLeaf(Vector2 pos,T obj)
15     {
16         this.pos = pos;
17         refObject = obj;
18     }
19 
20     public T LeafObject
21     {
22         get
23         {
24             return refObject;
25         }
26     }
27 
28     public Vector2 Pos
29     {
30         get { return pos; }
31         set { pos = value; }
32     }
33 }
QuadTreeLeaf 技術分享
  1 using System;
  2 using System.Collections;
  3 using System.Collections.Generic;
  4 using UnityEngine;
  5 
  6 /// <summary>
  7 /// 四叉樹節點
  8 /// </summary>
  9 /// <typeparam name="T"></typeparam>
 10 public class QuadTreeNode<T>
 11 {
 12     /// <summary>
 13     /// 節點擁有的葉子節點
 14     /// </summary>
 15     protected List<QuadTreeLeaf<T>> items;
 16     /// <summary>
 17     /// 節點擁有的分支
 18     /// </summary>
 19     protected QuadTreeNode<T>[] branch;
 20     /// <summary>
 21     /// 節點空間最大容量,受minSize影響
 22     /// </summary>
 23     protected int maxItems;
 24     /// <summary>
 25     /// 節點空間分割的最小大小(最小寬度,高度)
 26     /// </summary>
 27     protected float minSize;
 28 
 29     public const float TOLERANCE = 0.001f;
 30     /// <summary>
 31     /// 節點的空間
 32     /// </summary>
 33     public Rect bounds;
 34 
 35     public QuadTreeNode(float x, float y, float width, float height, int maximumItems, float minSize = -1)
 36     {
 37         bounds = new Rect(x, y, width, height);
 38         maxItems = maximumItems;
 39         this.minSize = minSize;
 40         items = new List<QuadTreeLeaf<T>>();
 41     }
 42 
 43     public bool HasChildren()
 44     {
 45         if (branch != null)
 46             return true;
 47         else
 48             return false;
 49     }
 50 
 51     /// <summary>
 52     /// 將節點空間分割4份
 53     /// </summary>
 54     protected void Split()
 55     {
 56         if (minSize != -1)
 57         {
 58             if (bounds.width <= minSize && bounds.height <= minSize)
 59             {
 60                 return;
 61             }
 62         }
 63 
 64         float nsHalf = bounds.height - bounds.height / 2;
 65         float ewHalf = bounds.width - bounds.width / 2;
 66 
 67         branch = new QuadTreeNode<T>[4];
 68 
 69         branch[0] = new QuadTreeNode<T>(bounds.x, bounds.y, ewHalf, nsHalf, maxItems, minSize);
 70         branch[1] = new QuadTreeNode<T>(ewHalf, bounds.y, ewHalf, nsHalf, maxItems, minSize);
 71         branch[2] = new QuadTreeNode<T>(bounds.x, nsHalf, ewHalf, nsHalf, maxItems, minSize);
 72         branch[3] = new QuadTreeNode<T>(ewHalf, nsHalf, ewHalf, nsHalf, maxItems, minSize);
 73 
 74         foreach (var item in items)
 75         {
 76             AddNode(item);
 77         }
 78 
 79         items.Clear();
 80     }
 81 
 82     /// <summary>
 83     /// 根據坐標獲得相應的子空間
 84     /// </summary>
 85     /// <param name="pos"></param>
 86     /// <returns></returns>
 87     protected QuadTreeNode<T> GetChild(Vector2 pos)
 88     {
 89         if (bounds.Contains(pos))
 90         {
 91             if (branch != null)
 92             {
 93                 for (int i = 0; i < branch.Length; i++)
 94                     if (branch[i].bounds.Contains(pos))
 95                         return branch[i].GetChild(pos);
 96 
 97             }
 98             else
 99                 return this;
100         }
101         return null;
102     }
103     /// <summary>
104     /// 增加葉子節點數據
105     /// </summary>
106     /// <param name="leaf"></param>
107     /// <returns></returns>
108     private bool AddNode(QuadTreeLeaf<T> leaf)
109     {
110         if (branch == null)
111         {
112             this.items.Add(leaf);
113 
114             if (this.items.Count > maxItems)
115                 Split();
116             return true;
117         }
118         else
119         {
120             QuadTreeNode<T> node = GetChild(leaf.Pos);
121             if (node != null)
122             {
123                 return node.AddNode(leaf);
124             }
125         }
126         return false;
127     }
128 
129     public bool AddNode(Vector2 pos, T obj)
130     {
131         return AddNode(new QuadTreeLeaf<T>(pos, obj));
132     }
133 
134     /// <summary>
135     /// 
136     /// </summary>
137     /// <param name="pos">可以是空間任意位置,只是根據這個位置找到所在的空間去刪除對象</param>
138     /// <param name="obj"></param>
139     /// <returns></returns>
140     public bool RemoveNode(Vector2 pos, T obj)
141     {
142         if (branch == null)
143         {
144             for (int i = 0; i < items.Count; i++)
145             {
146                 QuadTreeLeaf<T> qtl = items[i];
147                 if (qtl.LeafObject.Equals(obj))
148                 {
149                     items.RemoveAt(i);
150                     return true;
151                 }
152             }
153         }
154         else
155         {
156             QuadTreeNode<T> node = GetChild(pos);
157             if (node != null)
158             {
159                 return node.RemoveNode(pos, obj);
160             }
161         }
162         return false;
163     }
164 
165     public bool UpdateNode(Vector2 pos, T obj)
166     {
167         // TODO  參找RemoveNode
168         return false;
169     }
170 
171     /// <summary>
172     /// 得到在一個Rect內的所有數據
173     /// </summary>
174     /// <param name="rect"></param>
175     /// <param name="nodes"></param>
176     /// <returns></returns>
177     public int GetNode(Rect rect, ref List<T> nodes)
178     {
179         if (branch == null)
180         {
181             foreach (QuadTreeLeaf<T> item in items)
182             {
183                 if (rect.Contains(item.Pos))
184                 {
185                     nodes.Add(item.LeafObject);
186                 }
187             }
188         }
189         else
190         {
191             for (int i = 0; i < branch.Length; i++)
192             {
193                 if (branch[i].bounds.Overlaps(rect))
194                     branch[i].GetNode(rect, ref nodes);
195             }
196         }
197         return nodes.Count;
198     }
199 
200     /// <summary>
201     /// 根據坐標得到坐標附近節點的數據
202     /// </summary>
203     /// <param name="pos"></param>
204     /// <param name="ShortestDistance">離坐標最短距離</param>
205     /// <param name="list"></param>
206     /// <returns></returns>
207     public int GetNodeRecRange(Vector2 pos, float ShortestDistance, ref List<T> list)
208     {
209         float distance;
210         if (branch == null)
211         {
212             foreach (QuadTreeLeaf<T> leaf in this.items)
213             {
214                 distance = Vector2.Distance(pos,leaf.Pos);
215 
216                 if (distance < ShortestDistance)
217                 {
218                     list.Add(leaf.LeafObject);
219                 }
220             }
221         }
222         else
223         {
224             for (int i = 0; i < branch.Length; i++)
225             {
226                 float childDistance = branch[i].bounds.PointToBorderDistance(pos);
227                 if (childDistance < ShortestDistance * ShortestDistance)
228                 {
229                     branch[i].GetNodeRecRange(pos, ShortestDistance, ref list);
230                 }
231             }
232         }
233         return list.Count;
234     }
235 }
QuadTreeNode

測試代碼:

掛在紅點和黑點上。

技術分享
 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 
 5 public class TestController : MonoBehaviour
 6 {
 7     public bool IsPlayer = false;
 8 
 9     // Use this for initialization
10     void Start()
11     {
12         TestObj.quadRoot.AddNode(new Vector2(transform.position.x, transform.position.z), this.gameObject);
13     }
14 
15     // Update is called once per frame
16     void Update()
17     {
18         if (IsPlayer && Input.GetKeyDown(KeyCode.S))
19         {
20             List<GameObject> list = new List<GameObject>();
21             TestObj.quadRoot.GetNodeRecRange(new Vector2(transform.position.x, transform.position.z), 5f, ref list);
22             foreach (var item in list)
23             {
24                 Debug.Log(item.name);
25             }
26         }
27     }
28 }
TestController 技術分享
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4 
5 public class TestObj : MonoBehaviour
6 {
7     public static QuadTreeNode<GameObject> quadRoot = new QuadTreeNode<GameObject>(0, 0, 100, 100, 10, 50);
8 }
TestObj

TestObj隨便掛在一個物體上就可以了。

然後運行調試,你就能理解了。

《遊戲編程模式》空間分區章節:http://gpp.tkchu.me/spatial-partition.html

四叉樹代碼參考:http://www.oxox.work/web/recommend/quadtree-c-c/

Unity 遍歷敵人——使用四叉樹空間分區