О чем этот пример
Управление жизненным циклом объектов — ключевой навык в разработке игр. Неправильное удаление спрайтов и изображений может привести к утечкам памяти и нестабильной работе игры. В этой статье разберем, как корректно уничтожать объекты в Phaser, используя встроенный метод `destroy()`, и почему важно занулять ссылки на них.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
image;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('einstein', 'assets/pics/ra-einstein.png');
}
create ()
{
this.image = this.add.image(400, 300, 'einstein');
this.input.once('pointerdown', () =>
{
console.log('nuked');
this.image.destroy();
this.image = null;
});
}
update ()
{
if (this.image)
{
this.image.rotation += 0.01;
}
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Зачем нужно уничтожать объекты?
В процессе игры постоянно создаются новые объекты: выстрелы, враги, эффекты. Если их не удалять после завершения использования, они будут продолжать занимать оперативную память и, возможно, обрабатываться в цикле обновления, что снизит производительность.
Метод destroy() объекта Phaser.GameObjects.Image (и других игровых объектов) выполняет несколько важных действий:
- Удаляет объект из текущей сцены.
- Останавливает любые активные анимации или таймеры, связанные с объектом.
- Освобождает текстуру из памяти (если она больше не используется).
- Делает объект непригодным для дальнейшего использования.
После вызова destroy() обращаться к свойствам и методам объекта нельзя.
Корректное уничтожение изображения
Рассмотрим пример кода, где изображение вращается, а по клику мыши уничтожается. Обратите внимание на последовательность действий.
Сначала в методе create() создается и сохраняется изображение:
this.image = this.add.image(400, 300, 'einstein');
Затем мы настраиваем обработчик события клика, который вызовет уничтожение:
this.input.once('pointerdown', () => {
console.log('nuked');
this.image.destroy();
this.image = null;
});
Ключевые моменты:
1. Используем `this.input.once`, чтобы событие сработало один раз.
2. Вызываем `this.image.destroy()` для очистки.
3. **Важно:** Присваиваем переменной `this.image` значение `null`. Это предотвращает попытки обращения к уничтоженному объекту в других частях кода, например, в `update()`.
Защита от ошибок в update-цикле
Если объект уничтожен, но ссылка на него не обнулена, попытка изменить его свойства вызовет ошибку. В нашем примере метод update() каждый кадр пытается увеличивать вращение изображения, но только если оно существует.
update ()
{
if (this.image)
{
this.image.rotation += 0.01;
}
}
Проверка if (this.image) гарантирует, что код внутри выполнится, только когда переменная содержит действительный объект (не null и не undefined). После присваивания this.image = null условие станет ложным, и вращения не произойдет.
Такой паттерн — проверка перед использованием — стандартная практика для безопасной работы с объектами, которые могут быть уничтожены в ходе выполнения программы.
Что происходит под капотом?
При вызове `this.image.destroy()` Phaser выполняет следующие шаги:
1. Помечает объект как `active=false` и `visible=false`.
2. Удаляет его из списка обновления сцены (`this.sys.updateList`).
3. Удаляет его из списка отображения сцены (`this.sys.displayList`).
4. Если к объекту были привязаны слушатели событий или таймеры, они также очищаются.
5. Освобождается внутренняя ссылка на текстуру (если на эту же текстуру не ссылаются другие объекты).
После этого объект становится «мертвым» с точки зрения движка. Попытка вызвать его методы или изменить свойства приведет к предупреждению в консоли или ошибке.
Именно поэтому после destroy() необходимо явно обнулить свою собственную ссылку, как сделано в примере: this.image = null;. Это сообщает сборщику мусора JavaScript, что память можно освободить.
Что попробовать дальше
Корректное уничтожение объектов с помощью destroy() и последующее обнуление ссылок — обязательная практика для создания стабильных игр на Phaser. Это предотвращает утечки памяти и ошибки выполнения.
Для экспериментов попробуйте:
1. Убрать строку this.image = null; и понаблюдать за ошибками в консоли.
2. Создать несколько изображений и уничтожать их по очереди, отслеживая потребление памяти в инструментах разработчика.
3. Проверить, как ведут себя другие типы объектов (спрайты, текст, группы) при вызове destroy().
