Looking-Glass/Assets/vrsamplescene/XR Interaction Toolkit/2.5.2/Starter Assets/Scripts/ObjectSpawner.cs

237 lines
9.3 KiB
C#
Raw Permalink Normal View History

2024-02-02 12:54:47 +01:00
using System;
using System.Collections.Generic;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Behavior with an API for spawning objects from a given set of prefabs.
/// </summary>
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;
/// <summary>
/// The camera that objects will face when spawned. If not set, defaults to the <see cref="Camera.main"/> camera.
/// </summary>
public Camera cameraToFace
{
get
{
EnsureFacingCamera();
return m_CameraToFace;
}
set => m_CameraToFace = value;
}
[SerializeField]
[Tooltip("The list of prefabs available to spawn.")]
List<GameObject> m_ObjectPrefabs = new List<GameObject>();
/// <summary>
/// The list of prefabs available to spawn.
/// </summary>
public List<GameObject> 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;
/// <summary>
/// Optional prefab to spawn for each spawned object.
/// </summary>
/// <remarks>Use a prefab with <see cref="DestroySelf"/> to make sure the visualization only lives temporarily.</remarks>
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;
/// <summary>
/// The index of the prefab to spawn. If outside the range of <see cref="objectPrefabs"/>, this behavior will
/// select a random object each time it spawns.
/// </summary>
/// <seealso cref="isSpawnOptionRandomized"/>
public int spawnOptionIndex
{
get => m_SpawnOptionIndex;
set => m_SpawnOptionIndex = value;
}
/// <summary>
/// Whether this behavior will select a random object from <see cref="objectPrefabs"/> each time it spawns.
/// </summary>
/// <seealso cref="spawnOptionIndex"/>
/// <seealso cref="RandomizeSpawnOption"/>
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;
/// <summary>
/// Whether to only spawn an object if the spawn point is within view of the <see cref="cameraToFace"/>.
/// </summary>
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;
/// <summary>
/// The size, in viewport units, of the periphery inside the viewport that will not be considered in view.
/// </summary>
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;
/// <summary>
/// When enabled, the object will be rotated about the y-axis when spawned by <see cref="spawnAngleRange"/>
/// in relation to the direction of the spawn point to the camera.
/// </summary>
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;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// Whether to spawn each object as a child of this object.
/// </summary>
public bool spawnAsChildren
{
get => m_SpawnAsChildren;
set => m_SpawnAsChildren = value;
}
/// <summary>
/// Event invoked after an object is spawned.
/// </summary>
/// <seealso cref="TrySpawnObject"/>
public event Action<GameObject> objectSpawned;
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
void Awake()
{
EnsureFacingCamera();
}
void EnsureFacingCamera()
{
if (m_CameraToFace == null)
m_CameraToFace = Camera.main;
}
/// <summary>
/// Sets this behavior to select a random object from <see cref="objectPrefabs"/> each time it spawns.
/// </summary>
/// <seealso cref="spawnOptionIndex"/>
/// <seealso cref="isSpawnOptionRandomized"/>
public void RandomizeSpawnOption()
{
m_SpawnOptionIndex = -1;
}
/// <summary>
/// Attempts to spawn an object from <see cref="objectPrefabs"/> at the given position. The object will have a
/// yaw rotation that faces <see cref="cameraToFace"/>, plus or minus a random angle within <see cref="spawnAngleRange"/>.
/// </summary>
/// <param name="spawnPoint">The world space position at which to spawn the object.</param>
/// <param name="spawnNormal">The world space normal of the spawn surface.</param>
/// <returns>Returns <see langword="true"/> if the spawner successfully spawned an object. Otherwise returns
/// <see langword="false"/>, for instance if the spawn point is out of view of the camera.</returns>
/// <remarks>
/// The object selected to spawn is based on <see cref="spawnOptionIndex"/>. If the index is outside
/// the range of <see cref="objectPrefabs"/>, this method will select a random prefab from the list to spawn.
/// Otherwise, it will spawn the prefab at the index.
/// </remarks>
/// <seealso cref="objectSpawned"/>
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;
}
}
}