237 lines
9.3 KiB
C#
237 lines
9.3 KiB
C#
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|