О чем этот пример
Работа с анимациями — неотъемлемая часть разработки игр. Часто разработчики сталкиваются с ситуацией, когда твин внутри таймлайна ведёт себя не так, как ожидалось, особенно при использовании колбэка `onUpdate`. В этой статье мы разберём пример, где `onUpdate` не вызывается, и покажем, как исправить эту проблему. Понимание этих нюансов сэкономит вам часы отладки и позволит создавать более сложные и контролируемые последовательности анимаций.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ship', 'assets/tweens/spacetank.png');
}
create ()
{
const ship1 = this.add.image(200, 300, 'ship');
const ship2 = this.add.image(600, 300, 'ship');
// this.tweens.add({
// targets: [ ship1, ship2 ],
// alpha: {
// from: 1,
// to: 0,
// yoyo: true,
// duration: 500,
// ease: Phaser.Math.Easing.Sine.InOut,
// },
// onUpdate: (t) => {
// console.log(t.progress);
// }
// });
this.cameras.main.setBackgroundColor('rgb(87,255,0)');
const timeline = this.add.timeline([
{
at: 1000,
tween: {
targets: [ ship1, ship2 ],
alpha: {
from: 1,
to: 0,
yoyo: true,
duration: 500,
ease: Phaser.Math.Easing.Sine.InOut,
},
onUpdate: (t) => {
console.log(t.progress);
}
}
}
]);
timeline.play();
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: 'rgb(87,255,0)',
clearBeforeRender: false,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Проблема: onUpdate не срабатывает в таймлайне
В исходном коде представлен класс сцены, который создаёт два спрайта корабля и пытается анимировать их прозрачность с помощью Phaser.Tweens.Timeline. Ключевая проблема — колбэк onUpdate, переданный в конфигурацию твина внутри таймлайна, никогда не вызывается.
Это происходит потому, что структура конфигурации твина, когда он объявляется внутри события таймлайна, отличается от структуры при прямом вызове this.tweens.add(). В исходном примере onUpdate помещён на один уровень с параметрами анимации, что некорректно для контекста таймлайна.
Анатомия таймлайна в Phaser
Таймлайн (Phaser.Tweens.Timeline) — это мощный инструмент для упорядочивания твинов во времени. Он создаётся методом this.add.timeline(), который принимает массив событий. Каждое событие описывается объектом с двумя основными полями:
* at: Время в миллисекундах от начала воспроизведения таймлайна, когда должен стартовать твин.
* tween: Объект с конфигурацией для создания твина. Именно его структура часто вызывает путаницу.
Вот как выглядит создание таймлайна в коде:
const timeline = this.add.timeline([
{
at: 1000,
tween: {
targets: [ ship1, ship2 ],
alpha: { from: 1, to: 0, yoyo: true, duration: 500 }
}
}
]);
timeline.play();
Исправление: правильное размещение onUpdate
Чтобы колбэк onUpdate начал работать, его необходимо поместить не на верхний уровень конфигурации tween, а внутрь объекта, описывающего конкретное анимируемое свойство (в нашем случае — alpha). Это ключевое отличие от стандартного создания твина.
Вот исправленная версия конфигурации для события в таймлайне. Обратите внимание, что onUpdate теперь находится внутри alpha:
const timeline = this.add.timeline([
{
at: 1000,
tween: {
targets: [ ship1, ship2 ],
alpha: {
from: 1,
to: 0,
yoyo: true,
duration: 500,
ease: Phaser.Math.Easing.Sine.InOut,
onUpdate: (tween) => {
console.log(tween.progress);
}
}
}
}
]);
Колбэк onUpdate теперь будет вызываться на каждом кадре анимации свойства alpha, и в консоль будет выводиться текущий прогресс твина (значение от 0 до 1).
Сравнение с обычным твином
Давайте проясним разницу. При создании твина напрямую через this.tweens.add(), колбэки размещаются на том же уровне, что и targets. Это работает корректно.
Пример прямого создания твина (раскомментируйте в исходном коде):
this.tweens.add({
targets: [ ship1, ship2 ],
alpha: { from: 1, to: 0, yoyo: true, duration: 500 },
onUpdate: (t) => { console.log(t.progress); } // onUpdate здесь
});
Однако внутри таймлайна структура вложенная. Объект tween внутри события — это почти полный аналог конфига для this.tweens.add(), но с важным исключением: глобальные колбэки (как onUpdate) для всего твина в этом контексте не поддерживаются. Вместо этого они должны быть привязаны к конкретному свойству.
Что попробовать дальше
Главный вывод: при работе с Phaser.Tweens.Timeline размещайте колбэк onUpdate внутри конфигурации анимируемого свойства (например, alpha, `x,scale), а не на корневом уровне объектаtween`. Это обеспечит его корректный вызов.
Для экспериментов попробуйте:
1. Добавить в таймлайн несколько событий с разными onUpdate, чтобы отслеживать анимацию нескольких свойств независимо.
2. Использовать onUpdate для привязки логики игры к прогрессу анимации (например, менять цвет объекта в зависимости от tween.progress).
3. Поэкспериментировать с другими колбэками, такими как onComplete, внутри свойства, чтобы понять общий принцип.
