ObjectSpawner.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine.XR.Interaction.Toolkit.Utilities;
  4. namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
  5. {
  6. /// <summary>
  7. /// Behavior with an API for spawning objects from a given set of prefabs.
  8. /// </summary>
  9. public class ObjectSpawner : MonoBehaviour
  10. {
  11. [SerializeField]
  12. [Tooltip("The camera that objects will face when spawned. If not set, defaults to the main camera.")]
  13. Camera m_CameraToFace;
  14. /// <summary>
  15. /// The camera that objects will face when spawned. If not set, defaults to the <see cref="Camera.main"/> camera.
  16. /// </summary>
  17. public Camera cameraToFace
  18. {
  19. get
  20. {
  21. EnsureFacingCamera();
  22. return m_CameraToFace;
  23. }
  24. set => m_CameraToFace = value;
  25. }
  26. [SerializeField]
  27. [Tooltip("The list of prefabs available to spawn.")]
  28. List<GameObject> m_ObjectPrefabs = new List<GameObject>();
  29. /// <summary>
  30. /// The list of prefabs available to spawn.
  31. /// </summary>
  32. public List<GameObject> objectPrefabs
  33. {
  34. get => m_ObjectPrefabs;
  35. set => m_ObjectPrefabs = value;
  36. }
  37. [SerializeField]
  38. [Tooltip("Optional prefab to spawn for each spawned object. Use a prefab with the Destroy Self component to make " +
  39. "sure the visualization only lives temporarily.")]
  40. GameObject m_SpawnVisualizationPrefab;
  41. /// <summary>
  42. /// Optional prefab to spawn for each spawned object.
  43. /// </summary>
  44. /// <remarks>Use a prefab with <see cref="DestroySelf"/> to make sure the visualization only lives temporarily.</remarks>
  45. public GameObject spawnVisualizationPrefab
  46. {
  47. get => m_SpawnVisualizationPrefab;
  48. set => m_SpawnVisualizationPrefab = value;
  49. }
  50. [SerializeField]
  51. [Tooltip("The index of the prefab to spawn. If outside the range of the list, this behavior will select " +
  52. "a random object each time it spawns.")]
  53. int m_SpawnOptionIndex = -1;
  54. /// <summary>
  55. /// The index of the prefab to spawn. If outside the range of <see cref="objectPrefabs"/>, this behavior will
  56. /// select a random object each time it spawns.
  57. /// </summary>
  58. /// <seealso cref="isSpawnOptionRandomized"/>
  59. public int spawnOptionIndex
  60. {
  61. get => m_SpawnOptionIndex;
  62. set => m_SpawnOptionIndex = value;
  63. }
  64. /// <summary>
  65. /// Whether this behavior will select a random object from <see cref="objectPrefabs"/> each time it spawns.
  66. /// </summary>
  67. /// <seealso cref="spawnOptionIndex"/>
  68. /// <seealso cref="RandomizeSpawnOption"/>
  69. public bool isSpawnOptionRandomized => m_SpawnOptionIndex < 0 || m_SpawnOptionIndex >= m_ObjectPrefabs.Count;
  70. [SerializeField]
  71. [Tooltip("Whether to only spawn an object if the spawn point is within view of the camera.")]
  72. bool m_OnlySpawnInView = true;
  73. /// <summary>
  74. /// Whether to only spawn an object if the spawn point is within view of the <see cref="cameraToFace"/>.
  75. /// </summary>
  76. public bool onlySpawnInView
  77. {
  78. get => m_OnlySpawnInView;
  79. set => m_OnlySpawnInView = value;
  80. }
  81. [SerializeField]
  82. [Tooltip("The size, in viewport units, of the periphery inside the viewport that will not be considered in view.")]
  83. float m_ViewportPeriphery = 0.15f;
  84. /// <summary>
  85. /// The size, in viewport units, of the periphery inside the viewport that will not be considered in view.
  86. /// </summary>
  87. public float viewportPeriphery
  88. {
  89. get => m_ViewportPeriphery;
  90. set => m_ViewportPeriphery = value;
  91. }
  92. [SerializeField]
  93. [Tooltip("When enabled, the object will be rotated about the y-axis when spawned by Spawn Angle Range, " +
  94. "in relation to the direction of the spawn point to the camera.")]
  95. bool m_ApplyRandomAngleAtSpawn = true;
  96. /// <summary>
  97. /// When enabled, the object will be rotated about the y-axis when spawned by <see cref="spawnAngleRange"/>
  98. /// in relation to the direction of the spawn point to the camera.
  99. /// </summary>
  100. public bool applyRandomAngleAtSpawn
  101. {
  102. get => m_ApplyRandomAngleAtSpawn;
  103. set => m_ApplyRandomAngleAtSpawn = value;
  104. }
  105. [SerializeField]
  106. [Tooltip("The range in degrees that the object will randomly be rotated about the y axis when spawned, " +
  107. "in relation to the direction of the spawn point to the camera.")]
  108. float m_SpawnAngleRange = 45f;
  109. /// <summary>
  110. /// The range in degrees that the object will randomly be rotated about the y axis when spawned, in relation
  111. /// to the direction of the spawn point to the camera.
  112. /// </summary>
  113. public float spawnAngleRange
  114. {
  115. get => m_SpawnAngleRange;
  116. set => m_SpawnAngleRange = value;
  117. }
  118. [SerializeField]
  119. [Tooltip("Whether to spawn each object as a child of this object.")]
  120. bool m_SpawnAsChildren;
  121. /// <summary>
  122. /// Whether to spawn each object as a child of this object.
  123. /// </summary>
  124. public bool spawnAsChildren
  125. {
  126. get => m_SpawnAsChildren;
  127. set => m_SpawnAsChildren = value;
  128. }
  129. /// <summary>
  130. /// Event invoked after an object is spawned.
  131. /// </summary>
  132. /// <seealso cref="TrySpawnObject"/>
  133. public event Action<GameObject> objectSpawned;
  134. /// <summary>
  135. /// See <see cref="MonoBehaviour"/>.
  136. /// </summary>
  137. void Awake()
  138. {
  139. EnsureFacingCamera();
  140. }
  141. void EnsureFacingCamera()
  142. {
  143. if (m_CameraToFace == null)
  144. m_CameraToFace = Camera.main;
  145. }
  146. /// <summary>
  147. /// Sets this behavior to select a random object from <see cref="objectPrefabs"/> each time it spawns.
  148. /// </summary>
  149. /// <seealso cref="spawnOptionIndex"/>
  150. /// <seealso cref="isSpawnOptionRandomized"/>
  151. public void RandomizeSpawnOption()
  152. {
  153. m_SpawnOptionIndex = -1;
  154. }
  155. /// <summary>
  156. /// Sets the <see cref="spawnOptionIndex"/> so that a specific object will spawn. If the index is out
  157. /// of bounds of the list defined in <see cref="objectPrefabs"/>, the index will not be changed.
  158. /// </summary>
  159. /// <param name="index">Index of the object to be spawned.</param>
  160. /// <seealso cref="objectPrefabs"/>
  161. /// <seealso cref="spawnOptionIndex"/>
  162. public void SetSpawnObjectIndex(int index)
  163. {
  164. if (index < m_ObjectPrefabs.Count)
  165. m_SpawnOptionIndex = index;
  166. else
  167. Debug.LogWarning("Object index specified larger than number of Object Prefabs.", this);
  168. }
  169. /// <summary>
  170. /// Attempts to spawn an object from <see cref="objectPrefabs"/> at the given position. The object will have a
  171. /// yaw rotation that faces <see cref="cameraToFace"/>, plus or minus a random angle within <see cref="spawnAngleRange"/>.
  172. /// </summary>
  173. /// <param name="spawnPoint">The world space position at which to spawn the object.</param>
  174. /// <param name="spawnNormal">The world space normal of the spawn surface.</param>
  175. /// <returns>Returns <see langword="true"/> if the spawner successfully spawned an object. Otherwise returns
  176. /// <see langword="false"/>, for instance if the spawn point is out of view of the camera.</returns>
  177. /// <remarks>
  178. /// The object selected to spawn is based on <see cref="spawnOptionIndex"/>. If the index is outside
  179. /// the range of <see cref="objectPrefabs"/>, this method will select a random prefab from the list to spawn.
  180. /// Otherwise, it will spawn the prefab at the index.
  181. /// </remarks>
  182. /// <seealso cref="objectSpawned"/>
  183. public bool TrySpawnObject(Vector3 spawnPoint, Vector3 spawnNormal)
  184. {
  185. if (m_OnlySpawnInView)
  186. {
  187. var inViewMin = m_ViewportPeriphery;
  188. var inViewMax = 1f - m_ViewportPeriphery;
  189. var pointInViewportSpace = cameraToFace.WorldToViewportPoint(spawnPoint);
  190. if (pointInViewportSpace.z < 0f || pointInViewportSpace.x > inViewMax || pointInViewportSpace.x < inViewMin ||
  191. pointInViewportSpace.y > inViewMax || pointInViewportSpace.y < inViewMin)
  192. {
  193. Debug.LogWarning("Object spawn point out of view and OnlySpawnInView is set to true.", this);
  194. return false;
  195. }
  196. }
  197. var objectIndex = isSpawnOptionRandomized ? Random.Range(0, m_ObjectPrefabs.Count) : m_SpawnOptionIndex;
  198. var newObject = Instantiate(m_ObjectPrefabs[objectIndex]);
  199. if (m_SpawnAsChildren)
  200. newObject.transform.parent = transform;
  201. newObject.transform.position = spawnPoint;
  202. EnsureFacingCamera();
  203. var facePosition = m_CameraToFace.transform.position;
  204. var forward = facePosition - spawnPoint;
  205. BurstMathUtility.ProjectOnPlane(forward, spawnNormal, out var projectedForward);
  206. newObject.transform.rotation = Quaternion.LookRotation(projectedForward, spawnNormal);
  207. if (m_ApplyRandomAngleAtSpawn)
  208. {
  209. var randomRotation = Random.Range(-m_SpawnAngleRange, m_SpawnAngleRange);
  210. newObject.transform.Rotate(Vector3.up, randomRotation);
  211. }
  212. if (m_SpawnVisualizationPrefab != null)
  213. {
  214. var visualizationTrans = Instantiate(m_SpawnVisualizationPrefab).transform;
  215. visualizationTrans.position = spawnPoint;
  216. visualizationTrans.rotation = newObject.transform.rotation;
  217. }
  218. objectSpawned?.Invoke(newObject);
  219. return true;
  220. }
  221. /// <summary>
  222. /// Attempts to spawn an object from <see cref="objectPrefabs"/> at the given position. The object will have a
  223. /// yaw rotation that faces <see cref="cameraToFace"/>, plus or minus a random angle within <see cref="spawnAngleRange"/>.
  224. /// </summary>
  225. /// <param name="spawnPoint">The world space position at which to spawn the object.</param>
  226. /// <param name="spawnNormal">The world space normal of the spawn surface.</param>
  227. /// <remarks>
  228. /// The object selected to spawn is based on <see cref="spawnOptionIndex"/>. If the index is outside
  229. /// the range of <see cref="objectPrefabs"/>, this method will select a random prefab from the list to spawn.
  230. /// Otherwise, it will spawn the prefab at the index.
  231. /// </remarks>
  232. /// <seealso cref="objectSpawned"/>
  233. public void SpawnObject(Vector3 spawnPoint, Vector3 spawnNormal)
  234. {
  235. if (!TrySpawnObject(spawnPoint, spawnNormal))
  236. Debug.LogWarning("Could not spawn object.", this);
  237. }
  238. }
  239. }