Unity 遍历敌人——使用四叉树空间分区

Posted 爱裸奔的小亮亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity 遍历敌人——使用四叉树空间分区相关的知识,希望对你有一定的参考价值。

最近看了《游戏编程模式》这本书,里面有一篇空间分区的文章,看了心里痒痒,决定去尝试实现一下。文章后面会给出整个学习参考的链接。

实现的效果如下,我们有一个很大的场景,场景有许许多多的敌人。红色的点代表是玩家,黑色的点代表是敌人。在这样的一个大量敌人的情景下,我们不可能在玩家或敌人寻找身边的攻击对象时穷尽所有的对象。因为我们要建立空间分区,只遍历某个对应区的对象。在图下中,红点中遍历红框中的黑点对象,其他一律不遍历。

接下来我直接放代码了,主要采用了四叉树,如果对于一些不懂的地方断点调试下就可以了,并没有涉及到很高深的算法,不要想得太难。

其中也实现了红点在分区边缘遍历另外一个区的敌人的漏洞。

 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 遍历敌人——使用四叉树空间分区的主要内容,如果未能解决你的问题,请参考以下文章

[译]2D空间中使用四叉树Quadtree进行碰撞检测优化

在Unity中使用四叉树算法绘制地形

四叉树空间索引原理及其实现

四叉树空间索引原理及其实现

2D空间中使用Quadtree四叉树进行碰撞检测优化

空间划分的数据结构(四叉树/八叉树/BVH树/BSP树/k-d树)