О чем этот пример
При работе с анимациями в Phaser разработчики иногда сталкиваются с неочевидным поведением при попытке воспроизвести анимацию на спрайте, который был создан, но не добавлен в сцену. Этот пример наглядно демонстрирует, как правильно создавать и управлять анимированными объектами, а также как избежать типичной ошибки, когда спрайт уничтожается до того, как его можно увидеть. Понимание жизненного цикла игровых объектов — ключ к стабильной работе вашей игры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#010101',
parent: 'phaser-example',
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('walker', 'assets/animations/walker.png', 'assets/animations/walker.json');
}
function create ()
{
const animConfig = {
key: 'walk',
frames: 'walker',
frameRate: 60,
repeat: -1
};
this.anims.create(animConfig);
const sprite = this.add.sprite(0, 0, 'walker', 'frame_0000');
// const sprite = new Phaser.GameObjects.Sprite(this, 0, 0, 'walker', 'frame_0000');
sprite.play('walk');
sprite.destroy();
// var container = this.add.container(400, 300);
// container.add(sprite);
console.log(this);
}
Настройка сцены и загрузка ассетов
Вся логика примера разбита на две стандартные функции сцены Phaser: preload и create. В preload мы настраиваем базовый URL для загрузки и используем метод this.load.atlas для загрузки атласа анимации.
function preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('walker', 'assets/animations/walker.png', 'assets/animations/walker.json');
}
Метод setBaseURL задает корневую папку для всех последующих загрузок. load.atlas загружает изображение-атлас (walker.png) и соответствующий JSON-файл с данными о кадрах (walker.json). Ключ 'walker' будет использоваться для доступа к этому атласу в коде.
Создание анимации и спрайта
В функции create происходит основная работа. Сначала создается конфигурационный объект для анимации, который передается в this.anims.create. Затем создается спрайт.
function create ()
{
const animConfig = {
key: 'walk',
frames: 'walker',
frameRate: 60,
repeat: -1
};
this.anims.create(animConfig);
const sprite = this.add.sprite(0, 0, 'walker', 'frame_0000');
}
Конфигурация animConfig определяет анимацию с ключом 'walk', которая использует все кадры из атласа 'walker', проигрывается со скоростью 60 кадров в секунду и повторяется бесконечно (repeat: -1).
Спрайт создается через фабричный метод сцены this.add.sprite. Ему передаются координаты (0, 0), ключ текстуры 'walker' и имя стартового кадра 'frame_0000'. Важно, что this.add.sprite не только создает объект Phaser.GameObjects.Sprite, но и автоматически добавляет его в список отображения текущей сцены.
Проблема: воспроизведение и немедленное уничтожение
В исходном коде после создания спрайта на нем запускается анимация, а затем он сразу уничтожается. Из-за этого игрок ничего не увидит на экране.
sprite.play('walk');
sprite.destroy();
Метод sprite.play('walk') начинает проигрывать анимацию, созданную ранее. Однако следующий же вызов sprite.destroy() немедленно удаляет спрайт из сцены, освобождает его текстуру и останавливает все связанные с ним процессы, включая анимацию. Поскольку это происходит в одном кадре игры, спрайт не успевает отобразиться.
В закомментированном коде показана альтернатива — создание спрайта через конструктор new Phaser.GameObjects.Sprite. Такой спрайт не будет автоматически добавлен в сцену, и вызов play() на нем может привести к ошибкам, так как для работы анимации ему нужен родительский контекст сцены.
Как это исправить: отложенное уничтожение и контейнеры
Чтобы увидеть анимацию, нужно либо убрать вызов destroy(), либо отложить его. Phaser предоставляет несколько способов для управления временем. Самый простой — использовать встроенные события сцены или таймеры.
// Уничтожить спрайт через 2 секунды
this.time.delayedCall(2000, () => {
sprite.destroy();
});
Еще один подход, показанный в комментариях, — использование Container. Контейнеры полезны для группировки объектов.
// Создание контейнера и добавление в него спрайта
var container = this.add.container(400, 300);
container.add(sprite);
В этом случае спрайт будет отрисован относительно позиции контейнера (400, 300). Уничтожение контейнера автоматически уничтожит все его дочерние элементы, включая наш спрайт.
Что попробовать дальше
Ключевой вывод: спрайт должен быть частью списка отображения сцены (через this.add или добавление в контейнер) для корректного отображения и работы анимации. Немедленный вызов destroy() после play() не дает анимации ни единого шанса. Для экспериментов попробуйте
- Запустить анимацию и перемещать спрайт с помощью
this.tweens - Создать несколько спрайтов с одной анимацией и уничтожать их по клику мыши
- Использовать
sprite.on('animationcomplete', callback)для выполнения кода после окончания анимации
