using System; using System.Collections.Generic; using UnityEngine.XR.Interaction.Toolkit.Utilities; namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets { /// /// Behavior with an API for spawning objects from a given set of prefabs. /// public class ObjectSpawner : MonoBehaviour { [SerializeField] [Tooltip("The camera that objects will face when spawned. If not set, defaults to the main camera.")] Camera m_CameraToFace; /// /// The camera that objects will face when spawned. If not set, defaults to the camera. /// public Camera cameraToFace { get { EnsureFacingCamera(); return m_CameraToFace; } set => m_CameraToFace = value; } [SerializeField] [Tooltip("The list of prefabs available to spawn.")] List m_ObjectPrefabs = new List(); /// /// The list of prefabs available to spawn. /// public List objectPrefabs { get => m_ObjectPrefabs; set => m_ObjectPrefabs = value; } [SerializeField] [Tooltip("Optional prefab to spawn for each spawned object. Use a prefab with the Destroy Self component to make " + "sure the visualization only lives temporarily.")] GameObject m_SpawnVisualizationPrefab; /// /// Optional prefab to spawn for each spawned object. /// /// Use a prefab with to make sure the visualization only lives temporarily. public GameObject spawnVisualizationPrefab { get => m_SpawnVisualizationPrefab; set => m_SpawnVisualizationPrefab = value; } [SerializeField] [Tooltip("The index of the prefab to spawn. If outside the range of the list, this behavior will select " + "a random object each time it spawns.")] int m_SpawnOptionIndex = -1; /// /// The index of the prefab to spawn. If outside the range of , this behavior will /// select a random object each time it spawns. /// /// public int spawnOptionIndex { get => m_SpawnOptionIndex; set => m_SpawnOptionIndex = value; } /// /// Whether this behavior will select a random object from each time it spawns. /// /// /// public bool isSpawnOptionRandomized => m_SpawnOptionIndex < 0 || m_SpawnOptionIndex >= m_ObjectPrefabs.Count; [SerializeField] [Tooltip("Whether to only spawn an object if the spawn point is within view of the camera.")] bool m_OnlySpawnInView = true; /// /// Whether to only spawn an object if the spawn point is within view of the . /// public bool onlySpawnInView { get => m_OnlySpawnInView; set => m_OnlySpawnInView = value; } [SerializeField] [Tooltip("The size, in viewport units, of the periphery inside the viewport that will not be considered in view.")] float m_ViewportPeriphery = 0.15f; /// /// The size, in viewport units, of the periphery inside the viewport that will not be considered in view. /// public float viewportPeriphery { get => m_ViewportPeriphery; set => m_ViewportPeriphery = value; } [SerializeField] [Tooltip("When enabled, the object will be rotated about the y-axis when spawned by Spawn Angle Range, " + "in relation to the direction of the spawn point to the camera.")] bool m_ApplyRandomAngleAtSpawn = true; /// /// When enabled, the object will be rotated about the y-axis when spawned by /// in relation to the direction of the spawn point to the camera. /// public bool applyRandomAngleAtSpawn { get => m_ApplyRandomAngleAtSpawn; set => m_ApplyRandomAngleAtSpawn = value; } [SerializeField] [Tooltip("The range in degrees that the object will randomly be rotated about the y axis when spawned, " + "in relation to the direction of the spawn point to the camera.")] float m_SpawnAngleRange = 45f; /// /// The range in degrees that the object will randomly be rotated about the y axis when spawned, in relation /// to the direction of the spawn point to the camera. /// public float spawnAngleRange { get => m_SpawnAngleRange; set => m_SpawnAngleRange = value; } [SerializeField] [Tooltip("Whether to spawn each object as a child of this object.")] bool m_SpawnAsChildren; /// /// Whether to spawn each object as a child of this object. /// public bool spawnAsChildren { get => m_SpawnAsChildren; set => m_SpawnAsChildren = value; } /// /// Event invoked after an object is spawned. /// /// public event Action objectSpawned; /// /// See . /// void Awake() { EnsureFacingCamera(); } void EnsureFacingCamera() { if (m_CameraToFace == null) m_CameraToFace = Camera.main; } /// /// Sets this behavior to select a random object from each time it spawns. /// /// /// public void RandomizeSpawnOption() { m_SpawnOptionIndex = -1; } /// /// Attempts to spawn an object from at the given position. The object will have a /// yaw rotation that faces , plus or minus a random angle within . /// /// The world space position at which to spawn the object. /// The world space normal of the spawn surface. /// Returns if the spawner successfully spawned an object. Otherwise returns /// , for instance if the spawn point is out of view of the camera. /// /// The object selected to spawn is based on . If the index is outside /// the range of , this method will select a random prefab from the list to spawn. /// Otherwise, it will spawn the prefab at the index. /// /// public bool TrySpawnObject(Vector3 spawnPoint, Vector3 spawnNormal) { if (m_OnlySpawnInView) { var inViewMin = m_ViewportPeriphery; var inViewMax = 1f - m_ViewportPeriphery; var pointInViewportSpace = cameraToFace.WorldToViewportPoint(spawnPoint); if (pointInViewportSpace.z < 0f || pointInViewportSpace.x > inViewMax || pointInViewportSpace.x < inViewMin || pointInViewportSpace.y > inViewMax || pointInViewportSpace.y < inViewMin) { return false; } } var objectIndex = isSpawnOptionRandomized ? Random.Range(0, m_ObjectPrefabs.Count) : m_SpawnOptionIndex; var newObject = Instantiate(m_ObjectPrefabs[objectIndex]); if (m_SpawnAsChildren) newObject.transform.parent = transform; newObject.transform.position = spawnPoint; EnsureFacingCamera(); var facePosition = m_CameraToFace.transform.position; var forward = facePosition - spawnPoint; BurstMathUtility.ProjectOnPlane(forward, spawnNormal, out var projectedForward); newObject.transform.rotation = Quaternion.LookRotation(projectedForward, spawnNormal); if (m_ApplyRandomAngleAtSpawn) { var randomRotation = Random.Range(-m_SpawnAngleRange, m_SpawnAngleRange); newObject.transform.Rotate(Vector3.up, randomRotation); } if (m_SpawnVisualizationPrefab != null) { var visualizationTrans = Instantiate(m_SpawnVisualizationPrefab).transform; visualizationTrans.position = spawnPoint; visualizationTrans.rotation = newObject.transform.rotation; } objectSpawned?.Invoke(newObject); return true; } } }