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

При создании интерактивных игр часто возникает необходимость динамически управлять анимациями: запускать, останавливать и перезапускать их по действиям игрока. Неправильное управление твинами может привести к утечкам памяти и неожиданному поведению анимаций. В этой статье мы разберем практический пример управления твинами в Phaser, покажем как правильно останавливать анимации с сохранением текущего состояния объекта и как избежать распространенных ошибок при работе с интерактивными анимациями.

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

Живой запуск

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

Исходный код


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

var game = new Phaser.Game(config);
var tween = null;
var tweenAlive = false;

function create ()
{
    var box = this.add.rectangle(100, 100, 100, 100, 0xff0000);
    var reference = this.add.rectangle(100, 210, 100, 100, 0xff0000);
    var boxWidthText = this.add.text(200, 100, "BOX SCALE X: " + box.scaleX);

    this.input.on(Phaser.Input.Events.POINTER_DOWN, function () {
        if (tween) {
            tween.stop(0);
            tween = null;
            boxWidthText.setText("BOX SCALE X: " + box.scaleX);

        } else {
            tween = this.tweens.add({
                targets: box,
                scaleX: 0.25,
                paused: false,
                yoyo: true,
                repeat: -1
            });
        }

    }, this);

    this.add.text(20, 20, 'Click to start/stop scale tween');
}

Проблема: управление запущенными твинами

Когда игрок взаимодействует с игровым миром (кликает, нажимает кнопки), часто нужно включать и выключать анимации объектов. Простое создание нового твина без остановки предыдущего может привести к наложению анимаций и неконтролируемому поведению.

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

var tween = null;
var tweenAlive = false;

Мы объявляем переменную tween для хранения ссылки на текущий твин и tweenAlive для отслеживания его состояния (хотя в данном примере tweenAlive не используется активно).

Создание базовой сцены и объектов

Сначала настроим сцену и создадим визуальные элементы. Основной квадрат будет анимироваться, а второй квадрат (reference) останется статичным для сравнения.

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

var game = new Phaser.Game(config);

function create() {
    var box = this.add.rectangle(100, 100, 100, 100, 0xff0000);
    var reference = this.add.rectangle(100, 210, 100, 100, 0xff0000);
    var boxWidthText = this.add.text(200, 100, "BOX SCALE X: " + box.scaleX);
}

Мы создаем два одинаковых красных прямоугольника и текстовый элемент для отображения текущего масштаба анимируемого квадрата. Текст будет обновляться при остановке анимации.

Обработка кликов и управление твинами

Ключевая логика находится в обработчике события POINTER_DOWN. При каждом клике мы проверяем, существует ли активный твин, и в зависимости от этого либо останавливаем его, либо создаем новый.

this.input.on(Phaser.Input.Events.POINTER_DOWN, function () {
    if (tween) {
        tween.stop(0);
        tween = null;
        boxWidthText.setText("BOX SCALE X: " + box.scaleX);
    } else {
        tween = this.tweens.add({
            targets: box,
            scaleX: 0.25,
            paused: false,
            yoyo: true,
            repeat: -1
        });
    }
}, this);

Важные моменты: 1. tween.stop(0) — останавливает твин немедленно, сохраняя текущие значения свойств целевого объекта 2. tween = null — очищаем ссылку, позволяя сборщику мусора освободить память 3. boxWidthText.setText() — обновляем текст с текущим масштабом после остановки 4. При создании нового твина используем параметры: yoyo: true для обратного воспроизведения и repeat: -1 для бесконечного повторения

Параметры твина и их значение

Давайте подробнее рассмотрим параметры создаваемого твина:

tween = this.tweens.add({
    targets: box,          // Объект для анимации
    scaleX: 0.25,         // Конечное значение масштаба по X
    paused: false,        // Запуск сразу после создания
    yoyo: true,           // Возврат к начальному значению
    repeat: -1           // Бесконечное повторение
});

- targets: может быть массивом объектов или одним объектом - scaleX: 0.25: квадрат уменьшится до 25% от исходного размера по оси X - yoyo: true: после достижения scaleX: 0.25 анимация пойдет в обратном направлении - repeat: -1: анимация будет повторяться бесконечно - Отсутствуют параметры duration, ease — используются значения по умолчанию

Важность очистки ссылок на твины

Одна из частых ошибок — хранение устаревших ссылок на завершенные твины. В нашем примере это решается простым присваиванием tween = null после остановки.

Без этой очистки: 1. Проверка if (tween) всегда будет возвращать true 2. Невозможно будет создать новый твин (будет постоянно срабатывать ветка остановки) 3. Может произойти утечка памяти

Также обратите внимание на использование tween.stop(0) вместо tween.stop(). Нулевой параметр означает немедленную остановку без дополнительных переходов. Альтернативные варианты:

tween.stop();          // Остановка с завершением твина
тween.complete();      // Немедленное завершение с установкой конечных значений

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

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

Правильное управление твинами — основа отзывчивого и стабильного геймплея. Ключевые принципы: всегда останавливайте старые твины перед созданием новых, очищайте ссылки и обновляйте интерфейс в соответствии с состоянием анимаций. Для экспериментов попробуйте: 1. Добавить анимацию нескольких свойств одновременно (scaleX, scaleY, alpha) 2. Реализовать плавную остановку с параметром задержки в stop() 3. Создать систему очереди твинов, которые запускаются последовательно 4. Добавить визуальную индикацию состояния твина (активен/остановлен)