О чем этот пример
При разработке игр на Phaser управление памятью часто становится критически важным, особенно в проектах с большим количеством ресурсов. Игнорирование очистки текстур может привести к утечкам памяти, замедлению работы и, в конечном итоге, к падению производительности. В этой статье разберем практический пример с последовательной загрузкой и выгрузкой 500 текстур, чтобы понять, как правильно использовать `this.textures.remove()` и безопасно переключаться между сценами, не перегружая память браузера.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Scene1 extends Phaser.Scene {
constructor() {
super("scene1");
}
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
for (let i = 1; i<= 500; i++) {
// this.load.image('apple' + i, 'assets/sprites/apple.png')
this.load.image('apple' + i, 'assets/pics/uv-grid.jpg')
}
}
create() {
this.add.text(100, 100, 'Scene 1', { color: '#00ff00', align: 'left' });
for (let i = 1; i <= 32; i++)
{
let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(0, 600);
this.add.image(x, y, 'apple' + i);
}
this.input.once('pointerdown', () => {
for (let i = 1; i<= 500; i++)
{
this.textures.remove('apple' + i)
}
this.scene.start('scene2');
});
}
}
class Scene2 extends Phaser.Scene {
constructor() {
super("scene2");
}
create() {
this.add.text(100, 100, 'Textures Cleared', { color: '#00ff00', align: 'left' });
this.input.once('pointerdown', () => {
this.scene.start('scene3');
});
}
}
class Scene3 extends Phaser.Scene {
constructor() {
super("scene3");
}
create() {
this.add.text(100, 100, 'Click to start', { color: '#00ff00', align: 'left' });
this.input.once('pointerdown', () => {
this.scene.start('scene1');
});
}
}
var gameConfig = {
type: Phaser.CANVAS,
scale: {
width: 800,
height: 600
},
parent: 'phaser-example',
antialias: true,
mipmapFilter: 'LINEAR_MIPMAP_LINEAR',
scene: [Scene3, Scene1, Scene2]
}
new Phaser.Game(gameConfig);
Проблема: накопление текстур в памяти
В примере в Scene1 в методе preload() в цикле загружается 500 изображений. Каждое получает уникальный ключ от 'apple1' до 'apple500'. Это имитирует типичную ситуацию, когда игра загружает много ресурсов для уровня, меню или мини-игры.
Если просто переключать сцены без очистки, все 500 текстур останутся в кэше Phaser (this.textures). При множественных переходах память будет заполняться одними и теми же или разными текстурами, что в итоге приведет к утечке.
for (let i = 1; i<= 500; i++) {
this.load.image('apple' + i, 'assets/pics/uv-grid.jpg')
}
Решение: ручное удаление текстур через `this.textures.remove()`
Перед переходом на Scene2 в обработчике клика выполняется очистка. Метод this.textures.remove() удаляет текстуру из кэша Phaser по её ключу, освобождая связанную с ней память. Важно вызывать его для каждого загруженного ресурса, который больше не нужен.
В примере это делается в том же цикле, что и загрузка, но уже в create(). После очистки всех 500 текстур происходит переход на следующую сцену.
this.input.once('pointerdown', () => {
for (let i = 1; i<= 500; i++) {
this.textures.remove('apple' + i)
}
this.scene.start('scene2');
});
Архитектура сцен и порядок переключения
В конфигурации игры сцены передаются в массиве в порядке [Scene3, Scene1, Scene2]. Это означает, что первой инициализируется Scene3 (с текстом 'Click to start'), затем Scene1, и, наконец, Scene2. Однако стартует игра с Scene3, так как она первая в списке. Это демонстрирует, что порядок в массиве влияет на инициализацию, но не на стартовую сцену, если не указано иное.
Переходы организованы по цепочке: Scene3 -> Scene1 -> Scene2 -> Scene3. В Scene1 происходит основная работа с памятью.
scene: [Scene3, Scene1, Scene2]
Практические рекомендации по управлению памятью
1. **Используйте `preload()` для загрузки только необходимых ресурсов.** Не загружайте всё сразу, если в этом нет необходимости.
2. **Чистите текстуры, аудио и другие ресурсы, когда они больше не нужны.** Помимо `this.textures.remove()`, есть методы для звука (`this.sound.removeByKey()`) и данных (`this.cache.remove()`).
3. **Переключайте сцены после очистки.** Как в примере, сначала удалите ресурсы, затем вызовите `this.scene.start()`.
4. **Тестируйте на реальных устройствах.** Инструменты разработчика в браузере (вкладка Memory) помогут отследить утечки.
5. **Рассмотрите использование `Scene.sleep()` и `Scene.wake()`.** Если вы планируете возвращаться к сцене, можно не удалять её ресурсы, а переводить сцену в спящий режим.
Что попробовать дальше
Правильное управление памятью — ключ к стабильной работе игр на Phaser. Регулярно очищайте кэш текстур и других ресурсов при смене игровых состояний. Для экспериментов попробуйте: удалять только часть текстур, использовать динамическую загрузку через this.load.once('complete', ...), или создать менеджер ресурсов, который автоматически выгружает неиспользуемые ассеты.
