Last active
March 22, 2023 20:17
-
-
Save onewinter/2680facb018c1473cb2565af49a46dfc to your computer and use it in GitHub Desktop.
A Unity 2021+ Pooling Framework using TypeObjects and ScriptableObjects; GameEffect & GameEffectSetup show how to use it to easily start/stop Particle Systems effects; Pickup & PickupSetup show how to use it to create pickups like Gems or Coins.
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 UnityEngine; | |
public class GameEffect : PooledObjectBase | |
{ | |
public Transform EffectTarget; | |
public float EffectDuration; | |
[SerializeField] private float timer; | |
public override void BeforeEnable() | |
{ | |
base.BeforeEnable(); | |
timer = 0; | |
} | |
private void Update() | |
{ | |
timer += Time.deltaTime; | |
if (EffectDuration > 0f && timer >= EffectDuration) // first see if the effect has a timer | |
{ | |
ReleaseToPool(); | |
return; | |
} | |
// if no timer, see if we should be following a target | |
if (EffectTarget && EffectTarget.gameObject.activeInHierarchy) transform.position = EffectTarget.position; | |
// otherwise quit if we have no target | |
else if (EffectDuration == 0 && !EffectTarget) ReleaseToPool(); | |
} | |
} |
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 UnityEngine; | |
[CreateAssetMenu(fileName = "GameEffect_", menuName = "Scriptable Objects/Game Effect", order = 51)] | |
public class GameEffectSetup : PooledObjectSetup | |
{ | |
public GameEffect PlayEffect(Vector3 position, Transform target = null, float duration = 0f) | |
{ | |
var newEffect = (GameEffect)GetPooledObject(position); | |
newEffect.EffectDuration = duration; | |
newEffect.EffectTarget = target; | |
return newEffect; | |
} | |
public GameEffect PlayEffect(Vector3 position, float duration = 0f) => PlayEffect(position, null, duration); | |
public void StopEffect(GameEffect effect) | |
{ | |
effect.EffectDuration = 0; | |
effect.EffectTarget = null; | |
} | |
} |
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 UnityEngine; | |
using Random = UnityEngine.Random; | |
public enum PickupTypes{Wood, Stone, Material, Meat} | |
public class Pickup : PooledObjectBase | |
{ | |
[SerializeField] private GameData gameData; | |
[SerializeField] private GameEvents gameEvents; | |
[SerializeField] private PickupTypes pickupType; | |
[SerializeField] private int amount; | |
private float timer = 0; | |
private PickupSetup PickupObjectSetup => (PickupSetup)ObjectSetup; | |
public override void InitializeObjectSetup() | |
{ | |
base.InitializeObjectSetup(); | |
pickupType = PickupObjectSetup.PickupType; | |
} | |
private void OnTriggerEnter(Collider other) | |
{ | |
var player = other.GetComponent<Player>(); | |
if (!player) return; | |
var message = "Picked up " + amount + " " + PickupObjectSetup.PickupName + "."; | |
switch(pickupType) | |
{ | |
case PickupTypes.Wood: | |
gameData.Wood += amount; | |
gameData.Food += amount / 4f; | |
gameEvents.WoodCollected.Raise(amount); | |
break; | |
case PickupTypes.Stone: | |
gameData.Stone += amount; | |
gameData.Food += amount / 4f; | |
gameEvents.StoneCollected.Raise(amount); | |
break; | |
case PickupTypes.Material: | |
gameData.Material += amount; | |
gameData.Food += amount / 4f; | |
gameEvents.MaterialCollected.Raise(amount); | |
break; | |
case PickupTypes.Meat: | |
gameData.Meat += amount; | |
gameData.Food += amount; | |
gameEvents.MeatCollected.Raise(amount); | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
gameEvents.AudioEvent.Raise(PickupObjectSetup.PickupSound); | |
gameEvents.GameMessageEvent.Raise(new Message(PickupObjectSetup.PickupIcon, message, 1f)); | |
ReleaseToPool(); | |
} | |
public override void BeforeEnable() | |
{ | |
base.BeforeEnable(); | |
amount = Random.Range(PickupObjectSetup.PickupMin, PickupObjectSetup.PickupMax) + gameData.AdditionalPickup; | |
timer = 0; | |
} | |
void Update() | |
{ | |
timer += Time.deltaTime; | |
// pickups expire after N secs | |
if (timer > GameData.PickupExpireTime) ReleaseToPool(); | |
} | |
} |
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 UnityEngine; | |
[CreateAssetMenu (fileName = "Pickup_", menuName = "Pickup Setup", order = 51)] | |
public class PickupSetup : PooledObjectSetup | |
{ | |
public string PickupName; | |
public int PickupMin = 1; | |
public int PickupMax = 3; | |
public PickupTypes PickupType; | |
public AudioClip PickupSound; | |
public Sprite PickupIcon; | |
} |
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 UnityEngine; | |
using UnityEngine.Pool; | |
public abstract class PooledObjectBase : MonoBehaviour | |
{ | |
protected PooledObjectSetup ObjectSetup; | |
private IObjectPool<PooledObjectBase> objectPool; | |
public virtual void BeforeDisable() { } // called when object returned to pool | |
public virtual void BeforeDestroy() { } // called when object deleted from pool | |
public virtual void InitializeObjectSetup() { } // called when object created in pool | |
public virtual void BeforeEnable() { } // called when object taken from pool | |
// called when pooled object first created | |
public void AssignObjectSetup(PooledObjectSetup newSetup, IObjectPool<PooledObjectBase> newPool) | |
{ | |
ObjectSetup = newSetup; | |
objectPool = newPool; | |
} | |
// called to return object to pool | |
public void ReleaseToPool() | |
{ | |
StopAllCoroutines(); // this might not be necessary, SetActive(false) automatically does this | |
// only reason is if we want the coroutine to stop mid loop | |
try | |
{ | |
// try/catch in case we try to recycle the same enemy twice | |
objectPool.Release(this); | |
} | |
catch (Exception e) | |
{ | |
// ignored | |
} | |
} | |
} |
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.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.Pool; | |
public abstract class PooledObjectSetup : ScriptableObject | |
{ | |
[SerializeField] private PooledObjectBase basePrefab; | |
[SerializeField] private List<GameObject> modelPrefabs; | |
private IObjectPool<PooledObjectBase> objectPool; | |
private Transform poolOrganizer; | |
private GameObject masterParent; | |
private void OnEnable() | |
{ | |
objectPool = new ObjectPool<PooledObjectBase>(CreatePooledObject, OnTakeObjectFromPool, OnObjectReturnedToPool, | |
OnDestroyObject); | |
} | |
private void OnTakeObjectFromPool(PooledObjectBase pooledObject) | |
{ | |
if (!pooledObject) return; // sometimes the pool destroys an object before we can grab it... | |
pooledObject.BeforeEnable(); | |
pooledObject.gameObject.SetActive(true); | |
// then OnEnable() runs | |
} | |
private void OnObjectReturnedToPool(PooledObjectBase pooledObject) | |
{ | |
if (!pooledObject) return; // sometimes the pool destroys an object before we can return it... | |
pooledObject.BeforeDisable(); | |
pooledObject.gameObject.SetActive(false); | |
// then OnDisable() runs | |
} | |
private void OnDestroyObject(PooledObjectBase pooledObject) | |
{ | |
if (!pooledObject) return; // sometimes the pool destroys an object before we can return it... | |
pooledObject.BeforeDestroy(); | |
Destroy(pooledObject.gameObject); | |
// then OnDestroy() runs | |
} | |
private void CheckPoolOrganizer() | |
{ | |
if (poolOrganizer) return; | |
var poolOrganizerGO = new GameObject("Pool: " + this.name); | |
poolOrganizer = poolOrganizerGO.transform; | |
if (!masterParent) masterParent = GameObject.Find("Pools"); | |
if (!masterParent) masterParent = new GameObject("Pools"); | |
poolOrganizer.parent = masterParent.transform; | |
} | |
private PooledObjectBase CreatePooledObject() | |
{ | |
var newObject = Instantiate(basePrefab); | |
newObject.AssignObjectSetup(this, objectPool); // assign our type object setup | |
newObject.InitializeObjectSetup(); // run whatever init code this object has using its setup | |
if (modelPrefabs.Count > 0) | |
{ | |
// bring in a random model from our list | |
var model = Instantiate(modelPrefabs.RandomItem(), newObject.transform); | |
model.name = "Model"; | |
} | |
CheckPoolOrganizer(); | |
newObject.transform.parent = poolOrganizer; | |
newObject.name = this.name; | |
return newObject; | |
} | |
public PooledObjectBase GetPooledObject(Vector3 position, Transform parent = null) | |
{ | |
PooledObjectBase newObject; | |
do | |
{ | |
newObject = objectPool.Get(); | |
} while (!newObject); // ensure we actually get an object from the pool | |
newObject.transform.position = position; | |
if (parent) newObject.transform.parent = parent; | |
//newObject.Initialize(this); | |
return newObject; | |
} | |
} | |
public static class Extensions | |
{ | |
public static T RandomItem<T>(this T[] array) | |
{ | |
return array.Length == 0 ? default : array[Random.Range(0, array.Length)]; | |
} | |
public static T RandomItem<T>(this IList<T> array) | |
{ | |
return array.Count == 0 ? default : array[Random.Range(0, array.Count)]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment