📜 ⬆️ ⬇️

Simple pool of objects in Unity3D

During the development process, I faced the need to create a pool of objects. After reading this and other articles, I decided to write a simpler pool for my needs with access to the object by line (prefab name).

So, let's begin. The pool consists of four scripts. The on / off state on an object in a pool is determined by its Unity activeInHierarchy property, so as not to fence additional variables.

1. Pool Object


The Pool Object component must reside on any object used in the pool. Its main purpose is to return the object back to the pool.

using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/PoolObject")] public class PoolObject : MonoBehaviour { #region Interface public void ReturnToPool () { gameObject.SetActive (false); } #endregion } 

The class has only one method. In fact, one could do without it, but in this way we separate the objects intended for the pool and not (destroyed in the usual way).
')

2. Object Pooling


Go ahead. The Object Pooling class is the pool itself, which gives free objects on demand and creates new ones when there is a shortage.

 using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/ObjectPooling")] public class ObjectPooling { #region Data List<PoolObject> objects; Transform objectsParent; #endregion 

Here objects are all objects contained in the pool, objectsParent is used only as their parent in the hierarchy on the scene (so that there is no sheet of objects).

Adding is done using the AddObject method, which takes a sample that needs to be added and a parent in the hierarchy on the scene.

 void AddObject(PoolObject sample, Transform objects_parent) { GameObject temp = GameObject.Instantiate(sample.gameObject); temp.name = sample.name; temp.transform.SetParent (objects_parent); objects.Add(temp.GetComponent<PoolObject> ()); if (temp.GetComponent<Animator> ()) temp.GetComponent<Animator> ().StartPlayback (); temp.SetActive(false); } 

A Gameobject temp is created, a sample name is assigned to it, after which it is added to our List. The object is then turned off until it is “required” outside.

Separately about the lines:

  if (temp.GetComponent<Animator> ()) temp.GetComponent<Animator> ().StartPlayback (); 

They were introduced, because when creating an object without them, the animator did not start (the update did not have time to call). As a result, when at the start of the scene it was created, for example, 100 bullets and immediately turned off, when 50 bullets were requested, the animator started at all at the same time and the FPS subsidence began (on many objects the animation is constantly playing). If the project does not involve the use of a large number of objects with animators, this code is not needed.

Consider initialization:

  public void Initialize (int count, PoolObject sample, Transform objects_parent) { objects = new List<PoolObject> (); // List objectsParent = objects_parent; //      for (int i=0; i<count; i++) { AddObject(sample, objects_parent); //     } } 

The second method of this class is GetObject (), which returns a Gameobject:

  public PoolObject GetObject () { for (int i=0; i<objects.Count; i++) { if (objects[i].gameObject.activeInHierarchy==false) { return objects[i]; } } AddObject(objects[0], objectsParent); return objects[objects.Count-1]; } 

The logic is simple - we pass through the sheet, if any of the objects in the pool is turned off (i.e., free) - we return it, otherwise we add a new one.

3. PoolManager


The following PoolManager class manages pools of various objects. Class static to simplify access to objects, i.e. no need to create singletons, instances and so on.

 using UnityEngine; using System.Collections; using System.Collections.Generic; public static class PoolManager{ private static PoolPart[] pools; private static GameObject objectsParent; [System.Serializable] public struct PoolPart { public string name; //  public PoolObject prefab; // ,   public int count; //     public ObjectPooling ferula; //  } 

All information is stored in the PoolPart structure.

Initialization is performed by an array of these structures: (ferula, perhaps not a very good name, but it allows you not to get lost in the pool):

  public static void Initialize(PoolPart[] newPools) { pools = newPools; //  objectsParent = new GameObject (); objectsParent.name = "Pool"; //    Pool,     for (int i=0; i<pools.Length; i++) { if(pools[i].prefab!=null) { pools[i].ferula = new ObjectPooling(); //      pools[i].ferula.Initialize(pools[i].count, pools[i].prefab, objectsParent.transform); //     } } } 

The second method of this static class is GetObject, an analogue of the standard Instantiate, but by the name of the object. It checks all existing pools, and if it finds the correct one, it pulls its GetObject () method from the ObjectPooling class:

  public static GameObject GetObject (string name, Vector3 position, Quaternion rotation) { GameObject result = null; if (pools != null) { for (int i = 0; i < pools.Length; i++) { if (string.Compare (pools [i].name, name) == 0) { //       result = pools[i].ferula.GetObject ().gameObject; //    result.transform.position = position; result.transform.rotation = rotation; result.SetActive (true); //    return result; } } } return result; //     ,  null } 

4. PoolSetup


However, you must edit the objects intended for use in the pool, and their number, in the Unity inspector. To do this, you have to write a wrapper class, the heir of MonoBehaviour, which hangs on objects:

 using UnityEngine; using System.Collections; [AddComponentMenu("Pool/PoolSetup")] public class PoolSetup : MonoBehaviour {//     PoolManager #region Unity scene settings [SerializeField] private PoolManager.PoolPart[] pools; //,            #endregion #region Methods void OnValidate() { for (int i = 0; i < pools.Length; i++) { pools[i].name = pools[i].prefab.name; //  ,   } } void Awake() { Initialize (); } void Initialize () { PoolManager.Initialize(pools); //   } #endregion } 

This class must be one on stage, otherwise one of them will overwrite the pools of the other.

Using
Now we can call objects from the pool like this:

 Gameobject bullet = PoolManager.GetObject (bulletPrefab.name, shotPoint.position, myTransform.rotation); 

We return:

 GetComponent<PoolObject>().ReturnToPool (); 

As a result, the pool works and is fairly simple to use. A couple of screenshots:

Management in the editor:



Spawn bullets and ships:



Afterword


Of course, this implementation has many drawbacks. I will list the main ones:

1) Access by string can be replaced by access by, for example, an integer key identifier, which would speed up the work;
2) There is no error handling and exceptions (the methods will simply return null), almost no checks;
3) The need for a PoolSetup singleton on stage, although no one refers to it.

Update: GitHub repository

Full code


PoolObject
 using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/PoolObject")] public class PoolObject : MonoBehaviour { #region Interface public void ReturnToPool () { gameObject.SetActive (false); } #endregion } 


Object pooling

 using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/ObjectPooling")] public class ObjectPooling { #region Data List<PoolObject> objects; Transform objectsParent; #endregion #region Interface public void Initialize (int count, PoolObject sample, Transform objects_parent) { objects = new List<PoolObject> (); objectsParent = objects_parent; for (int i=0; i<count; i++) { AddObject(sample, objects_parent); } } public PoolObject GetObject () { for (int i=0; i<objects.Count; i++) { if (objects[i].gameObject.activeInHierarchy==false) { return objects[i]; } } AddObject(objects[0], objectsParent); return objects[objects.Count-1]; } #endregion #region Methods void AddObject(PoolObject sample, Transform objects_parent) { GameObject temp; temp = GameObject.Instantiate(sample.gameObject); temp.name = sample.name; temp.transform.SetParent (objects_parent); objects.Add(temp.GetComponent<PoolObject> ()); if (temp.GetComponent<Animator> ()) temp.GetComponent<Animator> ().StartPlayback (); temp.SetActive(false); } #endregion } 


Poolmanager
 using UnityEngine; using System.Collections; using System.Collections.Generic; public static class PoolManager{ private static PoolPart[] pools; private static GameObject objectsParent; [System.Serializable] public struct PoolPart { public string name; public PoolObject prefab; public int count; public ObjectPooling ferula; } public static void Initialize(PoolPart[] newPools) { pools = newPools; objectsParent = new GameObject (); objectsParent.name = "Pool"; for (int i=0; i<pools.Length; i++) { if(pools[i].prefab!=null) { pools[i].ferula = new ObjectPooling(); pools[i].ferula.Initialize(pools[i].count, pools[i].prefab, objectsParent.transform); } } } public static GameObject GetObject (string name, Vector3 position, Quaternion rotation) { GameObject result = null; if (pools != null) { for (int i = 0; i < pools.Length; i++) { if (string.Compare (pools [i].name, name) == 0) { result = pools[i].ferula.GetObject ().gameObject; result.transform.position = position; result.transform.rotation = rotation; result.SetActive (true); return result; } } } return result; } } 


Poolsetup
 using UnityEngine; using System.Collections; [AddComponentMenu("Pool/PoolSetup")] public class PoolSetup : MonoBehaviour {//     PoolManager #region Unity scene settings [SerializeField] private PoolManager.PoolPart[] pools; #endregion #region Methods void OnValidate() { for (int i = 0; i < pools.Length; i++) { pools[i].name = pools[i].prefab.name; } } void Awake() { Initialize (); } void Initialize () { PoolManager.Initialize(pools); } #endregion } 

Source: https://habr.com/ru/post/275091/


All Articles