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

В процессе разработки игры часто возникает необходимость прервать анимацию объекта по какому-либо событию — например, при клике игрока или изменении состояния. Phaser предоставляет для этого метод `killTweensOf()`, но его поведение может оказаться неочевидным. В этой статье мы разберем, почему вызов этого метода непосредственно после создания твина не работает, и как правильно организовать управление анимациями в вашем проекте, чтобы избежать багов и неожиданного поведения.

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

Живой запуск

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

Исходный код


var config = {
    type: Phaser.AUTO,
    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/block.png');
}

function create ()
{
    var marker = this.add.image(100, 300, 'block').setAlpha(0.3);
    var image = this.add.image(100, 300, 'block');

    var tween = this.tweens.add({
        targets: image,
        x: 700,
        delay: 1000,
        duration: 6000,
        ease: 'Power2'
    });

    // doesnt stop tween
    this.tweens.killTweensOf(image);

    this.input.on('pointerdown', ()=>{
        // does stop tween
        this.tweens.killTweensOf(image);
    });
}

Проблема: твин не останавливается при создании

В исходном примере твин создается для перемещения спрайта image от координаты x=100 до x=700 с задержкой в 1 секунду. Сразу после создания вызывается метод this.tweens.killTweensOf(image), который, по идее, должен остановить все твины, связанные с этим объектом. Однако анимация продолжает выполняться после задержки.

Это происходит из-за того, что твин в Phaser имеет внутреннее состояние. При создании он еще не активирован (не начал выполняться) из-за указанной задержки (delay: 1000). Метод killTweensOf() на данном этапе не находит активных твинов для данного целевого объекта и, следовательно, ничего не останавливает.

var tween = this.tweens.add({
    targets: image,
    x: 700,
    delay: 1000,
    duration: 6000,
    ease: 'Power2'
});

// Этот вызов не сработает, твин еще не активен
this.tweens.killTweensOf(image);

Решение: остановка по событию

Чтобы успешно прервать выполнение твина, нужно вызывать killTweensOf() когда твин уже активен. В примере это реализовано через обработчик события клика (pointerdown). В момент клика твин уже запущен (прошла задержка в 1 секунду), и метод корректно находит и останавливает его.

Этот подход следует использовать для управления анимациями в ответ на действия игрока или изменения игровой логики. Например, остановка движения персонажа при получении урона или прерывание анимации меню при переходе в другой режим.

this.input.on('pointerdown', () => {
    // Теперь твин активен, и вызов остановит его
    this.tweens.killTweensOf(image);
});

Альтернативы: полный контроль над твинами

Помимо killTweensOf(), Phaser предлагает другие методы для управления твинами, которые могут быть полезны в разных сценариях.

1. **Сохранение ссылки на твин:** При создании твина можно сохранить возвращаемый объект в переменную. Это позволяет управлять им напрямую через методы pause(), resume(), stop() или destroy(). 2. **Глобальная остановка:** Метод this.tweens.killAll() останавливает все твины в текущей сцене. Полезно при смене сцены или полной перезагрузке игрового состояния. 3. **Проверка состояния:** У объекта твина есть свойства, такие как isPlaying(), которые позволяют проверить, активен ли он в данный момент.

// Создание твина с сохранением ссылки
var myTween = this.tweens.add({
    targets: image,
    x: 700,
    duration: 3000
});

// Прямое управление через ссылку
myTween.pause();
myTween.resume();
myTween.stop(); // Немедленная остановка и возврат в начало
myTween.destroy(); // Полное уничтожение твина

// Остановка всех твинов в сцене
this.tweens.killAll();

Практические рекомендации

1. **Планируйте события:** Если вам нужно отменить твин сразу после его создания, возможно, стоит пересмотреть логику и не создавать его вовсе, либо создавать без задержки. 2. **Используйте обработчики событий:** Для интерактивной остановки всегда привязывайте killTweensOf() к конкретным игровым событиям (ввод, коллизия, таймер), а не к моменту создания. 3. **Очищайте твины при смене сцены:** Чтобы избежать утечек памяти и выполнения фоновых анимаций, вызывайте this.tweens.killAll() в методе shutdown() или destroy() вашей сцены. 4. **Документируйте сложные анимации:** Если в сцене много пересекающихся твинов, ведите список их целей и условий остановки для упрощения отладки.

// Пример очистки при уничтожении сцены
class GameScene extends Phaser.Scene {
    ...
    shutdown() {
        // Гарантированно останавливаем все анимации при выходе из сцены
        this.tweens.killAll();
    }
}

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

Корректное управление твинами — ключ к созданию отзывчивого и предсказуемого геймплея. Основной вывод: метод killTweensOf() работает только с активными твинами. Для надежного контроля сохраняйте ссылки на анимации или привязывайте их остановку к четким событиям в игре. Для экспериментов попробуйте создать цепочку твинов (chain) и реализовать систему приоритетной остановки одного из них, либо управлять анимациями через пользовательские события (this.events), чтобы разделить логику создания и прерывания.