Unitys ObjectPool
Teil des Abschnitts Spielarchitektur in Unity der Unity-Journey von Coddy. Lektion 39 von 50.
Unity enthält einen Pool, ObjectPool<T> in UnityEngine.Pool. Du legst mit vier Lambdas fest, was bei jedem Schritt passieren soll, und der Pool verwaltet die Objekte und ihre Anzahl. Dieser Schornstein stößt alle 0.2 Sekunden eine Rauchwolke aus, und jede Wolke wird nach einer Sekunde zurückgegeben:
using UnityEngine;
using UnityEngine.Pool;
public class Chimney : MonoBehaviour
{
public Puff puffPrefab;
private ObjectPool<Puff> pool;
private float timer;
void Awake()
{
pool = new ObjectPool<Puff>(
createFunc: () => Instantiate(puffPrefab),
actionOnGet: puff => puff.gameObject.SetActive(true),
actionOnRelease: puff => puff.gameObject.SetActive(false),
actionOnDestroy: puff => Destroy(puff.gameObject),
collectionCheck: true,
defaultCapacity: 10,
maxSize: 20);
}
void Update()
{
timer += Time.deltaTime;
if (timer >= 0.2f)
{
timer = 0f;
Puff puff = pool.Get();
puff.transform.position = transform.position + Vector3.up;
puff.Launch(pool);
}
}
}// Puff.cs
private IObjectPool<Puff> pool;
private float age;
public void Launch(IObjectPool<Puff> owner)
{
pool = owner;
age = 0f;
}
void Update()
{
transform.Translate(0f, Time.deltaTime, 0f);
age += Time.deltaTime;
if (age >= 1f) pool.Release(this);
}createFunc wird ausgeführt, wenn Get den Pool leer vorfindet; actionOnGet wird bei jedem Get und actionOnRelease bei jedem Release ausgeführt. Wird eine Rauchwolke freigegeben, während bereits maxSize Objekte im Pool warten, behält der Pool sie nicht: actionOnDestroy zerstört sie. defaultCapacity ist lediglich die anfängliche Größe der Liste des Pools, es wird also nichts im Voraus erstellt. CountAll zählt die Rauchwolken, die der Pool erstellt hat und noch besitzt, CountActive diejenigen, die sich im Spiel befinden, und CountInactive die übrigen Objekte. Get aktiviert die Rauchwolke, bevor der Schornstein sie positioniert. Das ist hier in Ordnung, weil Puff kein OnEnable hat, das ihre Position ausliest. Die Rauchwolke speichert den Pool als IObjectPool<Puff>, der Schnittstelle, die jeder Unity-Pool implementiert, und Launch setzt ihr Alter bei jeder Verwendung zurück.
Ist collectionCheck aktiviert, löst die Freigabe einer Rauchwolke, die sich bereits im Pool befindet, eine InvalidOperationException aus: Trying to release an object that has already been released to the pool. Die Prüfung wird nur im Editor ausgeführt, da sie bei jedem Release die übrigen Objekte durchsucht; in einem Build läge die Rauchwolke zweimal im Pool, und zwei Gets würden dasselbe Objekt ausgeben. Eine häufige Ursache ist ein Objekt, das nach seiner Freigabe weiterläuft und sich erneut freigibt. Deshalb deaktiviert actionOnRelease es.
Aufgabe
MittelFeuerwerk. Bei jedem Druck auf die Leertaste schießen 8 Funken aus dem Abschussgerät, und jeder Spark ruft Return(this) des Abschussgeräts auf, wenn seine Lebensdauer von 1 Sekunde abgelaufen ist (Spark ist bereit). Heute instanziiert und zerstört das Abschussgerät jeden Funken. Verwende Unitys Pool:
- Erstelle in
Launcher.csin Awake einenObjectPool<Spark>. Er erstellt einen Funken, indem ersparkPrefabinstanziiert, aktiviert einen Funken, wenn er entnommen wird, und deaktiviert ihn, wenn er zurückkommt. Wenn ein Funke freigegeben wird, zerstört er dessen Objekt. - Der Pool behält höchstens 5 Ersatzfunken, also warten nach einem Ausbruch von 8 Funken 5, und 3 werden zerstört.
- Mit der Leertaste werden die 8 Funken aus dem Pool entnommen, anstatt sie zu instanziieren, und
Returngibt den Funken an den Pool zurück, anstatt ihn zu zerstören.
Drücke Play, löse einige Ausbrüche aus und klicke dann auf Check.
Probier es selbst
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;
public class Launcher : MonoBehaviour
{
public Spark sparkPrefab;
public int sparks = 8;
void Awake()
{
// Erstelle den Pool: Erzeugen, Entnehmen, Zurückgeben und Zerstören, mit höchstens 5 ungenutzten Funken
}
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
for (int i = 0; i < sparks; i++)
{
Spark spark = Instantiate(sparkPrefab);
spark.transform.position = transform.position + Vector3.up;
spark.Launch(Direction(i), this);
}
}
}
// Ein Funke, dessen Lebensdauer abgelaufen ist
public void Return(Spark spark)
{
Destroy(spark.gameObject);
}
// Nach außen und oben, gleichmäßig um den Launcher verteilt
Vector3 Direction(int i)
{
return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Spielarchitektur in Unity
1Komponenten statt Hierarchien
Kleine KomponentenEin Basisskript für GegnerAbstrakte KomponentenKomponenten im ZusammenspielRückblick: Modularer Geschützturm4Serialisierte Daten
Serialisierbare KlassenListen im InspectorEnums im InspectorRückblick: Loot-Tabelle7Zustandsautomaten
Zustände als KlassenEin ZustandsautomatÜbergängeGegner-KIWiederholung: Türzustände10Projekt: Arena-Überlebenskampf
Der HeldGegnertypen2Interfaces in Unity
IDamageableInterfaces und TryGetComponentGetComponentsRückblick: Interaktionstaste5ScriptableObjects
Daten-AssetsWaffenwerteGemeinsame EinstellungenWellen als DatenRückblick: Item-Datenbank8Wiederverwendbarer Code
ErweiterungsmethodenEin generisches SingletonGenerische HilfsmethodenRückblick: FindNearest