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

Создание сложных сцен с множеством анимаций — обычное дело в разработке игр. Но что, если вам нужно остановить или удалить часть этих анимаций, не затрагивая остальные? В Phaser есть мощный инструмент для точечного управления твинами. В этой статье мы разберем пример, который демонстрирует, как массово останавливать анимации у произвольного набора игровых объектов одним вызовом. Этот подход полезен для создания интерактивных сцен, кат-сцен или очистки ресурсов.

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

Живой запуск

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

Исходный код


var config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    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.image('block', 'assets/sprites/50x50.png');
    this.load.spritesheet('fish', 'assets/sprites/fish-136x80.png', { frameWidth: 136, frameHeight: 80 });
}

function create ()
{
    var c = 0;
    var blocks = [];

    for (var i = 0; i < 108; i++)
    {
        var block = this.add.image(0, 0, 'block').setScale(0.3);

        blocks.push(block);

        this.tweens.add({
            targets: block,
            scaleX: 1,
            scaleY: 1,
            ease: 'Sine.easeInOut',
            duration: 300,
            delay: c * 50,
            repeat: -1,
            yoyo: true
        });

        c++;

        if (c % 12 === 0)
        {
            c = 0;
        }
    }

    Phaser.Actions.GridAlign(blocks, {
        width: 12,
        height: 10,
        cellWidth: 60,
        cellHeight: 60,
        x: 70,
        y: 60
    });

    var image1 = this.add.image(0, 80, 'fish', 0);

    this.tweens.add({
        targets: image1,
        props: {
            x: { value: 700, duration: 4000, flipX: true },
            y: { value: 500, duration: 8000,  },
        },
        ease: 'Sine.easeInOut',
        yoyo: true,
        repeat: -1
    });

    var image2 = this.add.image(400, 80, 'fish', 1);

    this.tweens.add({
        targets: image2,
        props: {
            x: { value: 500, duration: 2000, flipX: true },
            y: { value: 500, duration: 10000,  },
        },
        ease: 'Sine.easeInOut',
        yoyo: true,
        repeat: -1
    });

    var image3 = this.add.image(800, 200, 'fish', 2).setFlipX(true);

    this.tweens.add({
        targets: image3,
        props: {
            x: { value: 70, flipX: true },
            y: { value: 250 },
        },
        duration: 3000,
        ease: 'Power1',
        yoyo: true,
        repeat: -1
    });

    var image4 = this.add.image(100, 550, 'fish', 2).setScale(0.75);

    this.tweens.add({
        targets: image4,
        props: {
            x: { value: 700, duration: 2000, flipX: true },
            y: { value: 50, duration: 15000,  },
        },
        ease: 'Sine.easeInOut',
        yoyo: true,
        repeat: -1
    });

    this.input.once('pointerdown', () => {

        this.tweens.killTweensOf([ image1, blocks, image4 ]);

    });
}

Подготовка сцены и создание множества анимированных объектов

В начале кода создается стандартная конфигурация игры Phaser и загружаются спрайты. Основная логика находится в функции create. Первым делом создается массив из 108 блоков (blocks). Каждому блоку назначается персональный твин, который циклически меняет его масштаб от маленького к большому и обратно.

Задержка (delay) для каждого твина рассчитывается так, чтобы анимации запускались волнами, создавая эффект пульсации.

this.tweens.add({
    targets: block,
    scaleX: 1,
    scaleY: 1,
    ease: 'Sine.easeInOut',
    duration: 300,
    delay: c * 50,
    repeat: -1,
    yoyo: true
});

После создания всех блоков они выравниваются в сетку с помощью Phaser.Actions.GridAlign. Это чисто визуальное преобразование, не влияющее на твины.

Создание независимых анимированных спрайтов

Далее в сцене создаются четыре рыбы (image1, image2, image3, image4). Каждая из них получает свой собственный, более сложный твин. Эти твины анимируют несколько свойств одновременно (координаты `xиy), имеют разную длительность (duration) и используют эффект отражения спрайта (flipX`).

Ключевой момент: каждый твин привязан к своему целевому объекту (targets), но управляется централизованно через менеджер твинов сцены this.tweens.

this.tweens.add({
    targets: image1,
    props: {
        x: { value: 700, duration: 4000, flipX: true },
        y: { value: 500, duration: 8000,  },
    },
    ease: 'Sine.easeInOut',
    yoyo: true,
    repeat: -1
});

Массовая остановка анимаций по событию

Вся магия происходит в обработчике клика (pointerdown). По одному клику мыши нужно остановить анимации у первой рыбы (image1), у всех блоков в массиве (blocks) и у четвертой рыбы (image4). При этом вторая и третья рыбы должны продолжать двигаться.

Для этого используется метод this.tweens.killTweensOf(). Он принимает один целевой объект или массив объектов и немедленно останавливает все твины, которые в данный момент воздействуют на эти объекты.

this.input.once('pointerdown', () => {
    this.tweens.killTweensOf([ image1, blocks, image4 ]);
});

Важно понимать: в метод передается не сам массив твинов, а массив игровых объектов (Image), которые являются целями этих твинов. Система Phaser сама находит и завершает все связанные с ними активные анимации. Объекты image2 и image3 не входят в список, поэтому их твины продолжают работать.

Почему это работает: связь твинов и целей

Метод killTweensOf работает, потому что менеджер твинов this.tweens внутренне отслеживает связь между каждым созданным твином и его свойством targets. Когда вы вызываете этот метод, система проходит по всем активным твинам и проверяет, ссылаются ли их цели на переданные объекты. Если ссылка найдена — твин немедленно уничтожается.

Это мощный и безопасный способ управления анимациями. Вам не нужно хранить ссылки на каждый созданный твин в отдельной переменной, что особенно удобно при работе с десятками или сотнями анимированных объектов, как в нашем примере с блоками.

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

Использование this.tweens.killTweensOf() позволяет гибко и эффективно управлять жизненным циклом анимаций в Phaser. Вы можете останавливать анимации у любых комбинаций объектов по событию таймера, коллизии или пользовательского ввода. Для экспериментов попробуйте

  1. Останавливать твины не по клику, а при столкновении рыб
  2. Реализовать кнопку 'Пауза', которая останавливает все твины на сцене через this.tweens.killTweensOf(this.children.list)
  3. Восстанавливать остановленные анимации с помощью методов resume или перезапуская твины