-
-
Save neon-age/6f885b941ce3a099d80c047653a9fc0d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEditor.Callbacks; | |
using UnityEngine; | |
using UnityEngine.AddressableAssets; | |
using Object = UnityEngine.Object; | |
using InstantiateHandle = UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<UnityEngine.GameObject>; | |
/// <summary> | |
/// Handles prefab asset loading and allows to instantiate it in a synchronous way. | |
/// </summary> | |
[Serializable] | |
public class PrefabReference | |
{ | |
private struct SpawnHandle | |
{ | |
public Vector3 position; | |
public Quaternion rotation; | |
public Vector3 scale; | |
public Transform parent; | |
} | |
[SerializeField] | |
private AssetReferenceGameObject Asset; | |
private InstantiateHandle loadHandle; | |
/// <summary> | |
/// Loaded GameObject that is used as archetype for instancing. | |
/// </summary> | |
public GameObject Archetype { get; private set; } | |
/// <summary> | |
/// Last created object using <see cref="InstantiateFromArchetype"/>. | |
/// </summary> | |
public GameObject LastCreatedInstance { get; private set; } | |
public HashSet<GameObject> Instances => GetInstances(); | |
public int InstancesCount { get; private set; } | |
public bool IsAssetLoaded { get; private set; } | |
public InstantiateHandle LoadHandle => loadHandle; | |
private static Dictionary<InstantiateHandle, SpawnHandle> AsyncSpawnHandles = new Dictionary<InstantiateHandle, SpawnHandle>(); | |
private static Dictionary<GameObject, HashSet<GameObject>> CreatedInstances = new Dictionary<GameObject, HashSet<GameObject>>(); | |
#if UNITY_EDITOR | |
[DidReloadScripts] | |
private static void OnReloadScripts() | |
{ | |
AsyncSpawnHandles = new Dictionary<InstantiateHandle, SpawnHandle>(); | |
CreatedInstances = new Dictionary<GameObject, HashSet<GameObject>>(); | |
} | |
#endif | |
public void LoadAsset() | |
{ | |
loadHandle = Addressables.LoadAssetAsync<GameObject>(Asset); | |
loadHandle.Completed += ArchetypeLoadComplete; | |
} | |
public HashSet<GameObject> GetInstances() | |
{ | |
#if UNITY_EDITOR | |
if (!CreatedInstances.TryGetValue(Archetype, out var instances)) | |
{ | |
// ReSharper disable once Unity.PerformanceCriticalCodeInvocation | |
Debug.Log($"{Asset.editorAsset.name} asset is not loaded."); | |
} | |
#endif | |
return instances; | |
} | |
/// <summary> | |
/// Creates new instance of prefab in a synchronous way. | |
/// <para>If asset is not loaded, will fallback to async function.</para> | |
/// Use <seealso cref="LoadAsset"/> a few seconds before instancing to ensure synchronous creation. | |
/// </summary> | |
public void Instantiate(Vector3 position = default, Quaternion rotation = default, Transform parent = null) | |
{ | |
if (IsAssetLoaded) | |
{ | |
InstantiateFromArchetype(position, rotation, parent); | |
} | |
else | |
{ | |
InstantiateAsync(position, rotation, parent); | |
} | |
} | |
/// <summary> | |
/// Waits until prefab asset is loaded and creates new instance from it. | |
/// <para>If asset is not loaded yet, will wait until it's loaded and instantiate async.</para> | |
/// <para>Use <seealso cref="LoadAsset"/> a few seconds before instancing to ensure creation at the right time.</para> | |
/// </summary> | |
public IEnumerator InstantiateAsync(Vector3 position = default, Quaternion rotation = default, Transform parent = null) | |
{ | |
if (IsAssetLoaded) | |
{ | |
InstantiateFromArchetype(position, rotation, parent); | |
yield break; | |
} | |
if (loadHandle.IsValid()) | |
{ | |
yield return loadHandle; // Asset is still loading... | |
} | |
else | |
{ | |
// Was not preloaded - load async | |
loadHandle = Addressables.LoadAssetAsync<GameObject>(Asset); | |
loadHandle.Completed += InstantiateFromLoadedArchetype; | |
var spawn = new SpawnHandle {position = position, rotation = rotation, parent = parent}; | |
AsyncSpawnHandles.Add(loadHandle, spawn); | |
yield return loadHandle; | |
IsAssetLoaded = true; | |
} | |
} | |
private void InstantiateFromLoadedArchetype(InstantiateHandle handle) | |
{ | |
ArchetypeLoadComplete(handle); | |
var spawn = AsyncSpawnHandles[handle]; | |
InstantiateFromArchetype(spawn.position, spawn.rotation, spawn.parent); | |
AsyncSpawnHandles.Remove(handle); | |
} | |
private void ArchetypeLoadComplete(InstantiateHandle handle) | |
{ | |
Archetype = handle.Result; | |
IsAssetLoaded = true; | |
} | |
private void InstantiateFromArchetype(Vector3 position = default, Quaternion rotation = default, Transform parent = null) | |
{ | |
if (!CreatedInstances.TryGetValue(Archetype, out var instances)) | |
{ | |
CreatedInstances.Add(Archetype, instances = new HashSet<GameObject>()); | |
} | |
var instance = Object.Instantiate(Archetype, position, rotation, parent); | |
LastCreatedInstance = instance; | |
instances.Add(instance); | |
InstancesCount++; | |
} | |
public void UnloadAsset() | |
{ | |
Addressables.Release(loadHandle); | |
Debug.Log("а?"); | |
IsAssetLoaded = false; | |
} | |
public void DestroyAllInstances() | |
{ | |
#if UNITY_EDITOR | |
if (!CreatedInstances.TryGetValue(Archetype, out var instances)) | |
{ | |
Debug.LogError($"{Asset.editorAsset.name} is not loaded and has no instances."); | |
return; | |
} | |
#endif | |
foreach (var instance in instances) | |
{ | |
Object.Destroy(instance); | |
} | |
instances.Clear(); | |
InstancesCount = 0; | |
} | |
public void Destroy(GameObject instance) | |
{ | |
#if UNITY_EDITOR | |
if (!CreatedInstances.TryGetValue(Archetype, out var instances)) | |
{ | |
// ReSharper disable once Unity.PerformanceCriticalCodeInvocation | |
Debug.LogError($"{Asset.editorAsset.name} is not loaded and has no instances."); | |
return; | |
} | |
if (instance && !instances.Contains(instance)) | |
{ | |
// ReSharper disable once Unity.PerformanceCriticalCodeInvocation | |
Debug.LogError($"{instance} is not an instance of \"{Asset.editorAsset.name}\"."); | |
} | |
#endif | |
Object.Destroy(instance); | |
instances.Remove(instance); | |
InstancesCount--; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.InputSystem; | |
using Random = UnityEngine.Random; | |
public class Spawner : MonoBehaviour | |
{ | |
public PrefabReference prefab; | |
[Space] | |
[SerializeField, Min(0)] private float interval; | |
[SerializeField, Min(0)] private int initialSpawnCount = 1; | |
[SerializeField, Min(0)] private int maxInstancesCount = 1; | |
[Space] | |
[SerializeField] private Vector3 randomDistribution; | |
[SerializeField] private Vector3 minScaling; | |
[SerializeField] private Vector3 maxScaling; | |
[SerializeField] private bool spawnConstantly; | |
[SerializeField] private bool spawnAsChild; | |
public float Interval | |
{ | |
get => interval; | |
set => waitInterval = new WaitForSeconds(interval = value); | |
} | |
private WaitForSeconds waitInterval; | |
private WaitForFixedUpdate waitForFixedUpdate = new WaitForFixedUpdate(); | |
private IEnumerator spawnRoutine; | |
private new Transform transform; | |
private bool initialSpawnCompleted; | |
private void OnEnable() | |
{ | |
transform = base.transform; | |
prefab.LoadAsset(); | |
StartCoroutine(spawnRoutine = SpawnRoutine()); | |
} | |
private void OnValidate() | |
{ | |
waitInterval = new WaitForSeconds(Interval); | |
} | |
private void OnDisable() | |
{ | |
StopCoroutine(spawnRoutine); | |
prefab.UnloadAsset(); | |
} | |
private void Update() | |
{ | |
if (prefab.IsAssetLoaded) | |
{ | |
if (!initialSpawnCompleted) | |
{ | |
for (int i = 0; i < initialSpawnCount; i++) | |
Spawn(); | |
initialSpawnCompleted = true; | |
} | |
} | |
if (Keyboard.current.enterKey.isPressed) | |
{ | |
KillRandomInstance(); | |
} | |
} | |
private void KillRandomInstance() | |
{ | |
var i = 0; | |
var randomInstanceId = Random.Range(0, prefab.InstancesCount); | |
GameObject randomInstance = null; | |
foreach (var instance in prefab.GetInstances()) | |
{ | |
if (i == randomInstanceId) | |
randomInstance = instance; | |
i++; | |
} | |
prefab.Destroy(randomInstance); | |
} | |
private void Spawn() | |
{ | |
if (prefab.InstancesCount >= maxInstancesCount) | |
return; | |
prefab.Instantiate(GetRandomDistribution(), transform.rotation, spawnAsChild ? transform : null); | |
ScaleInstance(prefab.LastCreatedInstance); | |
} | |
private IEnumerator SpawnAsync() | |
{ | |
if (prefab.InstancesCount >= maxInstancesCount) | |
yield break; | |
yield return prefab.InstantiateAsync(GetRandomDistribution(), transform.rotation, spawnAsChild ? transform : null); | |
ScaleInstance(prefab.LastCreatedInstance); | |
} | |
private IEnumerator SpawnRoutine() | |
{ | |
while (true) | |
{ | |
yield return prefab.LoadHandle.IsDone; | |
yield return waitInterval; | |
if(spawnConstantly) | |
yield return SpawnAsync(); | |
if (prefab.InstancesCount >= maxInstancesCount) | |
yield return waitForFixedUpdate; | |
} | |
} | |
private Vector3 GetRandomDistribution() | |
{ | |
var distribution = Random.insideUnitSphere; | |
distribution.x *= randomDistribution.x; | |
distribution.y *= randomDistribution.y; | |
distribution.z *= randomDistribution.z; | |
distribution += transform.position; | |
return distribution; | |
} | |
private void ScaleInstance(GameObject instance) | |
{ | |
if (!instance) | |
return; | |
var scaling = new Vector3( | |
Random.Range(minScaling.x, maxScaling.x), | |
Random.Range(minScaling.y, maxScaling.y), | |
Random.Range(minScaling.z, maxScaling.z) | |
); | |
var transform = instance.transform; | |
var originalScale = transform.localScale; | |
transform.localScale = new Vector3( | |
originalScale.x * scaling.x, | |
originalScale.y * scaling.y, | |
originalScale.z * scaling.z | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment