1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
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;
}
}
}
|