#if AR_FOUNDATION_PRESENT using UnityEngine.EventSystems; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers; using UnityEngine.XR.Interaction.Toolkit.Interactors; using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets; namespace UnityEngine.XR.Interaction.Toolkit.Samples.ARStarterAssets { /// /// Spawns an object at an 's raycast hit position when a trigger is activated. /// public class ARInteractorSpawnTrigger : MonoBehaviour { /// /// The type of trigger to use to spawn an object. /// public enum SpawnTriggerType { /// /// Spawn an object when the interactor activates its select input /// but no selection actually occurs. /// SelectAttempt, /// /// Spawn an object when an input is performed. /// InputAction, } [SerializeField] [Tooltip("The AR ray interactor that determines where to spawn the object.")] XRRayInteractor m_ARInteractor; /// /// The AR ray interactor that determines where to spawn the object. /// public XRRayInteractor arInteractor { get => m_ARInteractor; set => m_ARInteractor = value; } [SerializeField] [Tooltip("The behavior to use to spawn objects.")] ObjectSpawner m_ObjectSpawner; /// /// The behavior to use to spawn objects. /// public ObjectSpawner objectSpawner { get => m_ObjectSpawner; set => m_ObjectSpawner = value; } [SerializeField] [Tooltip("Whether to require that the AR Interactor hits an AR Plane with a horizontal up alignment in order to spawn anything.")] bool m_RequireHorizontalUpSurface; /// /// Whether to require that the hits an with an alignment of /// in order to spawn anything. /// public bool requireHorizontalUpSurface { get => m_RequireHorizontalUpSurface; set => m_RequireHorizontalUpSurface = value; } [SerializeField] [Tooltip("The type of trigger to use to spawn an object, either when the Interactor's select action occurs or " + "when a button input is performed.")] SpawnTriggerType m_SpawnTriggerType; /// /// The type of trigger to use to spawn an object. /// public SpawnTriggerType spawnTriggerType { get => m_SpawnTriggerType; set => m_SpawnTriggerType = value; } [SerializeField] XRInputButtonReader m_SpawnObjectInput = new XRInputButtonReader("Spawn Object"); /// /// The input used to trigger spawn, if is set to . /// public XRInputButtonReader spawnObjectInput { get => m_SpawnObjectInput; set => XRInputReaderUtility.SetInputProperty(ref m_SpawnObjectInput, value, this); } [SerializeField] [Tooltip("When enabled, spawn will not be triggered if an object is currently selected.")] bool m_BlockSpawnWhenInteractorHasSelection = true; /// /// When enabled, spawn will not be triggered if an object is currently selected. /// public bool blockSpawnWhenInteractorHasSelection { get => m_BlockSpawnWhenInteractorHasSelection; set => m_BlockSpawnWhenInteractorHasSelection = value; } bool m_AttemptSpawn; bool m_EverHadSelection; /// /// See . /// void OnEnable() { m_SpawnObjectInput.EnableDirectActionIfModeUsed(); } /// /// See . /// void OnDisable() { m_SpawnObjectInput.DisableDirectActionIfModeUsed(); } /// /// See . /// void Start() { if (m_ObjectSpawner == null) #if UNITY_2023_1_OR_NEWER m_ObjectSpawner = FindAnyObjectByType(); #else m_ObjectSpawner = FindObjectOfType(); #endif if (m_ARInteractor == null) { Debug.LogError("Missing AR Interactor reference, disabling component.", this); enabled = false; } } /// /// See . /// void Update() { // Wait a frame after the Spawn Object input is triggered to actually cast against AR planes and spawn // in order to ensure the touchscreen gestures have finished processing to allow the ray pose driver // to update the pose based on the touch position of the gestures. if (m_AttemptSpawn) { m_AttemptSpawn = false; // Cancel the spawn if the select was delayed until the frame after the spawn trigger. // This can happen if the select action uses a different input source than the spawn trigger. if (m_ARInteractor.hasSelection) return; // Don't spawn the object if the tap was over screen space UI. var isPointerOverUI = EventSystem.current != null && EventSystem.current.IsPointerOverGameObject(-1); if (!isPointerOverUI && m_ARInteractor.TryGetCurrentARRaycastHit(out var arRaycastHit)) { if (!(arRaycastHit.trackable is ARPlane arPlane)) return; if (m_RequireHorizontalUpSurface && arPlane.alignment != PlaneAlignment.HorizontalUp) return; m_ObjectSpawner.TrySpawnObject(arRaycastHit.pose.position, arPlane.normal); } return; } var selectState = m_ARInteractor.logicalSelectState; if (m_BlockSpawnWhenInteractorHasSelection) { if (selectState.wasPerformedThisFrame) m_EverHadSelection = m_ARInteractor.hasSelection; else if (selectState.active) m_EverHadSelection |= m_ARInteractor.hasSelection; } m_AttemptSpawn = false; switch (m_SpawnTriggerType) { case SpawnTriggerType.SelectAttempt: if (selectState.wasCompletedThisFrame) m_AttemptSpawn = !m_ARInteractor.hasSelection && !m_EverHadSelection; break; case SpawnTriggerType.InputAction: if (m_SpawnObjectInput.ReadWasPerformedThisFrame()) m_AttemptSpawn = !m_ARInteractor.hasSelection && !m_EverHadSelection; break; } } } } #endif