CullArea.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="CullArea.cs" company="Exit Games GmbH">
  3. // Part of: Photon Unity Utilities,
  4. // </copyright>
  5. // <summary>
  6. // Represents the cull area used for network culling.
  7. // </summary>
  8. // <author>developer@exitgames.com</author>
  9. // --------------------------------------------------------------------------------------------------------------------
  10. using System.Collections.Generic;
  11. using UnityEngine;
  12. namespace Photon.Pun.UtilityScripts
  13. {
  14. using System;
  15. /// <summary>
  16. /// Represents the cull area used for network culling.
  17. /// </summary>
  18. public class CullArea : MonoBehaviour
  19. {
  20. private const int MAX_NUMBER_OF_ALLOWED_CELLS = 250;
  21. public const int MAX_NUMBER_OF_SUBDIVISIONS = 3;
  22. /// <summary>
  23. /// This represents the first ID which is assigned to the first created cell.
  24. /// If you already have some interest groups blocking this first ID, fell free to change it.
  25. /// However increasing the first group ID decreases the maximum amount of allowed cells.
  26. /// Allowed values are in range from 1 to 250.
  27. /// </summary>
  28. public readonly byte FIRST_GROUP_ID = 1;
  29. /// <summary>
  30. /// This represents the order in which updates are sent.
  31. /// The number represents the subdivision of the cell hierarchy:
  32. /// - 0: message is sent to all players
  33. /// - 1: message is sent to players who are interested in the matching cell of the first subdivision
  34. /// If there is only one subdivision we are sending one update to all players
  35. /// before sending three consequent updates only to players who are in the same cell
  36. /// or interested in updates of the current cell.
  37. /// </summary>
  38. public readonly int[] SUBDIVISION_FIRST_LEVEL_ORDER = new int[4] { 0, 1, 1, 1 };
  39. /// <summary>
  40. /// This represents the order in which updates are sent.
  41. /// The number represents the subdivision of the cell hierarchy:
  42. /// - 0: message is sent to all players
  43. /// - 1: message is sent to players who are interested in the matching cell of the first subdivision
  44. /// - 2: message is sent to players who are interested in the matching cell of the second subdivision
  45. /// If there are two subdivisions we are sending every second update only to players
  46. /// who are in the same cell or interested in updates of the current cell.
  47. /// </summary>
  48. public readonly int[] SUBDIVISION_SECOND_LEVEL_ORDER = new int[8] { 0, 2, 1, 2, 0, 2, 1, 2 };
  49. /// <summary>
  50. /// This represents the order in which updates are sent.
  51. /// The number represents the subdivision of the cell hierarchy:
  52. /// - 0: message is sent to all players
  53. /// - 1: message is sent to players who are interested in the matching cell of the first subdivision
  54. /// - 2: message is sent to players who are interested in the matching cell of the second subdivision
  55. /// - 3: message is sent to players who are interested in the matching cell of the third subdivision
  56. /// If there are two subdivisions we are sending every second update only to players
  57. /// who are in the same cell or interested in updates of the current cell.
  58. /// </summary>
  59. public readonly int[] SUBDIVISION_THIRD_LEVEL_ORDER = new int[12] { 0, 3, 2, 3, 1, 3, 2, 3, 1, 3, 2, 3 };
  60. public Vector2 Center;
  61. public Vector2 Size = new Vector2(25.0f, 25.0f);
  62. public Vector2[] Subdivisions = new Vector2[MAX_NUMBER_OF_SUBDIVISIONS];
  63. public int NumberOfSubdivisions;
  64. public int CellCount { get; private set; }
  65. public CellTree CellTree { get; private set; }
  66. public Dictionary<int, GameObject> Map { get; private set; }
  67. public bool YIsUpAxis = false;
  68. public bool RecreateCellHierarchy = false;
  69. private byte idCounter;
  70. /// <summary>
  71. /// Creates the cell hierarchy at runtime.
  72. /// </summary>
  73. private void Awake()
  74. {
  75. this.idCounter = this.FIRST_GROUP_ID;
  76. this.CreateCellHierarchy();
  77. }
  78. /// <summary>
  79. /// Creates the cell hierarchy in editor and draws the cell view.
  80. /// </summary>
  81. public void OnDrawGizmos()
  82. {
  83. this.idCounter = this.FIRST_GROUP_ID;
  84. if (this.RecreateCellHierarchy)
  85. {
  86. this.CreateCellHierarchy();
  87. }
  88. this.DrawCells();
  89. }
  90. /// <summary>
  91. /// Creates the cell hierarchy.
  92. /// </summary>
  93. private void CreateCellHierarchy()
  94. {
  95. if (!this.IsCellCountAllowed())
  96. {
  97. if (Debug.isDebugBuild)
  98. {
  99. Debug.LogError("There are too many cells created by your subdivision options. Maximum allowed number of cells is " + (MAX_NUMBER_OF_ALLOWED_CELLS - this.FIRST_GROUP_ID) +
  100. ". Current number of cells is " + this.CellCount + ".");
  101. return;
  102. }
  103. else
  104. {
  105. Application.Quit();
  106. }
  107. }
  108. CellTreeNode rootNode = new CellTreeNode(this.idCounter++, CellTreeNode.ENodeType.Root, null);
  109. if (this.YIsUpAxis)
  110. {
  111. this.Center = new Vector2(transform.position.x, transform.position.y);
  112. this.Size = new Vector2(transform.localScale.x, transform.localScale.y);
  113. rootNode.Center = new Vector3(this.Center.x, this.Center.y, 0.0f);
  114. rootNode.Size = new Vector3(this.Size.x, this.Size.y, 0.0f);
  115. rootNode.TopLeft = new Vector3((this.Center.x - (this.Size.x / 2.0f)), (this.Center.y - (this.Size.y / 2.0f)), 0.0f);
  116. rootNode.BottomRight = new Vector3((this.Center.x + (this.Size.x / 2.0f)), (this.Center.y + (this.Size.y / 2.0f)), 0.0f);
  117. }
  118. else
  119. {
  120. this.Center = new Vector2(transform.position.x, transform.position.z);
  121. this.Size = new Vector2(transform.localScale.x, transform.localScale.z);
  122. rootNode.Center = new Vector3(this.Center.x, 0.0f, this.Center.y);
  123. rootNode.Size = new Vector3(this.Size.x, 0.0f, this.Size.y);
  124. rootNode.TopLeft = new Vector3((this.Center.x - (this.Size.x / 2.0f)), 0.0f, (this.Center.y - (this.Size.y / 2.0f)));
  125. rootNode.BottomRight = new Vector3((this.Center.x + (this.Size.x / 2.0f)), 0.0f, (this.Center.y + (this.Size.y / 2.0f)));
  126. }
  127. this.CreateChildCells(rootNode, 1);
  128. this.CellTree = new CellTree(rootNode);
  129. this.RecreateCellHierarchy = false;
  130. }
  131. /// <summary>
  132. /// Creates all child cells.
  133. /// </summary>
  134. /// <param name="parent">The current parent node.</param>
  135. /// <param name="cellLevelInHierarchy">The cell level within the current hierarchy.</param>
  136. private void CreateChildCells(CellTreeNode parent, int cellLevelInHierarchy)
  137. {
  138. if (cellLevelInHierarchy > this.NumberOfSubdivisions)
  139. {
  140. return;
  141. }
  142. int rowCount = (int)this.Subdivisions[(cellLevelInHierarchy - 1)].x;
  143. int columnCount = (int)this.Subdivisions[(cellLevelInHierarchy - 1)].y;
  144. float startX = parent.Center.x - (parent.Size.x / 2.0f);
  145. float width = parent.Size.x / rowCount;
  146. for (int row = 0; row < rowCount; ++row)
  147. {
  148. for (int column = 0; column < columnCount; ++column)
  149. {
  150. float xPos = startX + (row * width) + (width / 2.0f);
  151. CellTreeNode node = new CellTreeNode(this.idCounter++, (this.NumberOfSubdivisions == cellLevelInHierarchy) ? CellTreeNode.ENodeType.Leaf : CellTreeNode.ENodeType.Node, parent);
  152. if (this.YIsUpAxis)
  153. {
  154. float startY = parent.Center.y - (parent.Size.y / 2.0f);
  155. float height = parent.Size.y / columnCount;
  156. float yPos = startY + (column * height) + (height / 2.0f);
  157. node.Center = new Vector3(xPos, yPos, 0.0f);
  158. node.Size = new Vector3(width, height, 0.0f);
  159. node.TopLeft = new Vector3(xPos - (width / 2.0f), yPos - (height / 2.0f), 0.0f);
  160. node.BottomRight = new Vector3(xPos + (width / 2.0f), yPos + (height / 2.0f), 0.0f);
  161. }
  162. else
  163. {
  164. float startZ = parent.Center.z - (parent.Size.z / 2.0f);
  165. float depth = parent.Size.z / columnCount;
  166. float zPos = startZ + (column * depth) + (depth / 2.0f);
  167. node.Center = new Vector3(xPos, 0.0f, zPos);
  168. node.Size = new Vector3(width, 0.0f, depth);
  169. node.TopLeft = new Vector3(xPos - (width / 2.0f), 0.0f, zPos - (depth / 2.0f));
  170. node.BottomRight = new Vector3(xPos + (width / 2.0f), 0.0f, zPos + (depth / 2.0f));
  171. }
  172. parent.AddChild(node);
  173. this.CreateChildCells(node, (cellLevelInHierarchy + 1));
  174. }
  175. }
  176. }
  177. /// <summary>
  178. /// Draws the cells.
  179. /// </summary>
  180. private void DrawCells()
  181. {
  182. if ((this.CellTree != null) && (this.CellTree.RootNode != null))
  183. {
  184. this.CellTree.RootNode.Draw();
  185. }
  186. else
  187. {
  188. this.RecreateCellHierarchy = true;
  189. }
  190. }
  191. /// <summary>
  192. /// Checks if the cell count is allowed.
  193. /// </summary>
  194. /// <returns>True if the cell count is allowed, false if the cell count is too large.</returns>
  195. private bool IsCellCountAllowed()
  196. {
  197. int horizontalCells = 1;
  198. int verticalCells = 1;
  199. foreach (Vector2 v in this.Subdivisions)
  200. {
  201. horizontalCells *= (int)v.x;
  202. verticalCells *= (int)v.y;
  203. }
  204. this.CellCount = horizontalCells * verticalCells;
  205. return (this.CellCount <= (MAX_NUMBER_OF_ALLOWED_CELLS - this.FIRST_GROUP_ID));
  206. }
  207. /// <summary>
  208. /// Gets a list of all cell IDs the player is currently inside or nearby.
  209. /// </summary>
  210. /// <param name="position">The current position of the player.</param>
  211. /// <returns>A list containing all cell IDs the player is currently inside or nearby.</returns>
  212. public List<byte> GetActiveCells(Vector3 position)
  213. {
  214. List<byte> activeCells = new List<byte>(0);
  215. this.CellTree.RootNode.GetActiveCells(activeCells, this.YIsUpAxis, position);
  216. // it makes sense to sort the "nearby" cells. those are in the list in positions after the subdivisions the point is inside. 2 subdivisions result in 3 areas the point is in.
  217. int cellsActive = this.NumberOfSubdivisions + 1;
  218. int cellsNearby = activeCells.Count - cellsActive;
  219. if (cellsNearby > 0)
  220. {
  221. activeCells.Sort(cellsActive, cellsNearby, new ByteComparer());
  222. }
  223. return activeCells;
  224. }
  225. }
  226. /// <summary>
  227. /// Represents the tree accessible from its root node.
  228. /// </summary>
  229. public class CellTree
  230. {
  231. /// <summary>
  232. /// Represents the root node of the cell tree.
  233. /// </summary>
  234. public CellTreeNode RootNode { get; private set; }
  235. /// <summary>
  236. /// Default constructor.
  237. /// </summary>
  238. public CellTree()
  239. {
  240. }
  241. /// <summary>
  242. /// Constructor to define the root node.
  243. /// </summary>
  244. /// <param name="root">The root node of the tree.</param>
  245. public CellTree(CellTreeNode root)
  246. {
  247. this.RootNode = root;
  248. }
  249. }
  250. /// <summary>
  251. /// Represents a single node of the tree.
  252. /// </summary>
  253. public class CellTreeNode
  254. {
  255. public enum ENodeType : byte
  256. {
  257. Root = 0,
  258. Node = 1,
  259. Leaf = 2
  260. }
  261. /// <summary>
  262. /// Represents the unique ID of the cell.
  263. /// </summary>
  264. public byte Id;
  265. /// <summary>
  266. /// Represents the center, top-left or bottom-right position of the cell
  267. /// or the size of the cell.
  268. /// </summary>
  269. public Vector3 Center, Size, TopLeft, BottomRight;
  270. /// <summary>
  271. /// Describes the current node type of the cell tree node.
  272. /// </summary>
  273. public ENodeType NodeType;
  274. /// <summary>
  275. /// Reference to the parent node.
  276. /// </summary>
  277. public CellTreeNode Parent;
  278. /// <summary>
  279. /// A list containing all child nodes.
  280. /// </summary>
  281. public List<CellTreeNode> Childs;
  282. /// <summary>
  283. /// The max distance the player can have to the center of the cell for being 'nearby'.
  284. /// This is calculated once at runtime.
  285. /// </summary>
  286. private float maxDistance;
  287. /// <summary>
  288. /// Default constructor.
  289. /// </summary>
  290. public CellTreeNode()
  291. {
  292. }
  293. /// <summary>
  294. /// Constructor to define the ID and the node type as well as setting a parent node.
  295. /// </summary>
  296. /// <param name="id">The ID of the cell is used as the interest group.</param>
  297. /// <param name="nodeType">The node type of the cell tree node.</param>
  298. /// <param name="parent">The parent node of the cell tree node.</param>
  299. public CellTreeNode(byte id, ENodeType nodeType, CellTreeNode parent)
  300. {
  301. this.Id = id;
  302. this.NodeType = nodeType;
  303. this.Parent = parent;
  304. }
  305. /// <summary>
  306. /// Adds the given child to the node.
  307. /// </summary>
  308. /// <param name="child">The child which is added to the node.</param>
  309. public void AddChild(CellTreeNode child)
  310. {
  311. if (this.Childs == null)
  312. {
  313. this.Childs = new List<CellTreeNode>(1);
  314. }
  315. this.Childs.Add(child);
  316. }
  317. /// <summary>
  318. /// Draws the cell in the editor.
  319. /// </summary>
  320. public void Draw()
  321. {
  322. #if UNITY_EDITOR
  323. if (this.Childs != null)
  324. {
  325. foreach (CellTreeNode node in this.Childs)
  326. {
  327. node.Draw();
  328. }
  329. }
  330. Gizmos.color = new Color((this.NodeType == ENodeType.Root) ? 1 : 0, (this.NodeType == ENodeType.Node) ? 1 : 0, (this.NodeType == ENodeType.Leaf) ? 1 : 0);
  331. Gizmos.DrawWireCube(this.Center, this.Size);
  332. byte offset = (byte)this.NodeType;
  333. GUIStyle gs = new GUIStyle() { fontStyle = FontStyle.Bold };
  334. gs.normal.textColor = Gizmos.color;
  335. UnityEditor.Handles.Label(this.Center+(Vector3.forward*offset*1f), this.Id.ToString(), gs);
  336. #endif
  337. }
  338. /// <summary>
  339. /// Gathers all cell IDs the player is currently inside or nearby.
  340. /// </summary>
  341. /// <param name="activeCells">The list to add all cell IDs to the player is currently inside or nearby.</param>
  342. /// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
  343. /// <param name="position">The current position of the player.</param>
  344. public void GetActiveCells(List<byte> activeCells, bool yIsUpAxis, Vector3 position)
  345. {
  346. if (this.NodeType != ENodeType.Leaf)
  347. {
  348. foreach (CellTreeNode node in this.Childs)
  349. {
  350. node.GetActiveCells(activeCells, yIsUpAxis, position);
  351. }
  352. }
  353. else
  354. {
  355. if (this.IsPointNearCell(yIsUpAxis, position))
  356. {
  357. if (this.IsPointInsideCell(yIsUpAxis, position))
  358. {
  359. activeCells.Insert(0, this.Id);
  360. CellTreeNode p = this.Parent;
  361. while (p != null)
  362. {
  363. activeCells.Insert(0, p.Id);
  364. p = p.Parent;
  365. }
  366. }
  367. else
  368. {
  369. activeCells.Add(this.Id);
  370. }
  371. }
  372. }
  373. }
  374. /// <summary>
  375. /// Checks if the given point is inside the cell.
  376. /// </summary>
  377. /// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
  378. /// <param name="point">The point to check.</param>
  379. /// <returns>True if the point is inside the cell, false if the point is not inside the cell.</returns>
  380. public bool IsPointInsideCell(bool yIsUpAxis, Vector3 point)
  381. {
  382. if ((point.x < this.TopLeft.x) || (point.x > this.BottomRight.x))
  383. {
  384. return false;
  385. }
  386. if (yIsUpAxis)
  387. {
  388. if ((point.y >= this.TopLeft.y) && (point.y <= this.BottomRight.y))
  389. {
  390. return true;
  391. }
  392. }
  393. else
  394. {
  395. if ((point.z >= this.TopLeft.z) && (point.z <= this.BottomRight.z))
  396. {
  397. return true;
  398. }
  399. }
  400. return false;
  401. }
  402. /// <summary>
  403. /// Checks if the given point is near the cell.
  404. /// </summary>
  405. /// <param name="yIsUpAxis">Describes if the y-axis is used as up-axis.</param>
  406. /// <param name="point">The point to check.</param>
  407. /// <returns>True if the point is near the cell, false if the point is too far away.</returns>
  408. public bool IsPointNearCell(bool yIsUpAxis, Vector3 point)
  409. {
  410. if (this.maxDistance == 0.0f)
  411. {
  412. this.maxDistance = (this.Size.x + this.Size.y + this.Size.z) / 2.0f;
  413. }
  414. return ((point - this.Center).sqrMagnitude <= (this.maxDistance * this.maxDistance));
  415. }
  416. }
  417. public class ByteComparer : IComparer<byte>
  418. {
  419. /// <inheritdoc />
  420. public int Compare(byte x, byte y)
  421. {
  422. return x == y ? 0 : x < y ? -1 : 1;
  423. }
  424. }
  425. }