Игры про вратаря: как Keeper воссоздаёт настоящее давление на реакцию

Мне хотелось сделать футбольную игру вокруг самого трудного момента сейва: короткого промежутка между тем, как ты прочитал удар, и тем, как подставил на пути мяча хоть какую-то часть тела.

Кнопки отпали почти сразу. Нажатие «влево» или «вправо» воспроизведёт результат, но не ощущение реакции. Я хотел, чтобы игрок следил за разбегом, читал полёт, тянулся, наклонялся и шёл до конца. Контроллером должна была стать веб-камера, а вратарём должен был стать человек, который стоит перед ней.

Так появился Keeper. Это одна из тех игр про вратаря, которые снаружи выглядят просто: отбей четыре удара из шести, и уровень пройден. Но внутри каждый уровень меняет саму задачу на реакцию. Удар становится быстрее, мяч закручивается сильнее, ворота шире, углы встречаются чаще, а область, которую прикрывает вратарь на экране, сужается.

Я не утверждаю, что браузерная игра воспроизводит профессиональный матч или заменяет тренировки вратаря. Это не так. Цель другая: взять важные ограничения настоящей игры на отражение ударов и уместить их в безопасную, понятную игру, в которую можно играть перед веб-камерой.

Игрок тянется через гостиную, а отслеживание позы по веб-камере переносит движение на вратаря, который прыгает в футбольной игре
Keeper собирает в одном браузерном цикле зрительный сигнал, сжимающееся окно реакции и настоящее движение игрока.

Как выглядит настоящее давление на реакцию вратаря

Пенальти трудно не потому, что вратарь никогда не видел летящего мяча. Трудно потому, что информация и время на движение приходят почти одновременно.

В рецензируемом анализе 88 пенальти среднее время полёта мяча составило 525 миллисекунд. Исследователи также обнаружили, что руки вратаря начинали расходиться на высоких и низких ударах примерно через 245 миллисекунд после касания мяча, а движение влево или вправо могло начаться по сигналам, доступным ещё до удара. Читай исследование о том, как вратари определяют направление и высоту пенальти.

Это различие и определило устройство Keeper. Настоящий вратарь реагирует не только на уже летящий мяч. Он читает подход, положение тела и сам удар, а потом уточняет решение по первым метрам полёта. Поэтому в игре футболист разбегается и замахивается ногой до касания. А когда мяч вылетает, скорость, закрутка и точка, куда он летит, решают, сколько времени остаётся на поправку.

Keeper не является проверенным тестом реакции. Задержка камеры, размер экрана, размер комнаты и усиленная модель движений отделяют его от настоящего поля. Но высокие уровни намеренно приближаются к давлению, как у профессионалов. На 9 уровне мяч летит 520 миллисекунд, почти как среднее значение 525 миллисекунд в том исследовании. С 10 уровня время полёта упирается в нижнюю границу игры в 500 миллисекунд.

Сначала нужно было сделать камеру похожей на зеркало

Keeper начинает с того, что просит у браузера видеопоток с фронтальной камеры. Окно разрешения показывает сам браузер через MediaDevices.getUserMedia(). Этому API нужен защищённый контекст и явное согласие пользователя, так что камера не может включиться втихую.

Я запрашиваю видео с идеальным разрешением 1280 на 720, отключаю звук и выбираю фронтальную камеру. Дальше кадры обрабатывает модель позы прямо в браузере. Keeper использует MediaPipe Pose Landmarker в видеорежиме с одной позой и лёгкой моделью, которая работает на GPU, если он доступен. Тот же принцип работы с видеокадрами описывает руководство Google по Pose Landmarker для веба.

Точки приходят в координатах камеры. Каждую координату x я отражаю по горизонтали, прежде чем её использовать. Если игрок шагнул вправо, вратарь на экране должен сместиться вправо с его точки зрения. Без такого зеркала игра с веб-камерой кажется неправильной ещё до первого удара.

Поза ещё не вратарь

Модель позы сообщает, где в кадре камеры находятся голова, плечи, локти, запястья, бёдра, колени и лодыжки. Но где эти точки находятся внутри ворот, она не говорит.

Я решаю это так: ворота получают собственную систему координат. Центр бёдер игрока становится подвижным центром вратаря. Все остальные точки измеряются относительно бёдер, масштабируются по ширине и высоте ворот и размещаются в створе.

Так разные тела получают общее игровое пространство. Запястье тянется к верхнему углу, будь игрок высоким или невысоким, стоит он рядом с камерой или подальше. Игра не переносит на экран сырые пиксели камеры, а переводит форму позы в форму ворот.

Почему наклон должен быть сильнее, чем на камере

Гостиная не футбольное поле. Большинство игроков не могут безопасно прыгнуть в полноценный бросок, и подталкивать к этому я не хотел.

Вместо этого Keeper измеряет смещение бёдер по горизонтали от откалиброванной стойки и усиливает его на всю ширину ворот. Осознанный наклон превращается в бросок. Положение рук и ног тоже учитывается, но усиленный сдвиг бёдер позволяет вратарю целиком перелететь к углу, и при этом игроку не нужно падать на пол.

Исходная стойка между ударами медленно возвращается в центр. Это важно, потому что после нескольких сейвов человек естественно смещается. Фиксированная точка калибровки в конце концов превратила бы обычную стойку в постоянный наклон.

Перед первым отсчётом игра проверяет, что голова, плечи и обе руки видны и достаточно неподвижны. Это состояние готовности она удерживает короткое время, а не принимает один удачный кадр. Экран калибровки не отдельная вещь, а часть дизайна игры. Он показывает, что должна видеть камера, и даёт игроку место для движения.

Как всё тело становится зоной сейва

Главное техническое решение: сейв не должен зависеть только от запястья. Настоящие вратари отбивают удары руками, ногами, корпусом, а иногда и головой. Система столкновений должна была распознавать весь силуэт.

Я собираю вратаря из нескольких простых фигур:

  • Корпус: заполненный многоугольник из обоих плеч и обоих бёдер.
  • Руки и ноги: толстые отрезки, которые считаются капсулами, а не костями толщиной в один пиксель.
  • У запястий чуть более крупные круглые зоны перчаток.
  • У головы своя круглая зона сейва.

Когда мяч достигает линии ворот, движок задаёт один вопрос: пересекается ли мяч с какой-нибудь из этих фигур? Для конечности я нахожу ближайшую точку на отрезке и сравниваю расстояние с толщиной конечности плюс радиусом мяча. Для корпуса проверяю, попадает ли центр мяча внутрь многоугольника.

Точки с видимостью ниже порога в сейве не учитываются. Так угаданное запястье или спрятанная лодыжка не создадут невидимую преграду. В итоге игра достаточно снисходительна, чтобы ощущаться физически, и достаточно точна, чтобы игроку всё равно приходилось двигаться навстречу удару.

Геймплей Keeper: летящий футбольный мяч, нападающий, рамка ворот, уровень и цель по сейвам на стадионе под прожекторами
Мяч растёт по мере приближения, а интерфейс показывает уровень, число ударов и цель в четыре сейва.

Имитация сигналов до реакции

Честной футбольной игре про вратаря нужен читаемый удар. Если мяч сразу вылетает на полной скорости и без подсказок, игрок угадывает, а не реагирует.

Каждый удар начинается с короткого замаха. Футболист подходит к мячу, бьющая нога загружается, а сужающееся кольцо делает момент удара заметным. Когда мяч срывается с бутсы, его движение к воротам ускоряется. Ещё он растёт от дальнего размера к ближнему, и это ещё один сигнал глубины.

Закрутка задана синусоидальной дугой. Когда мяч покидает ногу, изгиб равен нулю, в середине полёта он растёт и снова обнуляется в точке, куда бьёт нападающий. Мяч так может изгибаться, не промахиваясь мимо выбранной цели.

Когда происходит сейв, мяч не исчезает. На мгновение он отскакивает от точки контакта, а часть тела, которая его отбила, вспыхивает. Эта крошечная пауза сильно облегчила понимание столкновения. Игрок видит, почему удар был остановлен.

Высокие уровни имитируют более профессиональную задачу на реакцию

Самый очевидный способ усложнить игру про вратаря: увеличить скорость мяча. Опираться только на это я не хотел: у скорости есть потолок. С какого-то момента она просто отнимает возможность отреагировать.

С ростом уровней Keeper меняет пять связанных величин:

  1. Время полёта сокращается, и на решение остаётся меньше времени.
  2. Боковая закрутка растёт, и начало траектории менее прямое.
  3. Ворота становятся шире, и защищать нужно больше места.
  4. Прикрываемая вратарём область на экране немного сужается, поэтому стоя на месте закрываешь меньше ворот.
  5. Нападающий чаще целится в углы.

На первом уровне мяч летит примерно секунду. С каждым уровнем это время уменьшается на 0,06 секунды, пока не дойдёт до нижней границы в полсекунды. Одновременно закрутка и вероятность удара в угол растут к фиксированному максимуму. Ворота расширяются постепенно, а не прыгают между произвольными режимами сложности.

Важна не какая-то одна цифра, а то, как величины работают вместе. Первые уровни учат принципу управления. Дальнейшие требуют вытягивать информацию из замаха, следить за изгибом, решаться раньше и всё равно использовать весь размах позы. Такое сочетание ближе к давлению, под которым принимает решения опытный вратарь, чем простое ускорение всё того же удара по центру.

Статистика уровней показывает весь рост сложности

Я проверил актуальные настройки напрямую по конфигурации игры. Вот цифры, которые Keeper использует на самом деле:

  • Уровень 1: полёт 1,00 секунды, коэффициент закрутки 5 процентов, ширина ворот 42 процента, вероятность угла 25 процентов, базовый охват 102 процента.
  • Уровень 4: полёт 0,82 секунды, закрутка 20 процентов, ширина ворот 54 процента, вероятность угла 43 процента, базовый охват 96 процентов.
  • Уровень 7: полёт 0,64 секунды, закрутка 35 процентов, ширина ворот 66 процентов, вероятность угла 61 процент, базовый охват 90 процентов.
  • Уровень 9: полёт 0,52 секунды, максимальная закрутка 45 процентов, ширина ворот 74 процента, вероятность угла 73 процента, минимальный базовый охват 88 процентов.
  • Уровень 12: полёт 0,50 секунды, закрутка 45 процентов, ширина ворот 86 процентов, вероятность угла 90 процентов, базовый охват 88 процентов.

Коэффициент закрутки это игровая величина, а не заявление о метрах реального отклонения мяча. Ширина ворот и охват тоже заданы долями экранных ворот. Полезно сравнивать именно рост: между уровнями 1 и 12 время полёта сокращается вдвое, ворота становятся больше чем вдвое шире, вероятность удара в угол вырастает с одного удара из четырёх до девяти из десяти, а прикрытие стоя сужается.

Вот почему поздние раунды ощущаются как другой стандарт, а не как перекрашенный первый уровень. К 9 уровню окно реакции входит в тот же диапазон около полусекунды, который измерили на профессиональных пенальти, а удар к тому же чаще закручивается и приходится ближе к углу.

Цель по сейвам: правило игры, а не профессиональный эталон

В каждом уровне шесть ударов. Отбей хотя бы 60 процентов, то есть четыре из шести, и откроется следующий уровень. Результат: число полностью пройденных уровней.

Я выбрал фиксированный блок, потому что у каждого уровня появляется своя маленькая история. Игрок всегда знает цель, текущий удар и сколько ещё можно ошибиться. Если четыре нужных сейва становятся математически невозможными, забег сразу заканчивается: бессмысленные удары доигрывать не нужно.

Четыре из шести это 66,7 процента для прохождения уровня. Сравнивать это с процентом отражённых пенальти в реальном футболе не стоит. Удары, масштаб тела и распределение целей в Keeper рассчитаны на браузерную игру, в которую хочется возвращаться, а не на статистическое соответствие профессиональным соревнованиям. Реальный ориентир относится к окну реакции и зрительному давлению на решение, а не к счёту.

Такая структура ещё и отделяет стабильность от одного везучего сейва. Один эффектный сейв приятен, но пройти уровни можно только повторяя решения и движения. Для короткой онлайн-игры про вратаря это лучше, чем бесконечный поток без видимой контрольной точки.

Движок отдельно от камеры

Ядро игры ничего не знает ни про React, ни про веб-камеру, ни про точки позы. Оно отвечает за нападающего, замах, полёт мяча, закрутку, результаты ударов, правила уровней и счёт. Страница передаёт ему размер сцены и функцию проверки сейва.

Благодаря такой границе важные правила можно тестировать без запуска камеры. Я могу дать движку детерминированный источник случайных чисел, промотать время, проверить траекторию мяча и убедиться, когда уровень должен засчитаться или закончиться. Геометрия позы живёт в отдельном модуле, где её можно тестировать как геометрию.

Браузерный цикл построен на requestAnimationFrame(). Я считаю время между кадрами и передаю его движку, так что мяч не летит быстрее только потому, что у экрана выше частота обновления.

Чему меня научил Keeper

Трудно было не распознать человека. Трудно было решить, как этому человеку встать в цифровые ворота, чтобы удар начал напоминать настоящее давление на реакцию.

Игра заработала, когда я перестал считать точки позы готовым контроллером. Это сырьё. Контроллер получается из калибровки, зеркала, относительных координат, усиленного наклона, проверок видимости и фигур столкновений. А ощущение рождается из читаемого замаха, заметного контакта и кривой сложности, которая меняет задачу, не меняя правил.

Вот за что я люблю игры с веб-камерой. Техническая система сложная, а инструкция игроку остаётся простой: читай разбег, следи за ударом, тянись к мячу и отбивай. Первые уровни дают время выучить эту последовательность. А высокие просят сделать то же самое за полсекунды.

Можно бесплатно сыграть в Keeper в браузере. Короткий обзор игры есть в моём путеводителе по AR-футболу. А если интересно, как тот же конвейер распознавания позы работает для бега, а не для игры вратаря, прочитай, как я делал Dino Survival.