unity3d
Objekt-Pooling
Suche…
Objektpool
Wenn Sie ein Spiel erstellen, müssen Sie manchmal immer wieder Objekte des gleichen Typs erstellen und zerstören. Sie können dies einfach tun, indem Sie ein Prefab erstellen und es instanziieren / zerstören, wann immer Sie dies benötigen. Dies ist jedoch ineffizient und kann Ihr Spiel verlangsamen.
Eine Möglichkeit, dieses Problem zu umgehen, ist das Pooling von Objekten. Im Grunde bedeutet dies, dass Sie einen Pool (mit oder ohne Begrenzung der Anzahl) von Objekten haben, die Sie wann immer möglich wiederverwenden können, um unnötige Instanziierung oder Zerstörung zu verhindern.
Unten sehen Sie ein Beispiel für einen einfachen Objektpool
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;
}
}
Lassen Sie uns zuerst die Variablen durchgehen
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
-
GameObject prefab
: Dies ist der Prefab, den der Objektpool verwendet, um neue Objekte in den Pool zu instantiieren. -
int amount
: Dies ist die maximale Anzahl von Elementen, die sich im Pool befinden können. Wenn Sie ein anderes Element instanziieren möchten und der Pool bereits sein Limit erreicht hat, wird ein anderes Element aus dem Pool verwendet. -
bool populateOnStart
: Sie können den Pool beim Startbool populateOnStart
oder nicht. Dadurch wird der Pool mit Instanzen des Prefab gefüllt, so dass Sie beim ersten Aufruf vonInstantiate
ein bereits vorhandenes Objekt erhalten -
bool growOverAmount
: Wenn Sie diesebool growOverAmount
auf true setzen, wächst der Pool immer dann, wenn in einem bestimmten Zeitraum mehr als der Betrag angefordert wird. Sie können die Anzahl der Elemente, die Sie in Ihren Pool aufnehmen möchten, nicht immer genau vorhersagen, sodass Sie bei Bedarf mehr Pool hinzufügen können. -
List<GameObject> pool
: Dies ist der Pool, der Ort, an dem alle instanziierten / zerstörten Objekte gespeichert werden.
Schauen wir uns nun die Start
Funktion an
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 der prefab
prüfen wir, ob die Liste beim Start prefab
soll und ob das prefab
festgelegt wurde und der Betrag größer als 0 ist (sonst würden wir unbegrenzt prefab
).
Dies ist nur eine einfache Methode, um neue Objekte zu instantiieren und in den Pool zu setzen. Zu beachten ist, dass wir alle Instanzen auf inaktiv setzen. Auf diese Weise sind sie noch nicht im Spiel sichtbar.
Als nächstes gibt es die Instantiate
Funktion, bei der die meiste Magie stattfindet
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;
}
Die Instantiate
Funktion sieht genauso aus wie Unitys eigene Instantiate
Funktion, es sei denn, das Prefab wurde bereits als Klassenmitglied bereitgestellt.
Der erste Schritt der Instantiate
Funktion besteht darin, zu prüfen, ob sich momentan ein inaktives Objekt im Pool befindet. Dies bedeutet, dass wir das Objekt wiederverwenden und dem Anforderer zurückgeben können. Wenn sich ein inaktives Objekt im Pool befindet, setzen wir die Position und die Rotation, setzen es auf aktiv (andernfalls könnte es versehentlich wiederverwendet werden, falls Sie es vergessen, es zu aktivieren) und an den Anforderer zurückgeben.
Der zweite Schritt erfolgt nur, wenn sich keine inaktiven Elemente im Pool befinden und der Pool den ursprünglichen Betrag überschreiten darf. Was passiert, ist einfach: Eine weitere Instanz des Prefab wird erstellt und dem Pool hinzugefügt. Wenn Sie das Wachstum des Pools zulassen, können Sie die richtige Anzahl von Objekten im Pool anzeigen.
Der dritte "Schritt" erfolgt nur, wenn sich keine inaktiven Elemente im Pool befinden und der Pool nicht vergrößert werden darf. In diesem Fall erhält der Anforderer ein ungültiges GameObject, was bedeutet, dass nichts verfügbar war und ordnungsgemäß behandelt werden sollte, um NullReferenceExceptions
zu verhindern.
Wichtig!
Um sicherzustellen, dass Ihre Gegenstände wieder in den Pool gelangen, sollten Sie die Spielobjekte nicht zerstören. Das einzige, was Sie tun müssen, ist, sie inaktiv zu setzen, wodurch sie für die Wiederverwendung durch den Pool verfügbar sind.
Einfacher Objektpool
Nachfolgend finden Sie ein Beispiel für einen Objektpool, in dem ein bestimmter Objekttyp gemietet und zurückgegeben werden kann. Um den Objektpool zu erstellen, sind eine Funktion für die Erstellungsfunktion und eine Aktion zum Zerstören des Objekts erforderlich, um dem Benutzer Flexibilität zu geben. Wenn ein Objekt angefordert wird, wenn der Pool leer ist, wird ein neues Objekt erstellt. Wenn der Pool Objekte enthält, werden Objekte aus dem Pool entfernt und zurückgegeben.
Objektpool
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;
}
}
}
Verwendungsbeispiel
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.
}
}
Ein weiterer einfacher Objektpool
Ein anderes Beispiel: Eine Waffe, die Kugeln abschießt.
Die Waffe fungiert als Objektpool für die von ihr erstellten Aufzählungszeichen.
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);
}
}