HUD и адаптивный интерфейс браузерной игры: якоря, масштаб, размер цели касания

Коротко

HUD рисуют в экранных координатах после мира, привязывают каждый элемент якорем к углу или краю и масштабируют от опорного разрешения с нижней границей. На телефонах к отступам добавляют безопасные зоны env(safe-area-inset-*), а зоны касания делают не меньше 44×44 CSS-пикселей. Ниже — формулы якорей и масштаба, таблица раскладки HUD и код, проверенный в Node.js 24 на экранах 1280×720 и 844×390.

Содержание

  1. Мировые координаты против экранных
  2. Якоря и отступы: формула позиции
  3. Масштаб интерфейса от опорного разрешения
  4. Безопасные зоны: вырез и полоса жестов
  5. Размер цели касания: 24 и 44 CSS-пикселя
  6. Таблица: раскладка HUD платформера
  7. Код на JavaScript: якоря, масштаб, зона нажатия
  8. Практический сценарий: проверка HUD на трёх экранах
  9. Когда вам это не подойдёт

Мировые координаты против экранных

HUD живёт в экранных координатах: его позиция отсчитывается от угла окна и не зависит от камеры. Мир рисуется со смещением камеры, поэтому счётчик очков, нарисованный «в мире», уезжает влево, когда герой бежит вправо. Порядок кадра такой: сначала мир со смещением камеры, затем сброс смещения, затем HUD.

Обратное преобразование нужно, когда касание по экрану должно попасть в объект мира:

мир_x = экран_x / зум + камера_x − ширина_экрана / (2 × зум)

Касание сначала проверяют по элементам HUD и только если оно мимо, переводят в мировые координаты.

Якоря и отступы: формула позиции

Якорь задаёт, к какой точке экрана привязан элемент, в долях: 0 — левый или верхний край, 0,5 — середина, 1 — правый или нижний. Позиция левого верхнего угла элемента:

x = левая_граница + (правая_граница − левая_граница) × якорь_x − ширина × якорь_x
левая_граница  = безопасная_зона_слева + отступ
правая_граница = ширина_экрана − безопасная_зона_справа − отступ

Для вертикали формула та же. Одна формула покрывает все девять позиций: углы, середины краёв и центр.

Масштаб интерфейса от опорного разрешения

Масштаб HUD считают от опорного разрешения как меньшее из отношений ширины и высоты и ограничивают снизу и сверху:

масштаб = clamp(min(ширина / 1280, высота / 720), 0,75, 2)

Без нижней границы на телефоне в альбомной ориентации 844×390 масштаб был бы 0,54, и значок паузы 32×32 сжался бы до 17×17 — по нему трудно попасть. Без верхней на мониторе 2560×1440 счётчик стал бы огромным. Размеры шрифтов умножают на тот же масштаб.

Безопасные зоны: вырез и полоса жестов

На телефонах с вырезом и скруглёнными углами часть экрана перекрыта, и отступы HUD нужно увеличить на размер безопасной зоны. В CSS её дают переменные env(safe-area-inset-top), env(safe-area-inset-right), env(safe-area-inset-bottom) и env(safe-area-inset-left); у функции env() есть запасное значение вторым аргументом, например env(safe-area-inset-right, 0px). Игре на холсте эти значения удобно прочитать через вычисленные стили вспомогательного элемента и передать в расчёт якорей.

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

Размер цели касания: 24 и 44 CSS-пикселя

Минимальный размер цели касания задают критерии WCAG 2.2, и считается он в CSS-пикселях, а не в физических. Критерий 2.5.8 (уровень AA) требует не меньше 24×24 CSS-пикселя либо достаточного отступа: круг диаметром 24 CSS-пикселя по центру маленькой цели не должен задевать соседние. Критерий 2.5.5 (уровень AAA) — не меньше 44×44 CSS-пикселя.

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

Таблица: раскладка HUD платформера

Значения ниже заданы для опорного разрешения 1280×720 и умножаются на масштаб.

ЭлементЯкорьРазмерОтступЗона нажатия
Очкиверхний левый160 × 3216—
Здоровьеверхний левый, под очками160 × 1616—
Паузаверхний правый32 × 3216не меньше 44 × 44
Прыжокнижний правый72 × 7224сам элемент
Стик движениянижний левый120 × 12024вся левая треть экрана

Код на JavaScript: якоря, масштаб, зона нажатия

Код считает позиции элементов HUD для монитора и телефона с вырезом и расширяет зону нажатия маленького значка. Он проверен в Node.js 24, числа в комментариях совпадают с выводом.

// Якорь элемента HUD: доля ширины и высоты экрана (0 — левый/верхний край, 1 — правый/нижний)
const ANCHORS = {
  topLeft: [0, 0], top: [0.5, 0], topRight: [1, 0],
  bottomLeft: [0, 1], bottom: [0.5, 1], bottomRight: [1, 1],
};

// Масштаб интерфейса от опорного разрешения 1280×720 с нижней и верхней границей
function uiScale(view) {
  const s = Math.min(view.w / 1280, view.h / 720);
  return Math.max(0.75, Math.min(2, s));
}

// Позиция левого верхнего угла элемента в CSS-пикселях с учётом безопасной зоны (вырез, скругления)
function place(el, view, safe) {
  const k = uiScale(view);
  const a = ANCHORS[el.anchor];
  const w = el.w * k, h = el.h * k;
  const left = safe.left + el.margin * k;
  const right = view.w - safe.right - el.margin * k;
  const top = safe.top + el.margin * k;
  const bottom = view.h - safe.bottom - el.margin * k;
  const x = left + (right - left) * a[0] - w * a[0];
  const y = top + (bottom - top) * a[1] - h * a[1];
  return { x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h) };
}

// Кнопка касания: видимый значок может быть меньше, но зона нажатия — не меньше minSide
function hitBox(r, minSide) {
  const w = Math.max(r.w, minSide), h = Math.max(r.h, minSide);
  return { x: r.x - (w - r.w) / 2, y: r.y - (h - r.h) / 2, w, h };
}

const pause = { anchor: "topRight", w: 32, h: 32, margin: 16 };
const score = { anchor: "topLeft", w: 160, h: 32, margin: 16 };
const jump  = { anchor: "bottomRight", w: 72, h: 72, margin: 24 };
const none = { top: 0, right: 0, bottom: 0, left: 0 };

const desktop = { w: 1280, h: 720 };
console.log(uiScale(desktop), place(pause, desktop, none)); // 1 { x: 1232, y: 16, w: 32, h: 32 }

// Телефон в альбомной ориентации 844×390 с вырезом слева 47 и полосой жестов снизу 21
const phone = { w: 844, h: 390 };
const notch = { top: 0, right: 47, bottom: 21, left: 47 };
console.log(uiScale(phone));               // 0.75 — нижняя граница, иначе было бы 0.54
console.log(place(score, phone, notch));   // { x: 59, y: 12, w: 120, h: 24 }
console.log(place(jump, phone, notch));    // { x: 725, y: 297, w: 54, h: 54 }

// Значок паузы 24×24 на телефоне: зона нажатия расширяется до 44×44
const p = place(pause, phone, notch);
console.log(p.w, hitBox(p, 44));           // 24 { x: 751, y: 2, w: 44, h: 44 }

На телефоне значок паузы виден размером 24×24, но принимает касания в квадрате 44×44; часть этой зоны заходит в отступ, а не под вырез, потому что безопасная зона уже учтена в позиции.

Практический сценарий: проверка HUD на трёх экранах

Проверка занимает около 10 минут в инструментах разработчика браузера:

  1. Монитор 1280×720. Масштаб 1: элементы на своих местах, отступы 16 и 24 пикселя.
  2. Телефон в портретной ориентации 390×844. Масштаб упирается в нижнюю границу 0,75; проверьте, что очки и пауза не перекрываются на ширине 390.
  3. Телефон в альбомной ориентации 844×390. Задайте безопасные зоны слева и справа по 47, снизу 21 и убедитесь, что ни один элемент не заходит под них.
  4. Цели касания. Включите отображение зон нажатия и проверьте, что каждая не меньше 44×44 CSS-пикселей.
  5. Смена ориентации. Поверните устройство во время игры: позиции должны пересчитываться по событию изменения размера, без перезагрузки.

Когда вам это не подойдёт

Якоря с общим масштабом подходят большинству игр с HUD, но не всем:

  • Сложные меню с прокруткой и формами: списки сохранений, магазин с фильтрами и поля ввода проще сверстать обычным HTML и CSS поверх холста, чем рассчитывать вручную.
  • Диегетический интерфейс: если здоровье показано на самом персонаже, а патроны — на оружии, такие элементы живут в мировых координатах и двигаются с камерой.
  • Игры только для мыши на компьютере: требования к целям касания там мягче, но минимум 24×24 CSS-пикселя по WCAG 2.5.8 всё равно полезен.

Мы разрабатываем J2DRU — браузерный 2D игровой движок; текст, кнопки и другие элементы интерфейса поверх игры входят в проверенные внутри разработки основы. Движок публично пока не открыт, заявку можно оставить на странице раннего доступа (подача заявки не гарантирует приглашение).

Источники (проверено: ноябрь 2026): W3C — Understanding SC 2.5.8 Target Size (Minimum), W3C — Understanding SC 2.5.5 Target Size (Enhanced), MDN — env().

Читайте также:

Частые вопросы

Почему счётчик очков уезжает вместе с уровнем?

Его рисуют в мировых координатах, и камера сдвигает его вместе с миром. Элементы HUD нужно рисовать после мира в экранных координатах, без смещения камеры.

Что такое якорь элемента интерфейса?

Это точка экрана, к которой привязан элемент, — например, правый верхний угол. Позиция считается от якоря с отступом, поэтому при смене размера окна элемент остаётся у своего края.

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

По WCAG 2.2 минимальная цель касания — 24×24 CSS-пикселя (критерий 2.5.8), усиленный уровень — 44×44 (критерий 2.5.5). Для игровых зон, по которым нажимают не глядя, ориентируйтесь на 44×44 и больше; видимый значок может быть меньше, если зона нажатия расширена.

Как не спрятать интерфейс под вырез экрана телефона?

Добавлять к отступам значения безопасной зоны из CSS-функции env(safe-area-inset-top), env(safe-area-inset-right) и других. В альбомной ориентации вырез оказывается сбоку, поэтому учитывать нужно все четыре стороны.