~2 мин чтения

Твины

Система твинов 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.

Staggered bouncing blocks Phaser 4 · sandboxed

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) под капотом являются твинами.