Твины
Система твинов Phaser 4 — интерполяция любого числового свойства во времени. Цели, функции плавности (easing), цепочки, твины значений и подводные камни.
Твин интерполирует значение во времени. Вы направляете твин на один или несколько объектов и указываете, какие свойства менять, как и за какую длительность — Phaser берёт на себя покадровые вычисления.
Твины — самая часто используемая утилита в коде на Phaser: затухания, плавные перемещения камеры, отскок UI, появление диалогов, анимации значений, управляющие пользовательской логикой.
Форма твина
this.tweens.add({
targets: sprite,
x: 400,
duration: 1000,
});
Читается так: «перемести sprite.x к 400 за 1000 мс, используя функцию плавности по умолчанию». Phaser захватывает текущее значение sprite.x в момент старта твина и интерполирует от него.
Цели
Один твин может управлять любым количеством объектов одновременно:
targets: sprite // один объект
targets: [s1, s2, s3] // несколько
targets: group.getChildren() // члены группы
targets: { value: 0 } // произвольный обычный объект — см. «Твины значений» ниже
Свойства
Подходит любое числовое свойство. Phaser интерполирует от текущего значения к целевому.
this.tweens.add({
targets: sprite,
x: '+=50', // ОТНОСИТЕЛЬНОЕ — прибавляет 50 к текущему значению
alpha: 0, // твин к 0
scale: { from: 2, to: 1 }, // явные начало + конец
angle: { value: 360, ease: 'Cubic.Out' }, // переопределение для отдельного свойства
duration: 600,
});
Относительные цели ('+=50', '-=50', '*=2') вычисляются в момент старта — это полезно, когда абсолютная цель зависит от того, какое значение свойство имеет на момент запуска твина.
Функции плавности (easing)
ease: 'Linear' // значение по умолчанию при отсутствии тоже близко к Linear
ease: 'Sine.InOut'
ease: 'Cubic.Out'
ease: 'Bounce.Out'
ease: 'Back.InOut'
ease: 'Elastic.Out'
Полный набор находится в Phaser.Math.Easing.*. У каждого семейства есть варианты .In, .Out и .InOut. Выбирайте Out для естественно ощущающихся прибытий (объекты замедляются к своей цели); In — для отбытий; InOut — для симметричного движения.
Длительность, задержка, повтор, yoyo
this.tweens.add({
targets: sprite,
y: '+=50',
duration: 400,
delay: 200, // подождать 200 мс перед стартом
yoyo: true, // проиграть вперёд, затем назад
repeat: -1, // -1 = бесконечно; положительное N = N дополнительных проигрываний
repeatDelay: 100,
});
yoyo: true удваивает эффективную длительность одного цикла — твин на 400 мс становится 800 мс (вперёд + назад). hold: N делает паузу на N мс в точке yoyo, если нужна задержка.
Колбэки
this.tweens.add({
targets: sprite,
x: 400,
duration: 1000,
onStart: () => this.sound.play('whoosh'),
onUpdate: (_tween, target) => { /* каждый кадр */ },
onYoyo: () => { /* достигли точки yoyo */ },
onRepeat: () => { /* начался новый цикл */ },
onComplete: () => sprite.destroy(),
});
Для сценария «проиграть один раз и убрать» паттерн onComplete: () => target.destroy() — самый чистый.
Живой пример
Пять блоков с разбросом по времени и плавностью Bounce.Out, зацикленные через yoyo + repeat: -1.
this.tweens.stagger(step, options) возвращает функцию, которая вычисляет задержку для каждой цели — 0, step, 2*step, … Это самый чистый способ распределить эффект по группе.
Цепочки
Для сценария «сделай A, затем B, затем C» используйте chain:
this.tweens.chain({
targets: sprite,
tweens: [
{ x: 400, duration: 500, ease: 'Cubic.Out' },
{ y: 200, duration: 300 },
{ angle: 360, duration: 800, ease: 'Sine.InOut' },
],
});
chain — это более простой преемник timeline из v3. Каждая дочерняя запись — это обычный конфиг твина: функция плавности, колбэки, yoyo, всё это.
Твины значений
Когда нужно интерполировать что-то, до чего Phaser не может добраться через присваивание свойства — юниформ шейдера, CSS-переменную, значение в сторонней библиотеке — делайте твин обычного объекта и считывайте его в onUpdate:
const state = { mix: 0 };
this.tweens.add({
targets: state,
mix: 1,
duration: 2000,
ease: 'Cubic.InOut',
onUpdate: () => myShader.setUniform('uMix', state.mix),
});
Этот паттерн работает для чего угодно: плавного изменения громкости звука, анимации смешивания RenderTexture, затухания DOM-оверлея.
Остановка и управление твинами
const t = this.tweens.add({ targets: sprite, x: 400, duration: 1000 });
t.pause();
t.resume();
t.stop(); // остановить и уничтожить
t.seek(500); // перейти на отметку 500 мс
t.timeScale = 2; // проигрывать на двойной скорости
this.tweens.killTweensOf(sprite); // остановить каждый твин, затрагивающий этот объект
this.tweens.killAll(); // радикальный вариант
Для ответа на вопрос «выполняется ли сейчас твин этого объекта?» используйте this.tweens.isTweening(sprite).
Типичные ошибки
Не делайте твин позиции напрямую на физических объектах. Твины пишут .x/.y, минуя физическое тело. Тело рассинхронизируется до следующего шага физики. Либо делайте твин body.velocity.*, либо отключайте тело на время твина.
Мусорные твины. Твины остаются зарегистрированными после завершения, если не указать им очиститься. Для одноразовых эффектов, запускаемых в горячем цикле, используйте onComplete: () => tween.remove() — или используйте this.tweens.add({ ..., persist: false }), чтобы менеджер сбрасывал их автоматически.
Привязка к сцене. Твины живут на this.tweens — менеджере твинов сцены. Они останавливаются, когда останавливается сцена. Межсценовых твинов не существует — вместо этого моделируйте общее состояние.
Математика плавности применяется к каждому свойству. Твин с x и alpha будет использовать одну и ту же функцию плавности для обоих, если не переопределить её для каждого свойства. Если нужны разные кривые, разбейте на два твина или используйте форму с объектом для отдельного свойства.
Связанное
- Анимация — покадровая анимация спрайтов; дополняет твины (которые интерполируют свойства).
- Игровой цикл — твины продвигаются каждый кадр, используя
delta, поэтому они независимы от частоты кадров без дополнительных усилий. - Камеры — эффекты камеры (
shake,fade,pan,zoomTo) под капотом являются твинами.