SpatialHashingInterestManagement.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // extremely fast spatial hashing interest management based on uMMORPG GridChecker.
  2. // => 30x faster in initial tests
  3. // => scales way higher
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. namespace Mirror
  7. {
  8. [AddComponentMenu("Network/ Interest Management/ Spatial Hash/Spatial Hashing Interest Management")]
  9. public class SpatialHashingInterestManagement : InterestManagement
  10. {
  11. [Tooltip("The maximum range that objects will be visible at.")]
  12. public int visRange = 30;
  13. // if we see 8 neighbors then 1 entry is visRange/3
  14. public int resolution => visRange / 3;
  15. [Tooltip("Rebuild all every 'rebuildInterval' seconds.")]
  16. public float rebuildInterval = 1;
  17. double lastRebuildTime;
  18. public enum CheckMethod
  19. {
  20. XZ_FOR_3D,
  21. XY_FOR_2D
  22. }
  23. [Tooltip("Spatial Hashing supports 3D (XZ) and 2D (XY) games.")]
  24. public CheckMethod checkMethod = CheckMethod.XZ_FOR_3D;
  25. // debugging
  26. public bool showSlider;
  27. // the grid
  28. Grid2D<NetworkConnectionToClient> grid = new Grid2D<NetworkConnectionToClient>();
  29. // project 3d world position to grid position
  30. Vector2Int ProjectToGrid(Vector3 position) =>
  31. checkMethod == CheckMethod.XZ_FOR_3D
  32. ? Vector2Int.RoundToInt(new Vector2(position.x, position.z) / resolution)
  33. : Vector2Int.RoundToInt(new Vector2(position.x, position.y) / resolution);
  34. public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver)
  35. {
  36. // calculate projected positions
  37. Vector2Int projected = ProjectToGrid(identity.transform.position);
  38. Vector2Int observerProjected = ProjectToGrid(newObserver.identity.transform.position);
  39. // distance needs to be at max one of the 8 neighbors, which is
  40. // 1 for the direct neighbors
  41. // 1.41 for the diagonal neighbors (= sqrt(2))
  42. // => use sqrMagnitude and '2' to avoid computations. same result.
  43. return (projected - observerProjected).sqrMagnitude <= 2;
  44. }
  45. public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnectionToClient> newObservers)
  46. {
  47. // add everyone in 9 neighbour grid
  48. // -> pass observers to GetWithNeighbours directly to avoid allocations
  49. // and expensive .UnionWith computations.
  50. Vector2Int current = ProjectToGrid(identity.transform.position);
  51. grid.GetWithNeighbours(current, newObservers);
  52. }
  53. [ServerCallback]
  54. public override void Reset()
  55. {
  56. lastRebuildTime = 0D;
  57. }
  58. // update everyone's position in the grid
  59. // (internal so we can update from tests)
  60. [ServerCallback]
  61. internal void Update()
  62. {
  63. // NOTE: unlike Scene/MatchInterestManagement, this rebuilds ALL
  64. // entities every INTERVAL. consider the other approach later.
  65. // IMPORTANT: refresh grid every update!
  66. // => newly spawned entities get observers assigned via
  67. // OnCheckObservers. this can happen any time and we don't want
  68. // them broadcast to old (moved or destroyed) connections.
  69. // => players do move all the time. we want them to always be in the
  70. // correct grid position.
  71. // => note that the actual 'rebuildall' doesn't need to happen all
  72. // the time.
  73. // NOTE: consider refreshing grid only every 'interval' too. but not
  74. // for now. stability & correctness matter.
  75. // clear old grid results before we update everyone's position.
  76. // (this way we get rid of destroyed connections automatically)
  77. //
  78. // NOTE: keeps allocated HashSets internally.
  79. // clearing & populating every frame works without allocations
  80. grid.ClearNonAlloc();
  81. // put every connection into the grid at it's main player's position
  82. // NOTE: player sees in a radius around him. NOT around his pet too.
  83. foreach (NetworkConnectionToClient connection in NetworkServer.connections.Values)
  84. {
  85. // authenticated and joined world with a player?
  86. if (connection.isAuthenticated && connection.identity != null)
  87. {
  88. // calculate current grid position
  89. Vector2Int position = ProjectToGrid(connection.identity.transform.position);
  90. // put into grid
  91. grid.Add(position, connection);
  92. }
  93. }
  94. // rebuild all spawned entities' observers every 'interval'
  95. // this will call OnRebuildObservers which then returns the
  96. // observers at grid[position] for each entity.
  97. if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
  98. {
  99. RebuildAll();
  100. lastRebuildTime = NetworkTime.localTime;
  101. }
  102. }
  103. // OnGUI allocates even if it does nothing. avoid in release.
  104. #if UNITY_EDITOR || DEVELOPMENT_BUILD
  105. // slider from dotsnet. it's nice to play around with in the benchmark
  106. // demo.
  107. void OnGUI()
  108. {
  109. if (!showSlider) return;
  110. // only show while server is running. not on client, etc.
  111. if (!NetworkServer.active) return;
  112. int height = 30;
  113. int width = 250;
  114. GUILayout.BeginArea(new Rect(Screen.width / 2 - width / 2, Screen.height - height, width, height));
  115. GUILayout.BeginHorizontal("Box");
  116. GUILayout.Label("Radius:");
  117. visRange = Mathf.RoundToInt(GUILayout.HorizontalSlider(visRange, 0, 200, GUILayout.Width(150)));
  118. GUILayout.Label(visRange.ToString());
  119. GUILayout.EndHorizontal();
  120. GUILayout.EndArea();
  121. }
  122. #endif
  123. }
  124. }