#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