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 }
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 }
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 }
测试代码:
挂在红点和黑点上。
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 }
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随便挂在一个物体上就可以了。
《游戏编程模式》空间分区章节:http://gpp.tkchu.me/spatial-partition.html
四叉树代码参考:http://www.oxox.work/web/recommend/quadtree-c-c/
以上是关于Unity 遍历敌人——使用四叉树空间分区的主要内容,如果未能解决你的问题,请参考以下文章