О чем этот пример

Когда вы создаете много анимаций в Phaser, важно понимать, как менеджер твинов управляет памятью. Этот пример наглядно демонстрирует процесс создания и автоматического удаления сотен одноразовых анимаций (твинов). Мы разберем, как Phaser справляется с нагрузкой, и почему такой подход полезен для спецэффектов, частиц или любых объектов с коротким жизненным циклом.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


var x = 0;
var i = 0;
var blitter;
var text;

var scene = null;
var add = false;
var blitter;
var idx = 1;
var frame = 'veg01';
var numbers = [];

var text;
var tween;

class Example extends Phaser.Scene
{
    constructor()
    {
        super();
    }

    preload()
    {
        
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('balls', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });
    }

    //  This test will create lots of single-fire tweens and not re-use them, to test Tween Manager GC

    create()
    {
        window.scene = this;

        blitter = this.add.blitter(0, 0, 'balls');

        text = this.add.text(10, 720);

        this.time.addEvent({ delay: 2, callback: this.launch, callbackScope: this, repeat: 100000 });
    }

    launch()
    {
        i++;

        var bob = blitter.create(x, 700, Phaser.Math.Between(0, 5));

        x += 0.5;

        if (x >= 1024)
        {
            x = 0;
        }

        if (Phaser.VERSION.substr(0, 4) === '3.60')
        {
            text.setText('Active Tweens: ' + this.tweens.tweens.length + '\nTotal Tweens created: ' + i);
        }
        else
        {
            text.setText('Active Tweens: ' + this.tweens.tweens.length + '\nTotal Tweens created: ' + i);
        }

        this.tweens.add({
            targets: bob,
            y: 10,
            duration: Phaser.Math.Between(500, 1000),
            ease: 'Power1',
            yoyo: true,
            onComplete: function ()
            {
                bob.destroy();
            }
        });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Суть примера: нагрузочный тест для менеджера твинов

Пример создает простую нагрузочную систему: каждые 2 миллисекунды запускается новая анимация (tween) для спрайта. Твин перемещает спрайт вверх, затем обратно вниз, после чего спрайт уничтожается. Ключевой момент — твины не переиспользуются. Они создаются, выполняются и затем автоматически удаляются сборщиком мусора (GC) менеджера твинов. Это позволяет проверить, насколько эффективно Phaser управляет памятью при высокой частоте создания анимаций.

В интерфейсе отображаются два важных показателя: количество активных твинов в данный момент и общее количество созданных за время работы. Это помогает отслеживать нагрузку на систему.

Создание массового эффекта с помощью Blitter и таймера

Для эффективного отображения множества спрайтов используется Blitter — оптимизированный объект для рендеринга множества одинаковых или похожих изображений. Спрайтшит 'balls' загружается и служит источником кадров.

Основной механизм запуска заключен в событии таймера, которое повторяется 100 000 раз с минимальной задержкой.

this.time.addEvent({
    delay: 2,
    callback: this.launch,
    callbackScope: this,
    repeat: 100000
});

Функция launch вызывается этим событием. В ней создается новый спрайт (bob) через метод blitter.create в случайной горизонтальной позиции `x` и с случайным кадром из спрайтшита.

var bob = blitter.create(x, 700, Phaser.Math.Between(0, 5));

Сердце теста: создание и конфигурация одноразового твина

Для каждого созданного спрайта bob немедленно создается новый твин. Его задача — анимировать перемещение по оси Y.

Конфигурация твина содержит несколько ключевых параметров: - targets: объект, к которому применяется анимация (наш спрайт bob). - `y`: целевое значение свойства (движение к координате Y=10). - duration: случайная длительность от 500 до 1000 мс, что создает разнообразие в анимации. - ease: функция плавности 'Power1' для базового easing. - yoyo: если true, твин проиграется в обратном направлении после завершения, вернув объект в исходную позицию. - onComplete: колбэк, который срабатывает после окончания всей анимации (вперед и назад, если yoyo=true). В нем спрайт уничтожается.

this.tweens.add({
    targets: bob,
    y: 10,
    duration: Phaser.Math.Between(500, 1000),
    ease: 'Power1',
    yoyo: true,
    onComplete: function () {
        bob.destroy();
    }
});

После вызова onComplete твин автоматически удаляется из менеджера this.tweens. Именно этот процесс удаления и проверяется в тесте.

Мониторинг производительности в реальном времени

Для наглядности в левом нижнем углу сцены выводится текстовый счетчик. Он обновляется при каждом новом запуске твина и показывает два ключевых числа: 1. Количество активных твинов в массиве this.tweens.tweens. Это твины, которые в данный момент выполняются или ожидают запуска. 2. Общее количество созданных твинов за все время (`i`).

text.setText('Active Tweens: ' + this.tweens.tweens.length + '\nTotal Tweens created: ' + i);

Разница между этими числами — это количество твинов, которые уже завершили работу и были удалены сборщиком мусора менеджера. В стабильной системе количество активных твинов должно колебаться вокруг некоторого значения, а не бесконечно расти, что и подтверждает эффективность работы GC.

Что попробовать дальше

Этот пример — отличный способ убедиться, что Phaser надежно управляет памятью даже при интенсивном создании короткоживущих анимаций. Для экспериментов попробуйте изменить задержку таймера с 2 мс на большее значение, чтобы увидеть, как снижается нагрузка. Или уберите параметр yoyo: true и измените onComplete так, чтобы спрайт уничтожался сразу после достижения верха — это симулирует поведение частиц. Также можно заменить Blitter на обычные Sprite объекты и сравнить производительность.