О чем этот пример
Плавные переходы и анимации — ключ к созданию приятного игрового опыта. Phaser предлагает мощную систему твинов с различными функциями плавности (easing). В этой статье мы разберем пример визуализации одной из них — Exponential Ease. Понимание того, как работают разные типы плавностей (`expo.in`, `expo.out`, `expo.inout`), поможет вам создавать более естественные и выразительные анимации для движения персонажей, появления интерфейса или любых других изменений параметров в игре. Мы наглядно увидим, как объект движется по нелинейной траектории.
Версия 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('bg', 'assets/tweens/background-crt.jpg');
}
create ()
{
this.add.image(400, 300, 'bg').setScale(0.78);
this.add.text(400, 28, 'Exponential Ease').setColor('#00ff00').setFontSize(32).setShadow(2, 2).setOrigin(0.5, 0);
const types = [ 'expo.in', 'expo.out', 'expo.inout' ];
let type = 0;
let tween;
const label = this.add.text(400, 530).setColor('#00ff00').setFontSize(22).setShadow(1, 1).setOrigin(0.5, 0).setAlign('center');
const graph = this.add.graphics();
const rect = this.add.rectangle(100, 400, 2, 2, 0x00ff00);
const rt = this.add.renderTexture(400, 300, 800, 600);
const graphEase = () => {
if (tween)
{
tween.stop();
}
rt.clear();
graph.clear();
graph.lineStyle(3, 0x00ff00);
graph.beginPath();
rect.setPosition(50, 450);
label.setText([
types[type],
'Click to change type'
]);
tween = this.tweens.add({
targets: rect,
x: { value: 750, ease: 'linear' },
y: { value: 100, ease: types[type] },
duration: 4000,
onUpdate: (tween, target, key) => {
if (key === 'x')
{
rt.draw(rect);
graph.lineTo(rect.x, rect.y);
}
},
onComplete: () => {
graph.stroke();
}
});
}
this.input.on('pointerdown', () => {
type++;
if (type === types.length)
{
type = 0;
}
graphEase();
});
graphEase();
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов и базовая настройка
Как и в любой сцене Phaser, работа начинается с методов preload и create. В preload мы загружаем фоновое изображение, а в create — создаем все необходимые объекты для демонстрации.
Мы добавляем фоновую картинку, заголовок и массив с названиями типов плавности, которые будем переключать. Также создаются объекты для визуализации: Graphics для рисования графика, маленький зеленый прямоугольник (rect), который будет двигаться, и RenderTexture (rt) для отрисовки его следа.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bg', 'assets/tweens/background-crt.jpg');
}
create ()
{
this.add.image(400, 300, 'bg').setScale(0.78);
this.add.text(400, 28, 'Exponential Ease').setColor('#00ff00').setFontSize(32).setShadow(2, 2).setOrigin(0.5, 0);
const types = [ 'expo.in', 'expo.out', 'expo.inout' ];
let type = 0;
let tween;
const label = this.add.text(400, 530).setColor('#00ff00').setFontSize(22).setShadow(1, 1).setOrigin(0.5, 0).setAlign('center');
const graph = this.add.graphics();
const rect = this.add.rectangle(100, 400, 2, 2, 0x00ff00);
const rt = this.add.renderTexture(400, 300, 800, 600);
}
Логика анимации и отрисовки графика
Основная работа происходит в функции graphEase. Она запускается при старте и при каждом клике, перезапуская анимацию с новым типом плавности.
Функция сначала останавливает предыдущий твин (если он есть) и очищает RenderTexture и Graphics, чтобы начать рисовать заново. Затем она настраивает стиль линии для графика и сбрасывает позицию зеленого прямоугольника в стартовую точку.
Самое важное — создание твина для объекта rect. Мы задаем два свойства для анимации: `xиy. Движение по оси X использует линейную плавность ('linear'), чтобы перемещение было равномерным. Движение по оси Y использует выбранный тип экспоненциальной плавности из массиваtypes`. Это и создает кривую траекторию.
const graphEase = () => {
if (tween)
{
tween.stop();
}
rt.clear();
graph.clear();
graph.lineStyle(3, 0x00ff00);
graph.beginPath();
rect.setPosition(50, 450);
label.setText([ types[type], 'Click to change type' ]);
tween = this.tweens.add({
targets: rect,
x: { value: 750, ease: 'linear' },
y: { value: 100, ease: types[type] },
duration: 4000,
onUpdate: (tween, target, key) => {
if (key === 'x')
{
rt.draw(rect);
graph.lineTo(rect.x, rect.y);
}
},
onComplete: () => {
graph.stroke();
}
});
}
Обработка обновлений и завершения твина
Ключевую роль в визуализации играют коллбэки твина onUpdate и onComplete.
Коллбэк onUpdate вызывается на каждом кадре анимации. Параметр key указывает, какое свойство объекта было обновлено. В нашем коде логика отрисовки срабатывает только при обновлении координаты `x. Это сделано для того, чтобы точка на графике добавлялась один раз за кадр, а не дважды (отдельно дляxиy`). При каждом таком обновлении мы:
1. Рисуем текущее состояние прямоугольника rect в RenderTexture (rt.draw(rect)). Это создает "шлейф" из точек.
2. Добавляем новую точку в путь объекта Graphics с помощью graph.lineTo(rect.x, rect.y).
Коллбэк onComplete срабатывает по окончании анимации. В нем мы вызываем graph.stroke(), чтобы окончательно отрисовать накопленный путь (график функции плавности) на экране.
onUpdate: (tween, target, key) => {
if (key === 'x')
{
rt.draw(rect);
graph.lineTo(rect.x, rect.y);
}
},
onComplete: () => {
graph.stroke();
}
Интерактивность: переключение типов плавности
Чтобы наглядно сравнить разные типы плавности, в примере реализована простая интерактивность. При клике мышью (pointerdown) увеличивается индекс type, который выбирает функцию из массива types. Когда индекс достигает конца массива, он сбрасывается в ноль.
После изменения типа вызывается функция graphEase(), которая перезапускает анимацию и отрисовку с новыми параметрами. Таким образом, пользователь может циклически перебирать expo.in, expo.out и expo.inout, наблюдая за изменением формы графика.
this.input.on('pointerdown', () => {
type++;
if (type === types.length)
{
type = 0;
}
graphEase();
});
Что попробовать дальше
Этот пример — отличная отправная точка для работы с твинами в Phaser. Вы научились применять разные функции плавности, использовать коллбэки onUpdate и onComplete для сложной логики и визуализации, а также создавать интерактивные демонстрации.
**Идеи для экспериментов:**
1. Замените types на другие функции плавности, например, 'sine.in', 'back.out' или 'bounce.inout'.
2. Попробуйте анимировать другие свойства, например, scale или alpha, и понаблюдайте за разницей в поведении.
3. Используйте RenderTexture для создания эффекта "светлячка" или рисования траектории снаряда в реальной игре.
4. Создайте интерфейс выбора плавности (кнопки, селект) вместо переключения по клику.
