Search Unity

Resolved Generic Object Pool

Discussion in 'Getting Started' started by SUPERIONtheKnight, Dec 4, 2021.

  1. SUPERIONtheKnight

    SUPERIONtheKnight

    Joined:
    Jan 29, 2020
    Posts:
    2
    Hello! I am relatively new to the game dev/programming scene, and I'm struggling with making further progress. I am using Unity 2021.2.4f1 on Ubuntu Linux.

    So to put things simply, I have been learning about how to make object pools work. They are to my knowledge, a much better way to handle multiple gameObjects. They stop the garbage collector from kicking in and doing its thing(perfect for a weapon that shoots bullets). What I needed was a way to generate "bullets" for any player/enemy to use.

    I actually succeeded here, and managed to make a script that seems to do an ok job with accomplishing this. Here is the script if you are curious:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class BulletObjectPool : MonoBehaviour {
    5.     public List<GameObject> pooledBullets;
    6.     public GameObject bulletToPool;
    7.     public int amountToPool;
    8.  
    9.     private void Start() {
    10.         pooledBullets = new List<GameObject>();
    11.         GameObject tmp;
    12.         for (int i = 0; i < amountToPool; i++) {
    13.             tmp = Instantiate(bulletToPool);
    14.             tmp.SetActive(false);
    15.             pooledBullets.Add(tmp);
    16.         }
    17.     }
    18.  
    19.     public GameObject GetFreeBullet() {
    20.         for (int i = 0; i < amountToPool; i++) {
    21.             if (!pooledBullets[i].activeInHierarchy) {
    22.                 return pooledBullets[i];
    23.             }
    24.         }
    25.         return null;
    26.     }
    27. }

    Great! I figured it out, but I'm still not happy with it. For starters, what if I want/need to make more than just bullets in the future(maybe I want a horde of Zombies to fall in a pit or something? :p)? To my knowledge, I would essentially have to duplicate this script for every kind of new gameObject/prefab I want to work with, which is not ideal.

    So I went to the internet and found that Unity recently introduced the new concept of "generic object pools". This sounded exactly like what I was looking for! As such, I familiarized myself with how they work, and managed to get it working somehow(Really cool functionality btw :)).

    Now I thought I could take this a step further, and get it working on a separate script so I could call something like "GetGameObject()", or "ReleaseGameObject()"(similar to how you you would use "Instantiate()", or "Destroy()".). If I could do this, then that could save me a lot of time as I wouldn't even have to think about writing all the other stuff. Just drop a GenericObjectPool script onto my player gameObject, then call the needed methods from my PlayerController script.

    That was the idea anyways, but I just can't get it to fully work. Here is the code that I currently have for 3 different scripts. Note that a lot of code was removed from the PlayerController script to help make this post shorter(still long though). Also Both "GameObjectPool.cs"/"PlayerController.cs" are on the player gameObject, while "Bullet.cs" is on any Bullet gameObject.

    Code (CSharp):
    1. //GameObjectPool.cs
    2. using UnityEngine;
    3. using UnityEngine.Pool;
    4. public interface IGameObjectPool {
    5.     GameObject GetGameObject();
    6.     GameObject ReleaseGameObject();
    7.     GameObject DestroyGameObject();
    8.  
    9. }
    10. public class GameObjectPool : MonoBehaviour, IGameObjectPool {
    11.     [SerializeField] private GameObject _objectInPool;
    12.     private ObjectPool<GameObject> _pool;
    13.     private void Awake() => _pool = new ObjectPool<GameObject>(InstantiateGameObject, EnableGameObject, DisableGameObject, DisposeGameObject, false, 10, 10);
    14.     private GameObject InstantiateGameObject() {
    15.         var objectInPool = Instantiate(_objectInPool);
    16.         return objectInPool;
    17.     }
    18.     private void EnableGameObject(GameObject obj) {
    19.         obj.SetActive(true);
    20.     }
    21.     private void DisableGameObject(GameObject obj) {
    22.         obj.SetActive(false);
    23.     }
    24.     private void DisposeGameObject(GameObject obj) {
    25.         Destroy(obj);
    26.     }
    27.  
    28.     public GameObject GetGameObject() {
    29.         _pool.Get();
    30.         return _objectInPool;
    31.     }
    32.     public GameObject ReleaseGameObject() {
    33.         Debug.Log("test");
    34.         _pool.Release(_objectInPool);
    35.         return _objectInPool;
    36.     }
    37.     public GameObject DestroyGameObject() {
    38.         _pool.Dispose();
    39.         return _objectInPool;
    40.     }
    41. }
    Code (CSharp):
    1. //PlayerController.cs
    2. using UnityEngine;
    3. using UnityEngine.InputSystem;
    4.  
    5. public class PlayerController : MonoBehaviour {
    6.     public short xDirection = 1;
    7.     private float getFireWeaponCooldown;
    8.     private bool fireWeapon = false;
    9.     private Rigidbody rb;
    10.     private PlayerInput controls;
    11.     [SerializeField] private float fireWeaponCooldown = 0.5f;
    12.     private IGameObjectPool iGameObjectPool;
    13.  
    14.     private void OnEnable() {
    15.         controls.Player.Enable();
    16.     }
    17.     private void OnDisable() {
    18.         controls.Player.Disable();
    19.     }
    20.     private void Awake() {
    21.         controls = new PlayerInput();
    22.      
    23.         controls.Player.Fire.performed += context => fireWeapon = true;
    24.         controls.Player.Fire.canceled += context => fireWeapon = false;
    25.     }
    26.     private void Start() {
    27.         rb = GetComponent<Rigidbody>();
    28.      
    29.         getFireWeaponCooldown = fireWeaponCooldown;
    30.      
    31.         iGameObjectPool = gameObject.GetComponent<IGameObjectPool>();
    32.     }
    33.     private void FixedUpdate() {
    34.         Shoot();
    35.     }
    36.     private void Shoot() {
    37.         bool shoot = false;
    38.         if (getFireWeaponCooldown > 0.0f) {
    39.             getFireWeaponCooldown -= Time.fixedDeltaTime;
    40.         } else if (getFireWeaponCooldown <= 0.0f && fireWeapon) {
    41.             shoot = true;
    42.             getFireWeaponCooldown = fireWeaponCooldown;
    43.         }
    44.         if (shoot == true) {
    45.             GameObject bullet = iGameObjectPool?.GetGameObject();
    46.             if (bullet) {
    47.                 bullet.transform.position = transform.position;
    48.                 bullet.GetComponent<Bullet>().speed *= GetComponent<PlayerController>().xDirection;
    49.             }
    50.         }
    51.     }
    52. }
    Code (CSharp):
    1. //Bullet.cs
    2. using UnityEngine;
    3.  
    4. public class Bullet : MonoBehaviour {
    5.     public float speed, setBulletTimer = 15.0f;
    6.     [SerializeField] private float setSpeed = 25.0f;
    7.     [SerializeField] private LayerMask floor;
    8.     private float bulletTimer;
    9.     private Rigidbody rb;
    10.     private Vector3 previousPosition;
    11.     IGameObjectPool iGameObjectPool;
    12.  
    13.     private void OnEnable() {
    14.         speed = setSpeed;
    15.         bulletTimer = setBulletTimer;
    16.     }
    17.     private void Start() {
    18.         iGameObjectPool = gameObject.GetComponent<GameObjectPool>();
    19.         rb = GetComponent<Rigidbody>();
    20.         previousPosition = gameObject.transform.position;
    21.      
    22.         rb.velocity = new Vector3(speed, rb.velocity.y, rb.velocity.z);
    23.     }
    24.     private void FixedUpdate() {
    25.         GameObject bullet = this.gameObject;
    26.         if (bulletTimer > 0) {
    27.             bulletTimer -= Time.fixedDeltaTime;
    28.         } else {
    29.             iGameObjectPool?.ReleaseGameObject();
    30.         }
    31.      
    32.        RaycastHit[] detected = Physics.RaycastAll(previousPosition, (transform.position - previousPosition).normalized, (transform.position - previousPosition).magnitude, floor);
    33.      
    34.         for(int i = 0; i < detected.Length; i++) {
    35.             Debug.Log(detected[i].collider.gameObject.name);
    36.         }
    37.         Debug.DrawLine(gameObject.transform.position, previousPosition);
    38.      
    39.         previousPosition = gameObject.transform.position;
    40.     }
    41. }
    42.  

    Now, "GameObject bullet = iGameObjectPool?.GetGameObject();" on line 45 in "PlayerController.cs" seems to work just fine(although I would like to pass through a gameObject if possible). What isn't working is my "ReleaseGameObject()" method on line 29 in "Bullet.cs", and I have no idea why. I have tried many things, and none of them seem to work. One thing worth pointing out is I am also pretty new to using interfaces, so I'm not 100% sure if I'm using it correctly. Also I have yet to test my DestroyGameObject() method, so that might not even work.

    So now my question, what am I doing wrong here? Am I overthinking this? Any help whether it be a super detailed post, or a simple link to push me in the right direction would be much appreciated!

    Thank you very much for reading this rather large post of mine! :)
     
    Last edited: Dec 4, 2021
  2. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    No need to, really. Your original script is already abstract enough to handle pooling whatever types of GameObjects you'd like.
    You may have named it "BulletObjectPool", but it's not pooling "bullets" specifically, it's pooling GameObjects, and a GameObject could be whatever you want it to be.

    All you'd really need to to is just rename all the "bullet"-related properties of the script...
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.    
    4. public class GameObjectPool : MonoBehaviour {
    5.     public GameObject prefab;
    6.     public List<GameObject> pooledObjects;
    7.     public int amountToPool;
    8.    
    9.     private void Start() {
    10.         pooledObjects = new List<GameObject>();
    11.  
    12.         for (int i = 0; i < amountToPool; i++) {
    13.             GameObject instance = Instantiate(prefab);
    14.             instance.SetActive(false);
    15.             pooledObjects.Add(instance);
    16.         }
    17.     }
    18.    
    19.     public GameObject GetFreeObject() {
    20.         for (int i = 0; i < pooledObjects.Count; i++) {
    21.             if (!pooledObjects[i].activeInHierarchy) {
    22.                 return pooledObjects[i];
    23.             }
    24.         }
    25.         return null;
    26.     }
    27. }
    ...And presto, you have a reusable GameObjectPool script. Whether the script pools "bullets" or "zombies" or whatever else you want depends on what GameObject prefab you assign to its "prefab" field.

    (On a side, note generics are actually a built-in feature of the C# language - not anything specific to Unity. You can even create your own custom generic types as well.)
     
    SUPERIONtheKnight likes this.
  3. SUPERIONtheKnight

    SUPERIONtheKnight

    Joined:
    Jan 29, 2020
    Posts:
    2
    This is what I was sort of thinking in the back of my head, but still wasn't completely sure if it was the right approach. Greatly appreciate the reply! It helps a lot! Now I know that I was simply over-complicating things... :)