ObjectPool в Unity
Часть раздела Архитектура игр в Unity путешествия по Unity на Coddy. Урок 39 из 50.
В Unity есть пул ObjectPool<T> в UnityEngine.Pool. Укажи ему, что делать на каждом шаге, с помощью четырёх лямбда-выражений, а он будет хранить запасные объекты и вести их подсчёт. Эта дымовая труба выпускает клубы дыма каждые 0.2 секунды, и каждый клуб возвращается через секунду:
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 выполняется, когда Get обнаруживает, что пул пуст; actionOnGet выполняется при каждом Get, а actionOnRelease — при каждом Release. Если клуб дыма возвращается, когда в пуле уже ждут maxSize запасных объектов, пул не сохраняет его: actionOnDestroy уничтожает его. defaultCapacity — это только начальный размер списка пула, поэтому заранее ничего не создаётся. CountAll считает клубы дыма, которые пул создал и всё ещё хранит, CountActive — те, что находятся в игре, а CountInactive — запасные. Get включает клуб дыма до того, как труба задаёт его положение. Здесь это не проблема, потому что у Puff нет OnEnable, который считывал бы его положение. Клуб дыма хранит пул как IObjectPool<Puff> — интерфейс, который реализуют все пулы Unity, — а Launch сбрасывает его возраст при каждом использовании.
Если collectionCheck включён, возврат клуба дыма, который уже находится в пуле, вызывает исключение InvalidOperationException: Trying to release an object that has already been released to the pool. Проверка выполняется только в редакторе, потому что при каждом Release она перебирает запасные объекты; в сборке клуб дыма дважды окажется в пуле, и два вызова Get вернут один и тот же объект. Частая причина — объект продолжает работать после возврата в пул и возвращает себя повторно; поэтому actionOnRelease выключает его.
Задание
СреднеФейерверки. При каждом нажатии пробела из пусковой установки вылетают 8 искр, а каждая Spark вызывает Return(this) пусковой установки, когда заканчивается её 1 секунда жизни (Spark готова). Сегодня пусковая установка создаёт и уничтожает каждую искру. Используй пул Unity:
- В
Launcher.csсоздайObjectPool<Spark>в Awake. Он создаёт искру, создавая экземплярsparkPrefab, включает искру, когда её берут из пула, и выключает, когда она возвращается, а также уничтожает объект искры, когда та покидает пул. - Пул хранит не более 5 запасных искр, поэтому после залпа из 8 искр 5 остаются ждать, а 3 уничтожаются.
- При нажатии пробела 8 искр берутся из пула вместо создания экземпляров, а
Returnвозвращает искру в пул вместо её уничтожения.
Нажми Play, сделай несколько залпов, затем нажми Check.
Попробуйте сами
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;
public class Launcher : MonoBehaviour
{
public Spark sparkPrefab;
public int sparks = 8;
void Awake()
{
// Создай пул: создание, получение, возврат и уничтожение, не более 5 запасных искр
}
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);
}
}
}
// Искра, срок жизни которой истёк
public void Return(Spark spark)
{
Destroy(spark.gameObject);
}
// Наружу и вверх, равномерно во все стороны от пусковой установки
Vector3 Direction(int i)
{
return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Архитектура игр в Unity
1Компоненты, а не иерархии
Небольшие компонентыБазовый скрипт врагаАбстрактные компонентыСовместная работа компонентовПовторение: модульная турель4Сериализованные данные
Сериализуемые классыСписки в InspectorПеречисления в InspectorПовторение: таблица лута2Интерфейсы в Unity
IDamageableИнтерфейсы и TryGetComponentGetComponentsПовторение: клавиша взаимодействия5ScriptableObjects
Ресурсы данныхХарактеристики оружияОбщие настройкиВолны как данныеПовторение: база предметов