О чем этот пример
Анимация — это не просто перемещение объекта из точки А в точку Б. Это ощущение массы, упругости и инерции. Phaser предоставляет разработчикам 32 встроенных уравнения плавности (easing equations), которые превращают механическое движение в живое и выразительное. В этой статье мы разберем готовый пример, который наглядно демонстрирует все доступные варианты, и научимся применять их для создания профессиональной игровой анимации с помощью системы твинов. Понимание и умелое применение easing-функций — ключ к тому, чтобы игровые персонажи двигались естественно, интерфейсные элементы появлялись элегантно, а физические взаимодействия ощущались «правильно». Это фундаментальный навык для любого геймдев-разработчика, работающего с 2D.
Версия 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('bar', 'assets/sprites/bluebar.png');
}
create() {
// There are 32 built-in easing equations including Elastic which is shown in the Elasticity example
var eases = [
'Linear',
'Quad.easeIn',
'Cubic.easeIn',
'Quart.easeIn',
'Quint.easeIn',
'Sine.easeIn',
'Expo.easeIn',
'Circ.easeIn',
'Back.easeIn',
'Bounce.easeIn',
'Quad.easeOut',
'Cubic.easeOut',
'Quart.easeOut',
'Quint.easeOut',
'Sine.easeOut',
'Expo.easeOut',
'Circ.easeOut',
'Back.easeOut',
'Bounce.easeOut',
'Quad.easeInOut',
'Cubic.easeInOut',
'Quart.easeInOut',
'Quint.easeInOut',
'Sine.easeInOut',
'Expo.easeInOut',
'Circ.easeInOut',
'Back.easeInOut',
'Bounce.easeInOut'
];
const stepY = 19
// labels
for (const [index, easeFnName] of eases.entries()) {
this.add.text(140, 23 + (index * stepY), easeFnName).setOrigin(1, 0).setFontSize(14)
}
var markers = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY }, setAlpha: { value: 0.3 } });
var images = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY } });
var _this = this;
images.children.forEach(function (child) {
_this.tweens.add({
targets: child,
x: 700,
ease: eases.shift(),
duration: 1500,
delay: 1000,
yoyo: true,
repeat: -1,
repeatDelay: 1000,
hold: 1000
});
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ассетов
В методе preload() происходит базовая настройка загрузчика и загрузка единственного спрайта — синей полоски (bluebar.png). Этот спрайт будет использован как визуальный маркер для анимации.
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bar', 'assets/sprites/bluebar.png');
}
Библиотека встроенных уравнений плавности
Ядро примера — массив eases, содержащий строковые имена 32 встроенных функций плавности. Phaser группирует их по типам: Quad, Cubic, Back, Bounce и другие. Каждый тип представлен тремя вариациями:
* easeIn: Ускорение в начале анимации.
* easeOut: Замедление в конце анимации.
* easeInOut: Сочетание ускорения и замедления.
Первым в списке идет 'Linear' — равномерное, лишенное ускорения движение, которое служит точкой отсчета.
var eases = [
'Linear',
'Quad.easeIn',
'Cubic.easeIn',
// ... остальные уравнения
'Bounce.easeInOut'
];
Создание визуальной легенды
Чтобы было понятно, какая анимация соответствует какому уравнению, для каждого элемента массива создается текстовый лейбл. Метод this.add.text() размещает текст с выравниванием по правому краю (.setOrigin(1, 0)). Координата Y для каждого лейбла рассчитывается с помощью шага stepY, что создает ровный столбец.
const stepY = 19;
for (const [index, easeFnName] of eases.entries()) {
this.add.text(140, 23 + (index * stepY), easeFnName).setOrigin(1, 0).setFontSize(14);
}
Далее создаются две идентичные группы спрайтов-полосок с помощью this.add.group(). Первая группа (markers) служит статичным фоном с полупрозрачными полосками (.setAlpha: { value: 0.3 }), чтобы обозначить стартовую позицию. Вторая группа (images) содержит такие же полоски, но они будут анимированы.
var markers = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY }, setAlpha: { value: 0.3 } });
var images = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY } });
Применение анимации к каждому спрайту
Здесь происходит самое важное — создание твинов. В цикле forEach для каждого спрайта из группы images создается отдельная анимация с помощью this.tweens.add().
Ключевой параметр — ease. Для каждого следующего спрайта из массива eases забирается (.shift()) и подставляется новое уравнение плавности. Таким образом, каждая полоска в столбце анимируется по своему уникальному закону.
Параметры твина:
* targets: Цель анимации — текущий спрайт (child).
* x: 700: Конечная точка перемещения по оси X.
* duration: 1500: Длительность одного движения в миллисекундах.
* yoyo: true: После достижения конечной точки анимация проигрывается в обратном порядке.
* repeat: -1: Бесконечное повторение.
* delay, repeatDelay, hold: Эти параметры создают паузы, формируя четкий, легко читаемый цикл "старт-движение-пауза-возврат-пауза".
images.children.forEach(function (child) {
_this.tweens.add({
targets: child,
x: 700,
ease: eases.shift(), // Берём следующее уравнение из массива
duration: 1500,
delay: 1000,
yoyo: true,
repeat: -1,
repeatDelay: 1000,
hold: 1000
});
});
Что попробовать дальше
Этот пример — идеальная песочница для изучения easing-функций. Запустите его и понаблюдайте: Bounce имитирует отскок, Back создает эффект "перелета" за целевую точку с возвратом, Elastic похож на колебания пружины. Для экспериментов попробуйте изменить параметры твина: увеличьте duration, чтобы лучше увидеть разницу в кривых, или поменяйте свойство с `xнаscaleX`, чтобы анимировать масштаб. Понимание этих кривых позволит вам оживить любой объект в вашей игре, от плавного появления меню до реалистичного прыжка персонажа.
