unity3d
Object pooling
Zoeken…
Object zwembad
Soms moet je bij het maken van een game steeds weer een heleboel objecten van hetzelfde type maken en vernietigen. Je kunt dit eenvoudig doen door een prefab te maken en dit te instantiëren / vernietigen wanneer je dit nodig hebt. Dit is echter inefficiënt en kan je spel vertragen.
Een manier om dit probleem te omzeilen is het poolen van objecten. In feite betekent dit dat u een pool (met of zonder een beperking van de hoeveelheid) objecten die u gaat hergebruiken wanneer u kunt om onnodig instantiëren of vernietigen hebt.
Hieronder ziet u een voorbeeld van een eenvoudige objectpool
public class ObjectPool : MonoBehaviour
{
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
void Start()
{
if (populateOnStart && prefab != null && amount > 0)
{
for (int i = 0; i < amount; i++)
{
var instance = Instantiate(Prefab);
instance.SetActive(false);
pool.Add(instance);
}
}
}
public GameObject Instantiate (Vector3 position, Quaternion rotation)
{
foreach (var item in pool)
{
if (!item.activeInHierarchy)
{
item.transform.position = position;
item.transform.rotation = rotation;
item.SetActive( true );
return item;
}
}
if (growOverAmount)
{
var instance = (GameObject)Instantiate(prefab, position, rotation);
pool.Add(instance);
return instance;
}
return null;
}
}
Laten we eerst de variabelen bekijken
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
-
GameObject prefab
: dit is het prefab dat deGameObject prefab
zal gebruiken om nieuwe objecten in de pool te instantiëren. -
int amount
: dit is het maximale aantal items dat in de pool kan zijn. Als u een ander item wilt instantiëren en de pool zijn limiet al heeft bereikt, wordt een ander item uit de pool gebruikt. -
bool populateOnStart
: je kunt ervoor kiezen om de pool bij het opstarten te vullen of niet. Als je dit doet, wordt de pool gevuld met instanties van de prefab, zodat je de eerste keer dat jeInstantiate
belt, een al bestaand object krijgt -
bool growOverAmount
: Door dit op true in te stellen, kan de pool groeien wanneer binnen een bepaald tijdsbestek meer dan het bedrag wordt gevraagd. Je kunt niet altijd nauwkeurig het aantal items voorspellen dat je in je pool plaatst, dus dit zal meer aan je pool toevoegen als dat nodig is. -
List<GameObject> pool
: dit is de pool, de plaats waar al uw geïnstantieerde / vernietigde objecten worden opgeslagen.
Laten we nu eens kijken naar de Start
functie
void Start()
{
if (populateOnStart && prefab != null && amount > 0)
{
for (int i = 0; i < amount; i++)
{
var instance = Instantiate(Prefab);
instance.SetActive(false);
pool.Add(instance);
}
}
}
In de startfunctie controleren we of we de lijst bij het opstarten moeten invullen en doen we of het prefab
is ingesteld en het aantal groter is dan 0 (anders zouden we voor onbepaalde tijd creëren).
Dit is gewoon een eenvoudige manier om nieuwe objecten te instantiëren en in het zwembad te plaatsen. Een ding om op te letten is dat we alle instanties op inactief zetten. Op deze manier zijn ze nog niet zichtbaar in het spel.
Vervolgens is er de functie Instantiate
, waar de meeste magie gebeurt
public GameObject Instantiate (Vector3 position, Quaternion rotation)
{
foreach (var item in pool)
{
if (!item.activeInHierarchy)
{
item.transform.position = position;
item.transform.rotation = rotation;
item.SetActive(true);
return item;
}
}
if (growOverAmount)
{
var instance = (GameObject)Instantiate(prefab, position, rotation);
pool.Add(instance);
return instance;
}
return null;
}
De Instantiate
functie lijkt op Unity's eigen Instantiate
functie, behalve dat het prefab hierboven al als lid van de klas is opgegeven.
De eerste stap van de functie Instantiate
is controleren of er momenteel een inactief object in de pool staat. Dit betekent dat we dat object kunnen hergebruiken en terug kunnen geven aan de aanvrager. Als er een inactief object in de pool is, stellen we de positie en de rotatie in, stellen we dat deze actief is (anders kan het per ongeluk worden hergebruikt als u het vergeet te activeren) en terugsturen naar de aanvrager.
De tweede stap gebeurt alleen als er geen inactieve items in de pool zijn en de pool over de oorspronkelijke hoeveelheid mag groeien. Wat er gebeurt is eenvoudig: er wordt een ander exemplaar van het prefab gemaakt en aan de pool toegevoegd. Door de groei van het zwembad toe te staan, kunt u de juiste hoeveelheid objecten in het zwembad hebben.
De derde "stap" gebeurt alleen als er geen inactieve items in de pool zijn en de pool niet mag groeien. Wanneer dit gebeurt, ontvangt de aanvrager een null GameObject, wat betekent dat er niets beschikbaar was en correct moet worden behandeld om NullReferenceExceptions
te voorkomen.
Belangrijk!
Om ervoor te zorgen dat je items terug in het zwembad komen, moet je de spelobjecten niet vernietigen. Het enige dat u hoeft te doen, is ze inactief te maken en dat maakt ze beschikbaar voor hergebruik door het zwembad.
Eenvoudig object pool
Hieronder ziet u een voorbeeld van een objectpool waarmee een bepaald objecttype kan worden gehuurd en geretourneerd. Om de objectpool te maken, zijn een Func voor de create-functie en een actie om het object te vernietigen vereist om de gebruiker flexibiliteit te bieden. Bij het aanvragen van een object wanneer de pool leeg is, wordt een nieuw object gemaakt en bij het aanvragen wanneer de pool objecten heeft, worden objecten uit de pool verwijderd en teruggestuurd.
Object zwembad
public class ResourcePool<T> where T : class
{
private readonly List<T> objectPool = new List<T>();
private readonly Action<T> cleanUpAction;
private readonly Func<T> createAction;
public ResourcePool(Action<T> cleanUpAction, Func<T> createAction)
{
this.cleanUpAction = cleanUpAction;
this.createAction = createAction;
}
public void Return(T resource)
{
this.objectPool.Add(resource);
}
private void PurgeSingleResource()
{
var resource = this.Rent();
this.cleanUpAction(resource);
}
public void TrimResourcesBy(int count)
{
count = Math.Min(count, this.objectPool.Count);
for (int i = 0; i < count; i++)
{
this.PurgeSingleResource();
}
}
public T Rent()
{
int count = this.objectPool.Count;
if (count == 0)
{
Debug.Log("Creating new object.");
return this.createAction();
}
else
{
Debug.Log("Retrieving existing object.");
T resource = this.objectPool[count-1];
this.objectPool.RemoveAt(count-1);
return resource;
}
}
}
Voorbeeld gebruik
public class Test : MonoBehaviour
{
private ResourcePool<GameObject> objectPool;
[SerializeField]
private GameObject enemyPrefab;
void Start()
{
this.objectPool = new ResourcePool<GameObject>(Destroy,() => Instantiate(this.enemyPrefab) );
}
void Update()
{
// To get existing object or create new from pool
var newEnemy = this.objectPool.Rent();
// To return object to pool
this.objectPool.Return(newEnemy);
// In this example the message 'Creating new object' should only be seen on the frame call
// after that the same object in the pool will be returned.
}
}
Nog een eenvoudige objectpool
Een ander voorbeeld: een wapen dat kogels afschiet.
Het wapen fungeert als een objectpool voor de opsommingstekens die het maakt.
public class Weapon : MonoBehaviour {
// The Bullet prefab that the Weapon will create
public Bullet bulletPrefab;
// This List is our object pool, which starts out empty
private List<Bullet> availableBullets = new List<Bullet>();
// The Transform that will act as the Bullet starting position
public Transform bulletInstantiationPoint;
// To spawn a new Bullet, this method either grabs an available Bullet from the pool,
// otherwise Instantiates a new Bullet
public Bullet CreateBullet () {
Bullet newBullet = null;
// If a Bullet is available in the pool, take the first one and make it active
if (availableBullets.Count > 0) {
newBullet = availableBullets[availableBullets.Count - 1];
// Remove the Bullet from the pool
availableBullets.RemoveAt(availableBullets.Count - 1);
// Set the Bullet's position and make its GameObject active
newBullet.transform.position = bulletInstantiationPoint.position;
newBullet.gameObject.SetActive(true);
}
// If no Bullets are available in the pool, Instantiate a new Bullet
else {
newBullet newObject = Instantiate(bulletPrefab, bulletInstantiationPoint.position, Quaternion.identity);
// Set the Bullet's Weapon so we know which pool to return to later on
newBullet.weapon = this;
}
return newBullet;
}
}
public class Bullet : MonoBehaviour {
public Weapon weapon;
// When Bullet collides with something, rather than Destroying it, we return it to the pool
public void ReturnToPool () {
// Add Bullet to the pool
weapon.availableBullets.Add(this);
// Disable the Bullet's GameObject so it's hidden from view
gameObject.SetActive(false);
}
}