Найти в Дзене
ZDG

Программируем твининг для светлячков

Термин "твининг" (tweening) пришёл из анимации. Как вы вероятно знаете, анимация это очень тяжёлая работа. Чтобы получить всего 1 секунду анимации, нужно нарисовать 24 кадра (ну или 12-16). Поэтому было придумано разделение труда: опытные художники рисовали т.н. ключевые кадры, расположенные через некоторые интервалы времени, а подмастерья занимались дорисовкой промежуточных кадров между ними. Это и есть твининг. Когда делают современный 3D-видеоролик или игру, там вручную уже не рисуют, но твининг играет ту же роль. Допустим, чтобы объект переместился из позиции А в позицию Б, вы задаёте два ключевых кадра. Первый ключевой кадр это объект в позиции А, а второй ключевой кадр это объект в позиции Б. Все промежуточные позиции между А и Б рассчитываются автоматически с помощью твининга. Так как анимация работает на фиксированной частоте кадров (например, 24 кадра в секунду), то количество кадров между А и Б определяет время, которое нужно на перемещение. Ну или наоборот, время на перемеще

Термин "твининг" (tweening) пришёл из анимации. Как вы вероятно знаете, анимация это очень тяжёлая работа. Чтобы получить всего 1 секунду анимации, нужно нарисовать 24 кадра (ну или 12-16).

Поэтому было придумано разделение труда: опытные художники рисовали т.н. ключевые кадры, расположенные через некоторые интервалы времени, а подмастерья занимались дорисовкой промежуточных кадров между ними. Это и есть твининг.

Когда делают современный 3D-видеоролик или игру, там вручную уже не рисуют, но твининг играет ту же роль. Допустим, чтобы объект переместился из позиции А в позицию Б, вы задаёте два ключевых кадра. Первый ключевой кадр это объект в позиции А, а второй ключевой кадр это объект в позиции Б. Все промежуточные позиции между А и Б рассчитываются автоматически с помощью твининга.

Так как анимация работает на фиксированной частоте кадров (например, 24 кадра в секунду), то количество кадров между А и Б определяет время, которое нужно на перемещение. Ну или наоборот, время на перемещение определяет количество кадров. Это взаимозаменяемые величины. Если А и Б идут прямо друг за другом, то промежуточных кадров между ними нет и объект переместится за 1/24 секунды. Если между А и Б 11 промежуточных кадров, то объект переместится за 1/2 секунды, и т.д.

Начнём делать симулятор светлячка. А проще говоря, будем хаотично двигать по чёрному экрану зелёный огонёк.

Нам нужны следующие параметры:

  • точка старта (x0, y0)
  • точка финиша (x1, y1)
  • время движения (t)
  • текущее прошедшее время (progress)
  • текущие координаты (x, y)

Для того, чтобы результат можно было посмотреть онлайн, я буду писать на JavaScript. Сразу сделаю класс для твин-объекта:

Данный код позволит создавать твин-объекты, указывая в конструкторе все необходимые параметры, например так:

var tween = new Tween(100, 100, 200, 200, 60);

Это будет значить: переместиться из координат (100, 100) в координаты (200, 200) за 1 секунду. На самом деле я передаю не время, а количество кадров. Но я считаю, что анимация воспроизводится со скоростью 60 кадров в секунду, поэтому передаю 60, чтобы получить 1 секунду.

Теперь нужно сделать собственно движение. Каждый кадр твин-объект должен обновлять свои координаты:

  1. Увеличить значение текущего кадра (progress)
  2. Если достигнуто финальное время (progress == t), то закончить твининг
  3. Вычислить коэффициент progress / t. Он будет всегда в пределах от 0 до 1. В начале движения он 0, а в конце 1.
  4. Применить коэффициент к начальным координатам, чтобы получить текущие.

Разница между начальной и конечной координатой, скажем, между x0 и x1, это (x1 - x0). Чтобы из x0 получилось x1, очевидно нужно сложить x0 + (x1 - x0). Если разницу (x1 - x0) умножить на коэффициент прогресса, то мы получим промежуточное значение, где должен находиться x. То же самое сделаем и для координаты y:

И немедленно проверим, что получилось.

С помощью браузерного метода requestAnimationFrame() я организовал цикл вызова функции update() в каждом кадре. В свою очередь эта функция вызывает метод update() у объекта tween, который и выполняет свою работу.

Я также добавил в класс Tween метод continueTo(). При достижении финальной точки я отправляю твин-объект в новое путешествие, делая эту точку начальной и задавая новую финальную точку.

Как можно видеть, твин работает, но на полёт светлячка это пока мало похоже. Движение совершается с постоянной скоростью – объект резко стартует и так же резко останавливается.

Для того, чтобы сделать движения более естественными, были придуманы easing-функции. Как это перевести, я не знаю, официального перевода не нашёл. Ослабление? Типа того. Короче говоря, easing-функция задаёт параметры движения.

Допустим, мы хотим, чтобы объект плавно разгонялся в начале движения и плавно тормозил в конце. Координаты движения сейчас линейно зависят от прогресса во времени. Это значит, что когда прогресс равен 0.1, пройденная дистанция равна 0.1 от общей. Когда прогресс равен 0.5, то пройденная дистанция равна 0.5, и т.д. Задача же стоит такая, чтобы при линейном прогрессе сделать нелинейное приращение дистанции.

И когда речь идёт об easing-функции, то речь буквально о том, чтобы выбрать какую-либо математическую функцию, которая даст нужный эффект. В нашем случае отлично подходит обыкновенный синус:

Нас интересует часть графика от -𝜋/2 до 𝜋/2. Если мы примем ось x за время, которое движется линейно, то координата y на этом отрезке сначала медленно поднимается от значения -1, потом "ускоряется" до отметки 0, затем снова тормозит и на значении 1 затормаживает совсем.

Итого, что нужно сделать:

  • Привязать отрезок -𝜋/2..𝜋/2 к прогрессу твина. Когда прогресс меняется от 0 до 1, аргумент синуса должен меняться от -𝜋/2 до 𝜋/2:
  • sin_arg = -Math.PI/2 + Math.PI * progress
  • Сделать значения синуса новым, актуальным прогрессом. Так как прогресс меняется от 0 до 1, а синус меняется от -1 до 1, нужно привести эти значения в диапазон 0..1:
  • p = (1 + Math.sin(sin_arg)) / 2

Обратите внимание, что мы здесь не вычисляем координаты x или y твин-объекта. Эти вычисления нужны только для того, чтобы получить нелинейный прогресс в виде обезличенного коэффициента 0..1. Теперь, умножая любую из координат твина на p, мы получим нелинейное движение.

Смотрим результат

Стало гораздо лучше.

Но должен признаться, что всё это пока далеко от реального полёта светлячка, так как движение по-прежнему прямолинейно, а насекомые летают по криволинейным траекториям. Но нашей целью сегодня было освоить твининг, а исправлению недостатков я посвящу следующий выпуск.

Пока что я сделаю следующее:

  • Добавлю много светлячков. Когда их много, это больше похоже на правду.
  • Сделаю так, чтобы они иногда садились на землю. Для этого им случайным образом нужно назначать координату y в самом низу экрана.
  • Добавлю ещё одну ease-функцию. Она такая же, как первая, но будет работать в диапазоне от 0 до 𝜋/2. Это даст резкий старт и плавное торможение в конце. Каждому светлячку будет случайным образом назначаться одна из двух функций.
-2

Смотрим результат

Вы можете посмотреть, какие ещё бывают easing-функции, по этой ссылке.

Читайте также: Параллакс в играх

Следующая часть: Кривые