Тема 5 из 6

Плавная анимация

Прогресс анимации

Любая анимация — это значение, которое меняется со временем. Удобнее всего считать прогресс p — долю пройденного, от 0 до 1:

p = (t - start) / duration
p = max(0, min(1, p))        # до начала — 0, после конца — 1

Ограничение обязательно: без него до начала анимации получится отрицательное значение, а после конца — больше единицы.

Интерполяция

По прогрессу находят текущее значение между начальным и конечным — линейная интерполяция (lerp):

def lerp(a, b, p):
    return a + (b - a) * p

label.opacity = int(lerp(0, 255, p))      # проявление
ball.x = lerp(100, 700, p)                # движение

У фигур и надписей pyglet есть opacity — прозрачность от 0 (невидимо) до 255; а также color, x, y, rotation, scale у спрайтов. Всё это можно анимировать.

Функции плавности

Если подставлять p напрямую, движение равномерное и выглядит механически. Функция плавности (easing) переделывает прогресс:

def ease_in(p):          # медленный старт
    return p * p

def ease_out(p):         # медленный финиш
    return 1 - (1 - p) ** 2

def ease_in_out(p):      # разгон и торможение
    if p < 0.5:
        return 2 * p * p
    return 1 - (2 - 2 * p) ** 2 / 2

ball.x = lerp(100, 700, ease_in_out(p))

У всех функций плавности f(0) = 0 и f(1) = 1 — начало и конец не меняются, меняется только «характер» движения между ними.

Ключевые кадры

Сложную анимацию описывают ключевыми кадрами — значениями в отдельные моменты:

X_KEYS = [(0, 100), (1.5, 650), (2.5, 650), (4, 100)]   # (время, x)

Чтобы найти значение в момент t, ищут пару соседних кадров t0 ≤ t &lt; t1 и интерполируют между ними с прогрессом (t - t0) / (t1 - t0). До первого кадра берут его значение, после последнего — значение последнего.

Два кадра с одинаковым временем — мгновенный скачок. Делить на ноль при этом нельзя, поэтому такой «отрезок» нулевой длины пропускают.

Итог

  • прогресс p = (t - start) / duration, ограниченный отрезком от 0 до 1;
  • lerp(a, b, p); анимировать можно x, y, opacity, color, radius…;
  • easing: ease_in, ease_out, ease_in_out — f(0) = 0, f(1) = 1;
  • ключевые кадры: найти отрезок и интерполировать; скачок — два кадра с одним временем.