RVOSimulator.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. using UnityEngine;
  2. using UnityEngine.Profiling;
  3. namespace Pathfinding.RVO {
  4. using Pathfinding.Util;
  5. /// <summary>
  6. /// Unity front end for an RVO simulator.
  7. /// Attached to any GameObject in a scene, scripts such as the RVOController will use the
  8. /// simulator exposed by this class to handle their movement.
  9. /// In pretty much all cases you should only have a single RVOSimulator in the scene.
  10. ///
  11. /// You can have more than one of these, however most scripts which make use of the RVOSimulator
  12. /// will use the <see cref="active"/> property which just returns the first simulator in the scene.
  13. ///
  14. /// This is only a wrapper class for a Pathfinding.RVO.Simulator which simplifies exposing it
  15. /// for a unity scene.
  16. ///
  17. /// See: Pathfinding.RVO.Simulator
  18. /// See: local-avoidance (view in online documentation for working links)
  19. /// </summary>
  20. [ExecuteInEditMode]
  21. [AddComponentMenu("Pathfinding/Local Avoidance/RVO Simulator")]
  22. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_r_v_o_1_1_r_v_o_simulator.php")]
  23. public class RVOSimulator : VersionedMonoBehaviour {
  24. /// <summary>First RVOSimulator in the scene (usually there is only one)</summary>
  25. public static RVOSimulator active { get; private set; }
  26. /// <summary>
  27. /// Desired FPS for rvo simulation.
  28. /// It is usually not necessary to run a crowd simulation at a very high fps.
  29. /// Usually 10-30 fps is enough, but it can be increased for better quality.
  30. /// The rvo simulation will never run at a higher fps than the game
  31. /// </summary>
  32. [Tooltip("Desired FPS for rvo simulation. It is usually not necessary to run a crowd simulation at a very high fps.\n" +
  33. "Usually 10-30 fps is enough, but can be increased for better quality.\n"+
  34. "The rvo simulation will never run at a higher fps than the game")]
  35. public int desiredSimulationFPS = 20;
  36. /// <summary>
  37. /// Number of RVO worker threads.
  38. /// If set to None, no multithreading will be used.
  39. /// Using multithreading can significantly improve performance by offloading work to other CPU cores.
  40. /// </summary>
  41. [Tooltip("Number of RVO worker threads. If set to None, no multithreading will be used.")]
  42. public ThreadCount workerThreads = ThreadCount.Two;
  43. /// <summary>
  44. /// Calculate local avoidance in between frames.
  45. /// If this is enabled and multithreading is used, the local avoidance calculations will continue to run
  46. /// until the next frame instead of waiting for them to be done the same frame. This can increase the performance
  47. /// but it can make the agents seem a little less responsive.
  48. ///
  49. /// This will only be read at Awake.
  50. /// See: Pathfinding.RVO.Simulator.DoubleBuffering
  51. /// </summary>
  52. [Tooltip("Calculate local avoidance in between frames.\nThis can increase jitter in the agents' movement so use it only if you really need the performance boost. " +
  53. "It will also reduce the responsiveness of the agents to the commands you send to them.")]
  54. public bool doubleBuffering;
  55. /// <summary>\copydoc Pathfinding::RVO::Simulator::symmetryBreakingBias</summary>
  56. [Tooltip("Bias agents to pass each other on the right side.\n" +
  57. "If the desired velocity of an agent puts it on a collision course with another agent or an obstacle " +
  58. "its desired velocity will be rotated this number of radians (1 radian is approximately 57°) to the right. " +
  59. "This helps to break up symmetries and makes it possible to resolve some situations much faster.\n\n" +
  60. "When many agents have the same goal this can however have the side effect that the group " +
  61. "clustered around the target point may as a whole start to spin around the target point.")]
  62. [Range(0, 0.2f)]
  63. public float symmetryBreakingBias = 0.1f;
  64. /// <summary>
  65. /// Determines if the XY (2D) or XZ (3D) plane is used for movement.
  66. /// For 2D games you would set this to XY and for 3D games you would usually set it to XZ.
  67. /// </summary>
  68. [Tooltip("Determines if the XY (2D) or XZ (3D) plane is used for movement")]
  69. public MovementPlane movementPlane = MovementPlane.XZ;
  70. /// <summary>
  71. /// Draw obstacle gizmos to aid with debugging.
  72. ///
  73. /// In the screenshot the obstacles are visible in red.
  74. /// [Open online documentation to see images]
  75. /// </summary>
  76. public bool drawObstacles;
  77. /// <summary>Reference to the internal simulator</summary>
  78. Pathfinding.RVO.Simulator simulator;
  79. /// <summary>
  80. /// Get the internal simulator.
  81. /// Will never be null when the game is running
  82. /// </summary>
  83. public Simulator GetSimulator () {
  84. if (simulator == null) {
  85. Awake();
  86. }
  87. return simulator;
  88. }
  89. void OnEnable () {
  90. active = this;
  91. }
  92. protected override void Awake () {
  93. base.Awake();
  94. // We need to set active during Awake as well to ensure it is set when graphs are being scanned.
  95. // That is important if the RVONavmesh component is being used.
  96. active = this;
  97. if (simulator == null && Application.isPlaying) {
  98. int threadCount = AstarPath.CalculateThreadCount(workerThreads);
  99. simulator = new Pathfinding.RVO.Simulator(threadCount, doubleBuffering, movementPlane);
  100. }
  101. }
  102. /// <summary>Update the simulation</summary>
  103. void Update () {
  104. if (!Application.isPlaying) return;
  105. if (desiredSimulationFPS < 1) desiredSimulationFPS = 1;
  106. var sim = GetSimulator();
  107. sim.DesiredDeltaTime = 1.0f / desiredSimulationFPS;
  108. sim.symmetryBreakingBias = symmetryBreakingBias;
  109. sim.Update();
  110. }
  111. void OnDestroy () {
  112. active = null;
  113. if (simulator != null) simulator.OnDestroy();
  114. }
  115. #if UNITY_EDITOR
  116. [System.NonSerialized]
  117. RetainedGizmos gizmos = new RetainedGizmos();
  118. static Color ObstacleColor = new Color(255/255f, 60/255f, 15/255f, 1.0f);
  119. void OnDrawGizmos () {
  120. // Prevent interfering with scene view picking
  121. if (Event.current.type != EventType.Repaint) return;
  122. if (drawObstacles && simulator != null && simulator.obstacles != null) {
  123. var hasher = new RetainedGizmos.Hasher();
  124. var obstacles = simulator.obstacles;
  125. int numEdges = 0;
  126. for (int i = 0; i < obstacles.Count; i++) {
  127. var vertex = obstacles[i];
  128. do {
  129. hasher.AddHash(vertex.position.GetHashCode() ^ vertex.height.GetHashCode());
  130. numEdges++;
  131. vertex = vertex.next;
  132. } while (vertex != obstacles[i] && vertex != null);
  133. }
  134. if (!gizmos.Draw(hasher)) {
  135. Profiler.BeginSample("Rebuild RVO Obstacle Gizmos");
  136. using (var helper = gizmos.GetGizmoHelper(null, hasher)) {
  137. var up = movementPlane == MovementPlane.XY ? Vector3.back : Vector3.up;
  138. var vertices = new Vector3[numEdges*6];
  139. var colors = new Color[numEdges*6];
  140. int edgeIndex = 0;
  141. for (int i = 0; i < obstacles.Count; i++) {
  142. var start = obstacles[i];
  143. var c = start;
  144. do {
  145. vertices[edgeIndex*6 + 0] = c.position;
  146. vertices[edgeIndex*6 + 1] = c.next.position;
  147. vertices[edgeIndex*6 + 2] = c.next.position + up*c.next.height;
  148. vertices[edgeIndex*6 + 3] = c.position;
  149. vertices[edgeIndex*6 + 4] = c.next.position + up*c.next.height;
  150. vertices[edgeIndex*6 + 5] = c.position + up*c.height;
  151. edgeIndex++;
  152. c = c.next;
  153. } while (c != start && c != null && c.next != null);
  154. }
  155. for (int i = 0; i < colors.Length; i++) {
  156. colors[i] = ObstacleColor;
  157. }
  158. helper.DrawTriangles(vertices, colors, numEdges * 2);
  159. }
  160. Profiler.EndSample();
  161. }
  162. gizmos.FinalizeDraw();
  163. }
  164. }
  165. void OnDisable () {
  166. gizmos.ClearCache();
  167. }
  168. #endif
  169. }
  170. }