Unity 遍歷敵人——使用四叉樹空間分區
阿新 • • 發佈:2017-05-20
所在 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>RectExtension14 /// <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 }
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 遍歷敵人——使用四叉樹空間分區