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

Как я учился программировать: Пропорция и твининг

Оглавление

Продолжим тему трудностей и озарений начинающего программиста.

Предыдущая часть:

Как всегда, речь пойдёт о чём-то простом и самоочевидном, но только не тогда, когда сталкиваешься с этим в первый раз.

В школе давали примерно такую формулу:

5 / 2 = x / 5

Это формула пропорции. Она говорит о том, что 5 и 2 находятся в таком же соотношении, как x и 5. Для неё показывали способ решения, основанный на визуальном расположении аргументов:

Отношения нужно записать в виде вертикальных дробей, затем умножить два числа по диагонали, где нет x, и разделить на число по диагонали, где есть x.

  • x = 5 * 5 / 2 = 12.5
  • 12.5 / 5 = 2.5

Я кстати не помню, почему его давали именно в таком виде, потому что это же сводится к простому уравнению x / 5 = 2.5, откуда следует, что x = 5 * 2.5.

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

Когда нужна пропорция?

Впервые я столкнулся с пропорцией, когда захотел нарисовать градусник для игры "холодно-горячо".

Ход рассуждений был таким. У градусника есть полоска, которая укорачивается или удлиняется. Чем ближе игрок к цели, тем "горячее", и тем длиннее полоска.

-2

Как именно сопоставить расстояние до цели и длину полоски?

Когда расстояние равно нулю, это максимальная температура. А когда расстояние максимально, тогда температура равна нулю.

Исходя из размеров карты, скажем 20*20, можно принять, что максимальное расстояние до цели составляет 20 клеток. Значит, можно нарисовать полоску градусника размером в 20 клеток. Зависимость будет очень простая – сколько клеток осталось до цели, столько же клеток останется незакрашено в градуснике.

Но при таком подходе, если вдруг размер карты станет 1000*1000, то и размер градусника придётся сделать 1000. И он просто не поместится на экран.

Даже при размере карты 20*20 не нужен настолько же большой градусник. Хватит и длины полоски в 10 клеток, то есть в 2 раза меньше. И так я пришёл к первому выводу: количество незакрашенных клеток градусника можно вычислять как расстояние / 2.

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

Или по-другому: незакрашенная часть градусника относится к оставшемуся расстоянию так же, как длина градусника относится к максимальному расстоянию.

-3

После этого открылось широкое поле применений: это и полоска здоровья героя, и прогресс загрузки файлов, и расчёт наносимого урона.

Твининг

В анимации есть ключевые кадры. Это начальное и конечное положение какого-либо объекта. Между ключевыми кадрами вставляют промежуточные, которые должны создавать иллюзию движения. Сам процесс называется "inbetweening", буквально "вставка между", а сокращённо "tweening", или по-нашему твининг.

Самый простой вид твининга – линейный. Если между двумя ключевыми кадрами объект прошёл расстояние D за 1 секунду, то мы можем вставить, к примеру, 10 промежуточных положений объекта, которые будут находиться в моментах времени 0.1с, 0.2с, 0.3с и т.д., а пройденная дистанция будет равна 0.1D, 0.2D, 0.3D и т.д.

Как можно видеть, тут тоже используется пропорция: пройденная дистанция пропорциональна затраченному времени.

Но такой вид движения в анимации неприемлем именно в силу своей линейности. Все объекты подчиняются законам физики и поэтому не могут мгновенно набирать скорость и останавливаться.

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

Тут наша игра с пропорциями поднимается на новый уровень: время идёт линейно, то есть те же 0.1с, 0.2с, 0.3с и т.д., а положение объекта изменяется нелинейно.

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

-4

Если взять часть волны от -1/2π до 1/2π, то можно представить, что что x это время, а sin(x) это положение в пространстве. Время идёт от -1/2π до 1/2π, а положение меняется от -1 до 1.

Очевидно, что при линейном приращении времени положение меняется нелинейно: сначала величина приращения вообще нулевая, затем она постепенно увеличивается и в точке 0 на графике достигает максимума. После этого она начинает уменьшаться и в самом конце приходит к нулю.

То есть мы получили то самое движение, которое сначала разгоняется, а потом тормозит. Надо только привести все параметры в соответствие.

Пример: объект проходит дистанцию 150 пикселов за 3 секунды.

Наши 3 секунды это интервал от -1/2π до 1/2π на графике. Длина этого интервала равна π. А дистанция 150 пикселов это интервал значений синуса от -1 до 1. Длина этого интервала равна 2.

Преобразуем время t в параметр синуса x с помощью пропорции:

x = -1/2π + (π * t / 3)

Убедимся, что при t = 0 x = -1/2π, а при t = 3 x = 1/2π, то есть начало и конец времени совпадают с началом и концом интервала.

Вычислим синус в виде параметра s и приведём его интервал -1..1 к более удобному 0..2:

s = sin(x) + 1

Теперь сделаем пропорцию, в которой пройденное расстояние 0..150 пропорцинально значению 0..2:

dist = 150 * s / 2

Также убедимся, что при s = 0 dist = 0, а при s = 2 dist = 150, т.е. начало и конец дистанции совпадают с границами интервала синуса.

Теперь можем написать общую формулу дистанции для каждого момента t в твининге:

dist = 150 * (sin(-1/2π + (π * t / 3)) + 1) / 2

Кроме синуса, есть и другие способы задания нелинейного движения:

-5

Анимацию Дзен может не воспроизводить, поэтому смотрите здесь.