Анимация и delta time

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

Delta time — это время (в секундах) с предыдущего кадра. Умножая скорость на delta, мы получаем движение, одинаковое при любом FPS.

Проблема фиксированного шага

В первом разделе мы писали cube.rotation.y += 0.01 каждый кадр. Это работает, пока у всех 60 кадров в секунду. Но на мониторе 144 Гц кадров в 2.4 раза больше — и куб закрутится в 2.4 раза быстрее. А если браузер просел до 30 FPS, всё замедлится. Анимация, привязанная к кадрам, ведёт себя по-разному на разном железе.

Решение: умножать на delta

Вместо «прибавить столько-то за кадр» говорим «двигаться со скоростью столько-то в секунду» и умножаем на длительность кадра. Покажем математику вживую: сравним поворот за секунду при 60 и 144 FPS со старым и новым подходом.

// Старый способ: фиксированный шаг за кадр
const stepPerFrame = 0.01;
console.log('Фикс. шаг за секунду при 60 FPS:', (stepPerFrame * 60).toFixed(2));
console.log('Фикс. шаг за секунду при 144 FPS:', (stepPerFrame * 144).toFixed(2));

// Новый способ: скорость * delta
const speed = 0.6; // радиан в секунду — целевая скорость
function rotatePerSecond(fps) {
  const delta = 1 / fps;        // длительность кадра
  return speed * delta * fps;   // суммарно за секунду
}
console.log('Через delta при 60 FPS:', rotatePerSecond(60).toFixed(2));
console.log('Через delta при 144 FPS:', rotatePerSecond(144).toFixed(2));

Вывод:

Фикс. шаг за секунду при 60 FPS: 0.60
Фикс. шаг за секунду при 144 FPS: 1.44
Через delta при 60 FPS: 0.60
Через delta при 144 FPS: 0.60

Старый способ даёт разную скорость (0.60 против 1.44!), а подход через delta — одинаковые 0.60 на любом мониторе. Это и есть цель.

THREE.Clock в реальном коде

В Three.js delta удобно брать из THREE.Clock: метод getDelta() возвращает секунды с прошлого вызова.

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  const delta = clock.getDelta();   // секунды с прошлого кадра
  cube.rotation.y += 0.6 * delta;   // 0.6 рад/сек на любом FPS

  renderer.render(scene, camera);
}
animate();

Правило

Любое движение, поворот, изменение со временем умножайте на delta. Думайте в единицах «в секунду», а не «за кадр». Это единственный способ, чтобы игра или анимация ощущались одинаково у всех пользователей.

Итог

  • delta — время с прошлого кадра; основа анимации, независимой от FPS.
  • Фиксированный шаг за кадр ускоряется на быстрых мониторах — так нельзя.
  • Берите delta из clock.getDelta() и умножайте на скорость «в секунду».
Проверьте себя
1. Что такое delta time?
AНомер текущего кадра
BВремя, прошедшее с предыдущего кадра (в секундах)
CЧастота монитора
DКоордината объекта
2. Почему движение через += 0.01 за кадр — плохая идея?
AЭто синтаксическая ошибка
BСкорость будет зависеть от FPS: на 144 Гц объект полетит вдвое быстрее, чем на 60 Гц
CОбъект станет невидимым
DЭто работает только с камерой
3. Что вернёт clock.getDelta() в Three.js?
AОбщее время с запуска
BСекунды, прошедшие с прошлого вызова — то самое delta
CЧисло кадров в секунду
DСлучайное число
Поддержать проект