О чем этот пример
При разработке игр часто требуется запускать анимации не сразу, а по сложному расписанию: сначала один объект двигается в точку А, через 2 секунды начинает колебаться другой, а еще через секунду включается повторяющийся эффект. Прописывать все это вручную через таймеры и колбэки — долго и сложно для поддержки. Timeline (временная шкала) в Phaser 3 решает эту задачу элегантно, позволяя описывать последовательность твинов (анимаций) с привязкой к конкретным временным меткам в единой декларативной структуре. В этой статье разберем пример с пауком, которого Timeline заставляет спускаться на паутине, а затем раскачиваться из стороны в сторону, и научимся создавать свои сложные сценарии анимаций.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('timeline', 'assets/atlas/timeline.png', 'assets/atlas/timeline.json');
this.load.image('bg', 'assets/skies/spookysky.jpg');
}
create ()
{
this.add.image(400, 300, 'bg');
this.thread = this.add.graphics();
this.spider = this.add.sprite(400, -100, 'timeline', 'spider');
const timeline = this.add.timeline([
{
at: 2000,
tween: {
targets: this.spider,
y: 400,
ease: 'bounce.out',
duration: 1500
}
},
{
at: 4000,
tween: {
targets: this.spider,
x: 200,
angle: 30,
ease: 'sine.out',
duration: 1000,
yoyo: true,
repeat: -1,
repeatDelay: 2000
}
},
{
at: 6000,
tween: {
targets: this.spider,
x: 600,
angle: -30,
ease: 'sine.out',
duration: 1000,
yoyo: true,
repeat: -1,
repeatDelay: 2000
}
}
]);
timeline.play();
}
update ()
{
this.thread.clear();
this.thread.lineStyle(1, 0xffffff, 0.7);
this.thread.lineBetween(400, 0, this.spider.x, this.spider.y);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое Timeline и зачем он нужен
Timeline — это менеджер анимаций, встроенный в Phaser. В отличие от одиночного твина, который выполняет одну анимацию, Timeline позволяет создать список действий (actions), каждое из которых привязано к определенному моменту времени на шкале. Это похоже на монтажный стол в видеоредакторе: вы расставляете клипы (твины) на временной линии, и они воспроизводятся в заданное время.
Основные преимущества:
* **Централизованное управление**: Вся логика последовательности анимаций описана в одном месте — объекте конфигурации timeline.
* **Точное планирование**: Каждое действие имеет параметр at — время в миллисекундах от старта таймлайна, когда оно должно начаться.
* **Автоматический запуск**: Достаточно вызвать метод play(), и вся последовательность начнет воспроизводиться согласно плану.
* **Читаемость**: Код становится намного чище и проще для понимания по сравнению с вложенными setTimeout или цепочками событий onComplete.
Разбор структуры Timeline
В примере Timeline создается в методе create сцены. Давайте посмотрим на его конфигурацию.
const timeline = this.add.timeline([
{
at: 2000,
tween: {
targets: this.spider,
y: 400,
ease: 'bounce.out',
duration: 1500
}
},
// ... другие действия
]);
Ключевые моменты:
1. `this.add.timeline()` — фабричный метод для создания таймлайна. Он принимает массив объектов-действий.
2. Каждое действие — это объект с обязательным полем `at` (время старта в мс) и одним из свойств, определяющих тип действия (в нашем случае — `tween`).
3. Объект `tween` внутри действия имеет ту же структуру, что и конфиг для обычного твина, создаваемого через `this.tweens.add()`. Вы можете использовать все знакомые параметры: `targets`, свойства для анимации (`x`, `y`, `angle`, `alpha` и т.д.), `ease`, `duration`, `yoyo`, `repeat`.
В данном примере определены три действия:
* **На 2000 мс**: Паук (this.spider) за 1500 мс спускается по оси Y до позиции 400 с эффектом отскока (bounce.out).
* **На 4000 мс**: Паук начинает бесконечно повторяющуюся (repeat: -1) анимацию движения к X=200 и наклона на 30 градусов с эффектом sine.out. Анимация длится 1000 мс, после чего воспроизводится в обратном порядке (yoyo: true), а перед следующим повторением ждет 2000 мс (repeatDelay: 2000).
* **На 6000 мс**: Аналогичное бесконечное действие, но паук движется к X=600 и наклоняется на -30 градусов. Обратите внимание, что оба колебательных действия стартуют в разное время и работают параллельно, создавая сложную траекторию.
Визуализация связи: графика в update
Чтобы визуально связать паука с точкой его старта, в примере используется графика (Graphics). В методе update каждый кадр рисуется линия, имитирующая паутину.
update ()
{
this.thread.clear();
this.thread.lineStyle(1, 0xffffff, 0.7);
this.thread.lineBetween(400, 0, this.spider.x, this.spider.y);
}
Пояснение кода:
1. `this.thread.clear()` — очищает графику от нарисованного в предыдущем кадре. Без этого вы увидите "шлейф" из всех предыдущих линий.
2. `this.thread.lineStyle(1, 0xffffff, 0.7)` — задает стиль линии: толщина 1 пиксель, цвет белый (`0xffffff`), прозрачность 70% (`0.7`).
3. `this.thread.lineBetween(400, 0, this.spider.x, this.spider.y)` — рисует линию от фиксированной точки вверху экрана (400, 0) до текущих координат паука. Поскольку `this.spider.x` и `this.spider.y` непрерывно меняются твинами из Timeline, линия следует за пауком, создавая эффект паутины.
Это отличный пример динамической графики, которая реагирует на состояние игровых объектов.
Практическое применение: идеи для ваших игр
Timeline — мощный инструмент не только для декоративных анимаций.
* **Сценарии кат-сцен**: Описать появление персонажей, их движение и диалоговые реплики по времени. * **Фазы босса**: Задать последовательность атак: через 5 секунд — залп снарядов, через 10 — призыв мобов, через 15 — мощный луч. * **Сложные UI-анимации**: Последовательное появление элементов меню, тряска кнопок, исчезновение всплывающих окон. * **Кооперативные паззлы**: Активация механизмов уровня с задержкой друг относительно друга.
Главное — помнить, что время at отсчитывается от момента вызова timeline.play(). Если вам нужно запустить таймлайн не с нуля, изучить методы pause, seek или stop.
Что попробовать дальше
Timeline в Phaser 3 — это ваш союзник в создании сложных, синхронизированных во времени анимаций без головной боли с таймерами. Он превращает spaghetti-код из колбэков в чистый, декларативный и легко редактируемый план действий.
**Идеи для экспериментов:**
1. Добавьте в Timeline действие с типом event (не показано в примере, но есть в API) для вызова пользовательской функции в заданный момент, например, для воспроизведения звука скрипа паутины.
2. Создайте два независимых Timeline для разных групп объектов и запустите их с задержкой друг относительно друга.
3. Используйте timeline.seek(3000), чтобы начать воспроизведение не с начала, а с 3-й секунды сценария.
4. Свяжите прогресс Timeline с ползунком на экране, чтобы вручную перематывать анимацию.
