Коротко
Честное испытание в 2D-игре отличается от нечестного тремя измеримыми параметрами: у каждой атаки врага есть фаза заметного замаха (телеграфа) длительностью 250–400 мс, у патрульного маршрута между точками A и B есть короткая пауза перед разворотом (400–600 мс), а физическая зона поражения шипов и ловушек уменьшена на 20–30% относительно видимого спрайта. Такой запас учитывает время зрительно-моторной реакции человека и погрешность прямоугольных коллайдеров. В статье разобраны формулы патруля и усечённых хитбоксов, таблица фаз удара в миллисекундах и проверенный в Node.js код врага с фазами замаха, удара и восстановления.
Содержание
- Три фазы честной атаки: почему замах должен длиться минимум 250–400 мс
- Математика патрулирования между точками A и B с паузой на краю
- Прощающий хитбокс: почему зону шипов уменьшают на 20–30%
- Контраст и форма: как отличить опасность от декорации за долю секунды
- Практический сценарий: патрульный страж с предупреждением перед выпадом на JavaScript
- Когда вам это не подойдёт
Три фазы честной атаки: почему замах должен длиться минимум 250–400 мс
Любая читаемая атака врага или срабатывание ловушки в 2D-игре делится на три последовательные фазы: замах (предупреждение / телеграф), активный удар и восстановление (откат), причём фаза замаха не может быть короче 250 мс.
Нижняя граница длительности замаха прямо следует из физиологии человека и задержки ввода:
- Медианное время простой зрительно-моторной реакции человека на смену цвета экрана в онлайн-выборке Human Benchmark составляет
273 мс(около16кадров при60 к/с; источник: статистикаhumanbenchmark.com/tests/reactiontime/statistics, проверено: октябрь 2026); - В реальной игре человеку нужно не просто нажать единственную кнопку, а распознать позу врага и выбрать действие (прыжок, шаг назад или удар) — по закону Хика—Хаймана (У. Э. Хик, «On the rate of gain of information», Quarterly Journal of Experimental Psychology, 1952 год, проверено: октябрь 2026) время реакции с выбором из
2–3вариантов возрастает до300–450 мс.
В классической книге по диснеевской анимации Фрэнка Томаса и Олли Джонстона «The Illusion of Life: Disney Animation» (1981 год, проверено: октябрь 2026) эта подготовительная фаза описана как принцип Anticipation (упреждение / отказное движение): перед рывком вперёд фигура сначала отводится назад, предупреждая зрителя о направлении и силе действия.
| Фаза атаки | Длительность в мс | Число кадров при 60 к/с | Активен ли хитбокс урона? | Что видит и слышит игрок |
|---|---|---|---|---|
1. Замах (telegraph) | 300–450 мс | 18–27 кадров | Нет | Враг останавливается, отводит оружие назад, звучит предупреждающий сигнал |
2. Активный удар (strike) | 100–150 мс | 6–9 кадров | Да | Быстрый выпад вперёд, чёткий след удара |
3. Восстановление (recovery) | 400–600 мс | 24–36 кадров | Нет | Враг открыт для ответного прыжка сверху или обхода |
Если включить хитбокс урона сразу в кадре №1 фазы замаха, игрок получит урон от занесённой назад руки врага и воспримет столкновение как ошибку игры.
Математика патрулирования между точками A и B с паузой на краю
Патрулирование — это циклическое движение врага между левой границей minX (точка A) и правой границей maxX (точка B) с фиксированной скоростью speed и обязательной паузой waitTime при достижении края.
Без паузы на развороте координата врага просто отражается в один кадр, из-за чего школьнику или начинающему игроку трудно подгадать момент для перепрыгивания. С паузой поведение описывается переменной направления dir (-1 влево, +1 вправо) и таймером ожидания waitTimer:
если waitTimer > 0:
waitTimer = waitTimer - dt
Vx = 0
иначе:
Vx = dir * speed
X = X + Vx * dt
если X >= maxX:
X = maxX
dir = -1
waitTimer = 0.5 // пауза 500 мс на правом краю
если X <= minX:
X = minX
dir = +1
waitTimer = 0.5 // пауза 500 мс на левом краю
Конкретный расчёт цикла патруля: пусть платформа имеет ширину 256 пикселей (8 клеток по 32), враг ходит по отрезку от minX = 224 до maxX = 448 (длина пути L = 224 пикселя) со скоростью speed = 112 пикс/с и останавливается на краях на waitTime = 0,5 с. Тогда:
Время прохода в одну сторону = 224 / 112 = 2,0 с
Полный цикл патруля (туда + пауза + обратно + пауза) = 2,0 + 0,5 + 2,0 + 0,5 = 5,0 с
Каждые 2,5 секунды враг замирает на полсекунды в предсказуемой точке, давая игроку чёткое окно для манёвра.
Прощающий хитбокс: почему зону шипов уменьшают на 20–30%
Прощающий хитбокс (forgiving hitbox) — это намеренное несовпадение размера видимого спрайта и его физического прямоугольника столкновения в пользу игрока: опасные объекты делают физически меньше картинки, а полезные (монеты, аптечки, платформы) — чуть больше.
Когда на клетке 32×32 пикселя нарисованы треугольные шипы, верхняя треть клетки состоит из пустых пикселей между остриями. Если задать шипам полный квадратный хитбокс 32×32, герой погибнет, задев пяткой пустой угол над шипом.
Правило расчёта усечённого прямоугольника опасности внутри клетки шириной W и высотой H со сжатием на 25% по бокам и на 30% сверху:
hitboxW = W * 0.50 // для 32 пикс → 16 пикс (по 8 пикс отступа слева и справа)
hitboxH = H * 0.70 // для 32 пикс → 22 пикс (10 пикс запаса сверху у острия)
offsetX = (W - hitboxW) / 2 // +8 пикс от левого края спрайта
offsetY = H - hitboxH // +10 пикс от верхнего края (прижат к основанию пола)
| Тип объекта на сцене | Размер видимого спрайта | Размер зоны столкновения (W × H) | Смещение относительно спрайта | Зачем нужен такой допуск |
|---|---|---|---|---|
| Хёртбокс игрока (получение урона) | 32 × 48 пикс | 22 × 40 пикс (-31% по ширине) | По центру, прижат к стопам | Касание врага краем плаща или волос не отнимает жизнь |
| Напольные шипы (опасность) | 32 × 32 пикс | 16 × 20 пикс (-50% ширины, -37% высоты) | Отступ 8 пикс с боков и 12 пикс сверху | Смерть наступает только при реальном наступании на остриё |
| Летящий снаряд врага | 16 × 16 пикс | 10 × 10 пикс (-37%) | По центру (0,5; 0,5) | Позволяет увернуться в плотном потоке снарядов |
| Монета или бонус (награда) | 16 × 16 пикс | 24 × 24 пикс (+50%) | По центру (0,5; 0,5) | Предмет подбирается, даже если герой пролетел в 4 пикселях рядом |
Контраст и форма: как отличить опасность от декорации за долю секунды
В динамичной 2D-игре взгляд игрока сфокусирован на персонаже и точке в 100–150 пикселях впереди него, поэтому ловушки должны считываться периферическим зрением по трём визуальным признакам без чтения подписей:
- Язык геометрических форм: скруглённые и прямоугольные силуэты мозг считывает как безопасные опоры (платформы, кнопки), а острые треугольные и диагональные зубцы — как угрозу (шипы, пилы, челюсти);
- Цветовой код сцены: выберите один сигнальный цвет опасности на весь проект (например, ярко-алый или кислотно-фиолетовый) и никогда не используйте этот оттенок в безобидных фоновых цветах или декоративных кустах на слое параллакса;
- Разделение по слоям отрисовки: все объекты, способные нанести урон или остановить движение, размещаются строго на основном игровом слое с коэффициентом параллакса
1,0и чётким контуром.
Практический сценарий: патрульный страж с предупреждением перед выпадом на JavaScript
Страж, который патрулирует отрезок X = 224..448 с паузой 0,5 с на краях, а при приближении героя ближе 96 пикселей (3 клетки) делает замах 0,35 с и только потом бьёт, описывается конечным автоматом из четырёх состояний и одного таймера. Код ниже проверен в Node.js 24; комментарии показывают его фактический вывод.
// Страж: патруль X = 224..448 со скоростью 112 пикс/с и паузой 0,5 с,
// при игроке ближе 96 пикс — замах 0,35 с, удар 0,15 с, восстановление 0,5 с
const MIN_X = 224, MAX_X = 448, SPEED = 112, WAIT = 0.5, SIGHT = 96;
const PHASES = { windup: 0.35, strike: 0.15, recovery: 0.5 };
function createGuard() { return { x: MIN_X, dir: 1, wait: 0, state: "patrol", timer: 0 }; }
function updateGuard(g, dt, playerX) {
if (g.state === "patrol") {
if (Math.abs(playerX - g.x) < SIGHT) { g.state = "windup"; g.timer = PHASES.windup; return; }
if (g.wait > 0) { g.wait -= dt; return; }
g.x += g.dir * SPEED * dt;
if (g.x >= MAX_X) { g.x = MAX_X; g.dir = -1; g.wait = WAIT; }
if (g.x <= MIN_X) { g.x = MIN_X; g.dir = 1; g.wait = WAIT; }
return;
}
g.timer -= dt;
if (g.timer > 0) return;
if (g.state === "windup") { g.state = "strike"; g.timer = PHASES.strike; }
else if (g.state === "strike") { g.state = "recovery"; g.timer = PHASES.recovery; }
else { g.state = "patrol"; g.wait = WAIT; }
}
// Урон наносится только в фазе удара: касание во время замаха безопасно
function hurts(g, touching) { return touching && g.state === "strike"; }
// Проверка 1: полный цикл патруля без игрока поблизости
const dt = 1 / 60;
const g = createGuard();
let t = 0, turns = 0, lastDir = g.dir;
while (turns < 2) {
updateGuard(g, dt, 10000); t += dt;
if (g.dir !== lastDir) { turns++; lastDir = g.dir; }
}
while (g.wait > 0) { updateGuard(g, dt, 10000); t += dt; }
console.log("цикл патруля, с:", t.toFixed(2)); // 5.03 — 5 с с точностью до кадра 16,67 мс
// Проверка 2: сколько длится безопасный замах перед ударом
const h = createGuard();
let ms = 0;
updateGuard(h, dt, MIN_X + 50); // игрок подошёл на 50 пикс
while (h.state === "windup") { updateGuard(h, dt, MIN_X + 50); ms += dt * 1000; }
console.log("замах, мс:", Math.round(ms), "урон при касании в замахе:", hurts({ state: "windup" }, true)); // 367 false — 350 мс с точностью до кадра
Измеренные 5,03 с и 367 мс вместо 5,0 с и 350 мс — цена шага 1/60 с (16,67 мс): таймер переключает состояние в первом кадре, где он стал не больше нуля, поэтому фаза никогда не бывает короче заданной. Зону поражения шипов в той же игре задавайте по формуле усечённого хитбокса из раздела выше, а не по размеру спрайта.
Когда вам это не подойдёт
Описанная схема конечного автомата из нескольких состояний и прощающих прямоугольных зон имеет свои границы:
- Стелс-игры и тактики со сложным поиском пути по лабиринту: если
30врагов должны обходить динамические преграды по сетке (алгоритм A*), слышать шаги сквозь двери и координировать обход с флангов, простого патруля междуminXиmaxXне хватит — потребуются граф навигации и поиск пути. - Файтинги киберспортивного уровня с проверкой попиксельных полигонов: для турнирных дуэлей, где у каждого удара задаются отдельные прямоугольники головы, корпуса и ног на каждый кадр, одного уменьшенного хитбокса мало.
- Мирные игры для самых маленьких: если цель — исследование без поражения, фазы атаки и зоны урона лишние; достаточно безопасных препятствий и подсказок.
Мы разрабатываем J2DRU — браузерный 2D игровой движок; он в разработке и публично пока не открыт, заявку можно оставить на странице раннего доступа (подача заявки не гарантирует приглашение).
Читайте также: