Коротко
2D-сцена кажется живой, когда камера следует за героем плавно и смотрит чуть вперёд, фон разделён на слои с разной скоростью движения (параллакс), а каждое действие игрока получает мгновенный отклик: звук, частицы, короткое сжатие спрайта и редкую дозированную тряску. Плавность камеры считается по формуле 1 - exp(-k * dt), чтобы она не зависела от частоты кадров, а тряска — через убывающую величину trauma, возведённую в квадрат. В статье — формулы, таблицы коэффициентов и проверенный в Node.js код камеры, параллакса, сжатия спрайта и тряски.
Содержание
- Почему жёстко привязанная камера утомляет
- Плавное следование камеры, не зависящее от частоты кадров
- Мёртвая зона и упреждение взгляда
- Параллакс: коэффициенты слоёв и формула смещения
- Game Feel: шесть приёмов отклика и их дозировка
- Тряска камеры через trauma
- Практический сценарий: камера, параллакс и отклик в коде на JavaScript
- Когда вам это не подойдёт
Почему жёстко привязанная камера утомляет
Камера, жёстко прикреплённая к центру персонажа, повторяет каждый пиксель его движения, включая дрожание при приземлении и разворотах, и при этом показывает одинаково мало пространства впереди и позади. Итай Керен в докладе «Scroll Back: The Theory and Practice of Cameras in Side-Scrollers» (GDC 2015, источник: GDC Vault, проверено: октябрь 2026) разобрал десятки классических игр и показал, что почти все удачные платформеры используют комбинацию мёртвой зоны, упреждения и сглаживания вместо жёсткой привязки.
Плавное следование камеры, не зависящее от частоты кадров
Плавная камера на каждом кадре сдвигается на долю расстояния до цели, и эту долю нужно считать от шага времени. Частая ошибка — cam += (target - cam) * 0.1 в каждом кадре: при 60 к/с камера догоняет цель вдвое быстрее, чем при 30 к/с. Правильная формула:
cam = cam + (target - cam) * (1 - exp(-k * dt))
Параметр k задаёт жёсткость. Время, за которое камера проходит половину оставшегося пути, равно t_1/2 = ln(2) / k. При k = 8 это 87 мс, при k = 4 — 173 мс.
| Частота | Шаг dt | Доля за кадр 1 - exp(-8 * dt) | Доля за 1 секунду |
|---|---|---|---|
| 60 к/с | 16,67 мс | 12,5% | 99,97% |
| 30 к/с | 33,33 мс | 23,4% | 99,97% |
За секунду результат одинаков, значит ощущение камеры не меняется при просадке частоты.
Мёртвая зона и упреждение взгляда
Мёртвая зона — прямоугольник в центре экрана, внутри которого герой ходит, а камера стоит; упреждение — смещение точки цели камеры вперёд по направлению движения. Для окна 1280×720 рабочие значения: мёртвая зона 160×120 пикселей, упреждение по X — 96–160 пикселей (3–5 клеток по 32), переход упреждения при развороте — за 300–500 мс, чтобы экран не дёргался при каждом шаге назад.
Цель камеры с упреждением:
target_x = x_героя + dir * L (где dir = -1 или +1, L = 128 пикс)
По вертикали в платформерах камеру обычно двигают только после приземления на новую высоту (platform snapping), иначе экран качается вверх-вниз на каждом прыжке.
Параллакс: коэффициенты слоёв и формула смещения
Параллакс — эффект глубины, при котором дальние слои фона смещаются медленнее ближних. Смещение слоя на экране — это смещение камеры, умноженное на коэффициент слоя p:
x_экран = x_мир - cam_x * p
При p = 0 слой неподвижен (небо), при p = 1 движется вместе с игровым миром, при p > 1 — быстрее (передний план). Многослойный параллакс в аркадах стал узнаваемым с Moon Patrol (Irem, 1982 год, источник: каталог The International Arcade Museum / KLOV, проверено: октябрь 2026), где три слоя фона двигались с разной скоростью.
| Слой | Коэффициент p | Смещение при сдвиге камеры на 1000 пикс | Контраст и насыщенность |
|---|---|---|---|
| Небо | 0,0–0,1 | 0–100 пикс | Минимальные |
| Дальние горы | 0,2–0,3 | 200–300 пикс | Низкие, с цветом неба |
| Средний план | 0,5 | 500 пикс | Средние |
| Игровой слой | 1,0 | 1000 пикс | Максимальные, чёткий контур |
| Передний план | 1,1–1,3 | 1100–1300 пикс | Тёмные силуэты, не закрывают героя |
Правило читаемости: чем дальше слой, тем ближе его цвет к цвету неба. Тогда игрок никогда не спутает фоновую скалу с платформой.
Game Feel: шесть приёмов отклика и их дозировка
Game Feel — это ощущение прямого управления виртуальным объектом, складывающееся из отклика в реальном времени и визуальной полировки (определение Стива Свинка, книга «Game Feel: A Game Designer's Guide to Virtual Sensation», Morgan Kaufmann, 2008 год, проверено: октябрь 2026). Доклад Мартина Йонассона и Петри Пурхо «Juice it or lose it» (2012 год) показал, как одна и та же игра-арканоид меняется, если по очереди добавлять к ней звук, частицы, анимацию и тряску.
| Приём | Что делает | Рабочая дозировка |
|---|---|---|
| Звук | Подтверждает действие на слух | Каждое значимое действие, вариация высоты тона ±5–10% |
| Частицы | Показывают точку события | 6–20 частиц, жизнь 0,2–0,5 с |
| Сжатие и растяжение | Передают вес и упругость | Приземление: масштаб Y 0,8, X 1,25, возврат за 80–120 мс |
| Пауза при ударе (hit stop) | Подчёркивает силу удара | 2–6 кадров при 60 к/с (33–100 мс) |
| Вспышка | Отмечает получение урона | 1–2 кадра белого цвета |
| Тряска камеры | Сообщает о сильном событии | Амплитуда 8–16 пикс, 0,2–0,4 с, только для редких событий |
Сжатие и растяжение — один из двенадцати принципов анимации студии Disney (Ф. Томас и О. Джонстон, «The Illusion of Life: Disney Animation», 1981 год). Чтобы спрайт не «надувался», произведение масштабов держат равным единице:
sx * sy = 1 → при sy = 0,8 получаем sx = 1,25
Тряска камеры через trauma
Тряска через trauma — это способ, при котором событие добавляет к переменной trauma значение от 0 до 1, она равномерно убывает со временем, а сила тряски пропорциональна её квадрату. Метод описал Сквиррел Айзерло в докладе «Math for Game Programmers: Juicing Your Cameras With Math» (GDC 2016, источник: GDC Vault, проверено: октябрь 2026).
shake = trauma * trauma
offset_x = A_max * shake * noise(t)
trauma = max(0, trauma - r * dt)
Здесь A_max = 12 пикселей, r = 2,5 в секунду (полное затухание от 1 до 0 за 400 мс), noise(t) — шум в диапазоне [-1, 1]. Квадрат нужен, чтобы слабые события давали почти незаметную тряску, а сильные — выразительную: trauma = 0,3 даёт всего 9% амплитуды, trauma = 1 — 100%.
Практический сценарий: камера, параллакс и отклик в коде на JavaScript
Плавную камеру, слои параллакса, сжатие при приземлении и отключаемую тряску можно проверить без графики: каждая функция возвращает числа, которые легко сравнить с таблицами выше. Код ниже проверен в Node.js 24; комментарии показывают его фактический вывод.
// 1. Плавная камера, не зависящая от частоты кадров
function follow(cam, target, k, dt) {
return cam + (target - cam) * (1 - Math.exp(-k * dt));
}
function runCamera(fps) {
let cam = 0;
for (let i = 0; i < fps; i++) cam = follow(cam, 1000, 8, 1 / fps); // 1 секунда
return cam.toFixed(2);
}
console.log("за 1 с при 60 и 30 к/с:", runCamera(60), runCamera(30)); // 999.66 999.66
// 2. Параллакс: экранная координата слоя с коэффициентом p
const layers = { sky: 0.1, mountains: 0.3, middle: 0.5, game: 1.0 };
function screenX(worldX, camX, p) { return worldX - camX * p; }
for (const name in layers) console.log(name, screenX(0, 1000, layers[name])); // -100 -300 -500 -1000
// 3. Сжатие при приземлении с сохранением площади: sx * sy = 1
function squash(t, duration) { // t — время с момента приземления, с
const k = Math.min(1, t / duration);
const sy = 0.8 + (1 - 0.8) * k; // от 0,8 обратно к 1,0
return { sx: 1 / sy, sy: sy };
}
console.log(squash(0, 0.1), squash(0.1, 0.1)); // { sx: 1.25, sy: 0.8 } { sx: 1, sy: 1 }
// 4. Тряска через trauma: амплитуда 12 пикс, затухание 2,5 в секунду
let trauma = 0;
const shakeEnabled = true; // настройка игрока
function addTrauma(v) { if (shakeEnabled) trauma = Math.min(1, trauma + v); }
function shakeOffset(dt, noise) {
const offset = 12 * trauma * trauma * noise;
trauma = Math.max(0, trauma - 2.5 * dt);
return offset;
}
addTrauma(1);
let frames = 0;
while (trauma > 0) { shakeOffset(1 / 60, 1); frames++; }
console.log("тряска затухла за кадров:", frames); // 25 — около 0,4 с при 60 к/с
Дальние горы с p = 0,3 смещаются втрое медленнее игрового слоя, а сжатие возвращается к 1,0 через 0,1 с. Флаг shakeEnabled стоит вынести в настройки игры: при false функция addTrauma ничего не делает, и игрок, чувствительный к движению экрана, отключает тряску одним переключателем.
Когда вам это не подойдёт
Отклик и параллакс мешают там, где важна точность чтения экрана или спокойствие.
- Головоломки и игры на точное прицеливание: тряска и сжатие мешают считать клетки и траектории; в пошаговых головоломках достаточно звука и подсветки.
- Игроки, чувствительные к движению экрана: частая тряска, вспышки и быстрый параллакс могут вызывать дискомфорт — их нужно дозировать и давать отключать.
- Игры на одном неподвижном экране: если уровень целиком помещается в окно, как в аркадах конца 1970-х, слежение камеры и параллакс не нужны, а отклик сводится к звуку, частицам и вспышке.
Мы разрабатываем J2DRU — браузерный 2D игровой движок; он в разработке и публично пока не открыт, заявку можно оставить на странице раннего доступа (подача заявки не гарантирует приглашение).
Читайте также: