unity3d
Pula obiektów
Szukaj…
Pula obiektów
Czasami, kiedy tworzysz grę, musisz ciągle tworzyć i niszczyć wiele obiektów tego samego typu. Możesz to po prostu zrobić, tworząc prefabrykat i tworzyć go / niszczyć, kiedy tylko zajdzie taka potrzeba, jednak jest to nieefektywne i może spowolnić grę.
Jednym ze sposobów obejścia tego problemu jest łączenie obiektów. Zasadniczo oznacza to, że masz pulę (z ograniczeniem lub bez ograniczenia) obiektów, których zamierzasz ponownie użyć, gdy tylko możesz, aby zapobiec niepotrzebnemu tworzeniu lub niszczeniu.
Poniżej znajduje się przykład prostej puli obiektów
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;
}
}
Najpierw przejrzyjmy zmienne
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
-
GameObject prefab
: jest to prefabryk, którego użyje pula obiektów, aby utworzyć instancję nowych obiektów w puli. -
int amount
: Jest to maksymalna kwota przedmiotów, które mogą być w basenie. Jeśli chcesz utworzyć instancję innego elementu, a pula osiągnęła już limit, zostanie użyty inny element z puli. -
bool populateOnStart
: możesz zapełnić pulę przy starcie lub nie. Spowoduje to wypełnienie puli wystąpieniami prefabrykatu, dzięki czemu przy pierwszym wywołaniuInstantiate
otrzymasz już istniejący obiekt -
bool growOverAmount
: Ustawienie wartości true powoduje, że pula rośnie, ilekroć w określonym czasie zażąda się więcej niż kwota. Nie zawsze możesz dokładnie przewidzieć liczbę przedmiotów, które chcesz umieścić w puli, więc w razie potrzeby zwiększy to pulę. -
List<GameObject> pool
: jest to pula, miejsce, w którym przechowywane są wszystkie utworzone / zniszczone obiekty.
Sprawdźmy teraz funkcję Start
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);
}
}
}
W funkcji start sprawdzamy, czy powinniśmy wypełnić listę na starcie i robimy to, jeśli prefab
został ustawiony, a ilość jest większa niż 0 (w przeciwnym razie tworzylibyśmy w nieskończoność).
Jest to prosta pętla do tworzenia nowych obiektów i umieszczania ich w puli. Należy zwrócić uwagę na to, że wszystkie instancje są nieaktywne. W ten sposób nie są jeszcze widoczne w grze.
Następnie jest funkcja Instantiate
, w której dzieje się większość magii
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;
}
Funkcja tworzenia Instantiate
wygląda tak samo, jak własna funkcja tworzenia Instantiate
Unity, z tym wyjątkiem, że prefabrykacja została już podana powyżej jako element klasy.
Pierwszym krokiem funkcji tworzenia Instantiate
jest sprawdzenie, czy w puli jest teraz nieaktywny obiekt. Oznacza to, że możemy ponownie użyć tego obiektu i zwrócić go żądającemu. Jeśli w puli znajduje się nieaktywny obiekt, ustawiamy pozycję i obrót, ustawiamy go jako aktywny (w przeciwnym razie mógłby zostać ponownie użyty przypadkowo, jeśli zapomnisz go aktywować) i zwróć go do requestera.
Drugi krok ma miejsce tylko wtedy, gdy w puli nie ma nieaktywnych przedmiotów, a pula może rosnąć powyżej początkowej kwoty. To, co się dzieje, jest proste: kolejne wystąpienie prefabrykatu jest tworzone i dodawane do puli. Zezwolenie na wzrost puli pomaga w utrzymaniu odpowiedniej ilości obiektów w puli.
Trzeci „krok” ma miejsce tylko wtedy, gdy w puli nie ma nieaktywnych przedmiotów, a pula nie może rosnąć. Gdy tak się stanie, requester otrzyma zerowy obiekt GameObject, co oznacza, że nic nie było dostępne i powinno być odpowiednio obsługiwane, aby zapobiec NullReferenceExceptions
.
Ważny!
Aby upewnić się, że twoje przedmioty wrócą do puli, nie powinieneś niszczyć obiektów gry. Jedyne, co musisz zrobić, to ustawić je jako nieaktywne, co umożliwi ich ponowne użycie w puli.
Prosta pula obiektów
Poniżej znajduje się przykład puli obiektów, która umożliwia wynajem i zwrot danego typu obiektu. Aby utworzyć pulę obiektów, Func dla funkcji tworzenia i Akcja niszczenia obiektu są wymagane, aby dać użytkownikowi elastyczność. Po zażądaniu obiektu, gdy pula jest pusta, zostanie utworzony nowy obiekt, a po zapytaniu, kiedy pula ma obiekty, obiekty są usuwane z puli i zwracane.
Pula obiektów
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;
}
}
}
Przykładowe użycie
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.
}
}
Kolejna prosta pula obiektów
Kolejny przykład: broń strzelająca pociskami.
Broń działa jak pula obiektów dla pocisków, które tworzy.
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);
}
}