Skip to content

Instantly share code, notes, and snippets.

@neon-age
Created September 30, 2020 11:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save neon-age/6f885b941ce3a099d80c047653a9fc0d to your computer and use it in GitHub Desktop.
Save neon-age/6f885b941ce3a099d80c047653a9fc0d to your computer and use it in GitHub Desktop.
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--;
}
}
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