Что такое детерминизм в играх? Перевод

ggdev ggdev 17 Сентября 2024

Детерминизм
Детерминизм — это идея, что повторение определенного действия с тем же начальным условием всегда приведет к тому же результату. В реальной жизни квантовая механика вставляет палки в колеса, но в играх это на самом деле осуществимо.

Если вы используете эмуляторы, подумайте о загрузке сохраненного состояния и повторении одного и того же ввода снова, и снова, и снова, только чтобы получить всегда один и тот же результат. Например, представьте, что вы играете за Руя в Hyper Road Warriors 2: Octane Edition и нажимаете кнопку Hard Punch, затем сохраняете свое состояние. Независимо от того, сколько раз вы загружаете его обратно, Руй всегда будет двигаться вперед на то же количество пикселей, переходить между кадрами анимации, всегда следуя тому же таймингу, и ударять Нека своим активным хитбоксом всегда в один и тот же момент, нанося тот же урон.

Почему это важно? Ну, для начала, это делает игровой процесс более последовательным. Было бы очень странно, если бы успех или неудача настройки были привязаны к (виртуальному) подбрасыванию монеты, частоте кадров или другим удобствам. Во-вторых — и что более важно — это абсолютно необходимо для одноранговой сетевой многопользовательской игры.

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

Один шаг назад. За исключением некоторых исключений, файтинги проходят онлайн как одноранговые (p2p) соединения: ваш компьютер и компьютер вашего противника обмениваются данными без посредника, и игра запускается на каждом из ваших ПК отдельно. Если бы между ними был сервер, запускающий игру и отправляющий состояние обратно каждому участнику, эта проблема, вероятно, была бы менее очевидна. Но из-за огромного количества экземпляров, необходимых для поддержки такой системы, и во избежание ненужной дополнительной задержки ввода, p2p является наиболее заманчивым вариантом при настройке онлайн-инфраструктуры для такого рода игр.

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

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

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

Профилактика лучше лечения
Сделать игру детерминированной может быть сложно, особенно если вы уже создали свой движок и вам нужно вернуться и искоренить первопричины проблем. Кроме того, если вы разрабатываете для ПК или имеете в виду кросс-игру, это может быть еще сложнее из-за некоторых особенностей оборудования.

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

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

  • разделите логику и рендеринг : обновление логики вашей игры (движение, ввод, столкновения, повреждения…) не должно зависеть от того, что отображается на экране. Если вы работаете с 3D-моделями, убедитесь, что положение ваших хитбоксов не рассчитывается во время рендеринга. Все, что касается игрового процесса, должно обрабатываться отдельно;
  • зафиксируйте свой временной шаг: один кадр обновления должен быть одним кадром обновления и должен продвигать внутриигровой таймер всегда на одну и ту же фиксированную величину, независимо от частоты кадров. Это гарантирует, что когда вы смотрите, например, на кадр 25 на вашем компьютере и компьютере вашего противника, общее количество времени прошло одинаково. Это особенно полезно, чтобы избежать интерполяции движения по-разному в зависимости от прошедшего времени. Например, если выбранный вами временной шаг составляет 16 миллисекунд, неважно, прошло ли на самом деле 16,8 миллисекунд с момента последнего обновления на вашем ПК и 18,4 на компьютере противника: следующий кадр всегда должен будет продвигать внутриигровой таймер на 16 миллисекунд.
  • обрабатывать вводы один раз за логический кадр: вы не хотите, чтобы игрок с более быстрым ПК наводнял противника вводами подкадров. Обрабатывайте ввод только один раз за каждый логический кадр. Вы можете собирать ввод с точностью подкадра, но вы не должны действовать на его основе, пока не пришло время обновления;
  • избегайте использования переменных с плавающей точкой: числа с плавающей точкой… довольно запутанны. Конечно, есть четко определенный стандарт, но стандарт оставляет многие операции «определенными поставщиком». Пока вы используете только сумму, вычитание, умножение, деление и квадратный корень, все может быть в порядке (акцент на «может») , но — например — синус, косинус, экспонента, логарифмы и другие часто используемые операции могут иметь разные результаты в зависимости от архитектуры процессора, модели, производителя и других деталей реализации. Если вы разрабатываете для консоли и не планируете кросс-игру, это, возможно, не проблема, поскольку оборудование стандартизировано, но горе вам, если вашей платформой является ПК . Лучший способ полностью избежать этой проблемы — придерживаться библиотек с фиксированной точкой или просто использовать простые целые числа. Это избавит вас от ужаса необходимости отлаживать неприятные рассинхронизации, которые не имеют других возможных объяснений.
  • исправьте ваши случайности: случайные числа добавляют немного перца некоторым персонажам. Фауст без его уморительно случайного запуска предметов не был бы таким захватывающим или интересным. Но случайные числа должны быть детерминированными на всех машинах . Убедитесь, что вы инициализируете генератор случайных чисел с одним и тем же начальным значением для обоих игроков, так что последовательность «случайных чисел» будет одинаковой на обеих машинах, и ваш персонаж функции Фауста всегда будет вытягивать правильный предмет. Если вы не уверены в том, как это реализовать, сделайте это «глупым» способом: заполните массив произвольным количеством целых чисел (например, 255) в заранее определенном «случайном», жестко закодированном порядке и увеличивайте счетчик на единицу при каждом новом запросе случайного числа, «сворачивая» его обратно в 0 при достижении конца массива. Хорошо, это грубо, но это работает, и это просто;
  • выкиньте Unity Physics и Unity Animator. Нет, серьезно: если вы используете Unity, выбросьте встроенный физический движок и не оглядывайтесь. Эта штука даже не детерминирована на том же устройстве . Я слышал и видел ужасные истории о рассинхронизации от друзей-разработчиков, которые профессионально работают над многопользовательскими играми Unity. Напишите свой собственный физический движок без использования операций с плавающей точкой и не оглядывайтесь — в конце концов, традиционным файтингам нужны только гравитация и столкновения, так что эта задача должна быть относительно выполнима, даже если я бы не назвал ее легкой для новичков. Бонусные баллы за все остальные функции Unity, которые не являются детерминированными. Серьезно, лучший способ иметь детерминированную игру на Unity — запустить игровую логику независимо от нее и просто использовать Unity для рендеринга игры на экране.
    Все вышеперечисленное должно гарантировать, что ваша игра достаточно детерминирована, чтобы в нее можно было играть онлайн, только обмениваясь входами между игроками. Это также является основой для хорошего сетевого кода задержки.

Andrea «Jens» Demetrio

Для ответа вы можете авторизоваться