[Перевод] Компенсация лагов для оружия в MechWarrior Online
Задержка в сети (лаг) — это реальность, которую нужно учитывать в многопользовательских играх. Сообщениям, передаваемым через Интернет, требуется время, чтобы достичь точки назначения. В зависимости от маршрута и его длины передача этих сообщений может занять довольно долгое время. Это может негативно влиять на процесс игры, особенно в динамичных клиент-серверных играх, таких как FPS. То, что кажется очень простой задачей (стреляй, пытаясь попасть в цель), внезапно становится очень сложным в создании плавного игрового процесса для всех игроков. Думаю, не нужно говорить, что создавать многопользовательские игры сложно, при этом возникает множество проблем, которые разработчики должны решить. В этой статье я расскажу, как система вооружения MechWarrior Online справляется с лагом.
Одна из стратегий решения проблемы лага — уменьшить расстояния и таким образом улучшить маршрут между игроками и игровым сервером, чтобы лаг стал не так заметен. Этого можно достичь, создав систему региональных серверов и подключая игроков только к тем серверам, которые находятся к ним ближе; или же можно обеспечить передачу игрового трафика по маршрутам через определённые сети для оптимальной передачи на серверы. К сожалению, такой способ не всегда возможен из-за высокой стоимости и может применяться при ограниченных объёмах данных;, но даже и в этом случае он не всегда срабатывает для игроков, находящихся в отдалённых регионах или имеющих низкое качество Интернет-соединения. MechWarrior Online (MWO) подходит к этой проблеме так же и использует региональные выделенные сервера для минимизации задержек для игроков. Однако этого часто недостаточно для большинства игроков, и нам нужно сделать что-то ещё.
Прогнозирование на стороне клиента
Вы могли слышать выражение «прогнозирование на стороне клиента» в обсуждениях клиент-серверных многопользовательских игр. Это важная концепция, позволяющая обеспечить плавность игрового процесса, особенно в динамичных играх. Без неё даже небольшой лаг может быть очень заметным и раздражающим. Идея прогнозирования на стороне клиента проста: вместо ожидания передачи сервером клиенту результата действия клиент прогнозирует исход этого действия, как будто оно происходит немедленно. Например, если игрок хочет пойти вперёд, мы сразу начинаем перемещать игрока на стороне клиента, а затем отправляем серверу запрос на перемещение. С точки зрения игрока кажется, что он перемещается сразу, однако в реальности для сервера он ещё не переместился. Если прогнозы игрового клиента всегда правильны, то игрок воспринимает это как отсутствие лага. Конечно же, прогнозы не могут быть всегда правильными и эту проблему нужно как-то решать, но мы не будем ею в этой статье. MWO очень сильно зависит от прогнозирования на стороне клиента. И движение, и система вооружения — когда игрок пытается переместить своего меха или выстрелить из оружия, игра реагирует немедленно.
Компенсация лага
Теперь у нас есть клиент, немедленно реагирующий на поведение игрока и устраняющий ощущение лага; однако есть ещё одна проблема. Несмотря на кажущуюся мгновенную реакцию на действия игрока, игровой мир реагирует на них с замедлением. Мы только устранили видимость лага, но информация, получаемая от сервера, всё равно приходит с задержкой. Это означает, что цель, которую видит игрок, на самом деле для сервера находится в другом месте; хуже того, когда игрок стреляет в цель и запрос на стрельбу получает сервер, цель сдвигается ещё дальше из своего исходного положения. Время, необходимое для получения информации и отправки запроса на сервер, называют временем передачи и подтверждения, или пингом. Если у игрока ненулевая латентность, то без компенсации лага игрокам приходилось бы стрелять в цель на упреждение, чтобы попасть в неё; чем выше пинг, тем дальше должно быть упреждение. Вы можете представить, насколько бы это раздражало игроков. Можно проиллюстрировать ситуацию следующими изображениями.
Что видит игрок при лаге
Что видит сервер при лаге
С точки зрения игрока, он стопроцентно попадает в свою цель, но сервер видит, что игрок стреляет совершенно мимо. Чтобы решить эту проблему, нужно каким-то образом компенсировать лаг игрока.
Сейчас вы можете задать вопрос: зачем вообще решать эту проблему? Если клиент прогнозирует всё, может ли клиент просто сообщить серверу, попал игрок в цель или нет? Проще всего ответить «да», этот вариант возможен; однако в нём есть большой изъян. Когда игровому клиенту разрешают сообщать что-то серверу, возникает возможность читерства. При игре на компьютере игровой клиент можно с лёгкостью модифицировать, и хитрый читер изменит игровой клиент таким образом, что тот будет отправлять сообщения о попадании, когда захочет читер, даже если он находится далеко от цели или вообще её не видит. Поэтому передача таких прав клиенту, особенно в отношении нанесения ущерба врагам — это невероятно рискованное предложение. Если многопользовательская игра предназначена для платформ, обеспечивающих какую-нибудь «защиту» игрового клиента, например, для консолей, то это может быть вполне приемлемым решением. Но в мире ПК, если вы даёте клиенту любой контроль над состоянием игры, то он почти наверняка будет использован в свою пользу. Существует множество способов жульничать в бою, но в этой статье мы тоже не будем их рассматривать. Для нас самым безопасным решением будет предоставление полного контроля серверу, именно такой подход и реализован в MWO.
В MWO существуют две категории оружия, и для каждой лаг должен компенсироваться по-своему. Вооружение подразделяется на оружие прямолинейного огня (такое как лазеры и пулемёты) и на артиллерийское оружие (например, ракеты и баллистическое оружие). Работать гораздо проще с прямолинейным оружием, так что давайте начнём с него.
Прямолинейное оружие с компенсацией лага
Прямолинейное вооружение стреляет мгновенно, достигая цели в момент выстрела. Это обеспечивается рейкастингом или трассировкой линий в текущем состоянии игрового мира для определения точки удара оружия. Поэтому для компенсации лага нужно выполнить трассировку линий. Если сервер хранит предыдущие положения и направления целей, то он может использовать данные о пинге игрока для определения местоположения цели в момент выстрела игрока. Это местоположение обозначено синим мехом на картинке ниже. Он выровнен относительно положения экрана стреляющего игрока. MWO обращается к положению этого синего меха в качестве «перемотанного по времени назад» положения цели. Теперь, когда сервер знает, где должна быть цель, он может переместить цель обратно в эту точку, выполнить трассировку линий для определения наличия попадания, а затем поместить цель в её исходную точку. Таким образом сервер делает прогноз игрового клиента точным.
Как сервер видит «перемотанное назад» положение цели
В случае MWO недостаточно просто хранить предыдущие положения и направления. Каждый компонент меха может быть повреждён отдельно, и это очень важная игровая механика, так что необходимо ещё состояние анимации. Оно также должно запоминаться и «перематываться» на сервере. На картинке это видно как разница в цикле движения «прошлого» и «настоящего» меха. Итак, мы разобрались с прямолинейным оружием, теперь давайте найдём решение для артиллерийского вооружения.
Артиллерийское оружие с компенсацией лага
Поведение артиллерии очень сильно отличается от поведения прямолинейного оружия. Они стреляют геометрическими объектами, такими как снаряды или ракеты, эти объекты перемещаются по воздуху, а затем наносят повреждения, сталкиваясь с целью. В этом случае простая «перемотка» не сработает. Если бы мы использовали тот же подход, что и для прямолинейного оружия, и приняли бы, что снаряды двигаются с бесконечной скоростью, то сервер мог некорректно определять, что снаряд мгновенно сталкивался с целью, даже если цель уже могла уйти с его пути до столкновения. Хуже того, игроки намеренно стреляют артиллерийским оружием на упреждение, потому что рассчитывают, где будет снаряд ко времени, когда он долетит до цели; если бы мы применяли такой способ, то игрок с совершенным глазомером на сервере с совершенной «перемоткой» постоянно бы промахивался. Нам нужен другой подход.
Упреждающая стрельба артиллерийским оружием на игровом клиенте
Нам бы хотелось перемотать вперёд снаряд на сервере, чтобы определить его положение, прогнозируемое клиентом. Если мы сделаем прогноз клиента правильным, мы скомпенсируем лаг на игровом клиенте. Чтобы достичь этого, мы разобьём цикл «жизни» снаряда на две части.
Ко времени получения сервером запроса на выстрел снарядом проходит половина времени на передачу и подтверждение игрока. Но нужно помнить, что сервер должен опережать клиент на половину времени на передачу и подтверждение, поскольку обрабатываемый сервером снаряд должен был существовать в течение секунд пинга, чтобы соответствовать всему остальному в мире сервера. В течение этого времени снаряд уже может столкнуться с целью в клиенте, и сервер должен на это реагировать, но давайте пока не будем волноваться об этом. Давайте назовём этот первый этап срока жизни снаряда периодом «перемотки назад». Всё после этой первой части срока жизни снаряда будет точкой, в которой симуляция снаряда сервером и прогнозирование поведения снаряда клиентом должны синхронизироваться. Важно заметить, что синхронизированность в данном контексте не означает, что снаряд должен находиться в одинаковых точках в клиенте и на сервере. Клиент должен отставать от сервера на половину времени на передачу и подтверждение. Поэтому в этом случае синхрозирированность означает, что снаряд находится в том же месте относительно других объектов мира и в клиенте, и на сервере. Назовём этот этап срока жизни снаряда синхронизированным периодом. Работа с первой частью немного сложнее, поэтому давайте сначала займёмся второй.
Предположим, что снаряд имеет простую физику и двигается по прямой с постоянной скоростью. В этом случае в момент получения сервером запроса на выстрел снаряд уже пройдёт в игровом клиенте расстояние, равное его скорости, умноженной на пинг клиента. Если снаряд имеет более сложную физику, например, с учётом трения и гравитации, то всё равно должно быть возможно рассчитать пройденное им расстояние с помощью более сложной формулы.
Траектория снаряда на сервере, вид сверху
Если мы пока проигнорируем период «перемотки назад», сервер может сделать прогноз клиента правильным или синхронизироваться с клиентом, просто создав снаряд на расстоянии, которое он прошёл от обычного положения создания. Другими словами, сервер не будет стрелять снарядом из дула оружия, а вместо этого выстрелит им на нужном расстоянии вдоль его траектории. Если снаряд имеет более сложную физику, серверу может потребоваться рассчитать нужную скорость и ускорение, которое должен будет иметь снаряд в соответствующей точке траектории. Полагая, что снаряд движется детерминированно, он должен теперь перемещаться по синхронизированной между клиентом и сервером траектории. Это довольно просто, но как насчёт периода «перемотки назад»?
Если бы мы всегда создавали снаряды на расстоянии периода «перемотки назад», то снаряды бы могли проходить через цели или даже статические объекты. Довольно просто ограничить прохождение через статические объекты с помощью трассировки линий, но как насчёт подвижных целей? Если цель находится в пределах периода «перемотки назад», то снаряд согласно прогнозу клиента может уже столкнуться с целью, прежде чем запрос дойдёт до сервера. В этом и состоит настоящая проблема со снарядами — сервер должен уметь распознавать такие ситуации, чтобы реализовывать прогнозы клиента.
Один из способов решения проблемы — итеративный, сочетающий в себе «перемотку назад» с определённой симуляцией «перемотки вперёд». Когда запрос на выстрел снарядом достигает сервера, он может «перемотать назад» цель, создать снаряд и циклично просчитывать снаряд и цель «перемоткой вперёд» в течение периода «перемотки назад». В случае обнаружения столкновения между целью и снарядом при цикличном просчёте вперёд, мы можем нанести повреждение цели и вообще избежать создания снаряда. Если столкновение не обнаружено, мы можем создать снаряд в его синхронизированный период.
К сожалению, у этого решения есть пара других проблем. Первая заключается в том, что быстро летящие снаряды без какого-нибудь способа непрерывного обнаружения столкновений в каждом цикле смогут проникать через цели в собственном цикле. Вторая состоит в том, что такой тип решения очень затратен для применения на сервере. Количество итераций соотносится с длительностью периода «перемотки назад». Так что если у игроков будут повышенные пинги, обработка каждого выстрела снарядом будет более ресурсоёмкой. В MWO бывает очень много снарядов, и они летят очень быстро; по этим и другим причинам, которые мы не будем здесь рассматривать, такое решение нам не подходило.
Для создания неитеративного решения давайте сначала сделаем некоторые допущения, упрощающие проблему. Мы примем для снарядов следующие допущения: снаряд приблизительно может считаться точкой, так что можно использовать трассирование линий для аппроксимации его траектории; траектория периода «перемотки назад» достаточно хорошо аппроксимируется прямой линией; также можно принять, что снаряды в течение периода «перемотки назад» имеют постоянную скорость. В MWO для снарядов применяется баллистическая физика, поэтому они не всегда движутся по прямым, но относительно мехов они очень малы, движутся очень быстро и не снижают скорости при движении в воздухе, так что эти допущения нам подходят. Также нам нужно принять допущения относительно движения целей. Примем, что движение цели между положением «перемотки назад» и текущим положением на сервере может быть достаточно хорошо аппроксимировано с помощью прямой линии; также примем, что скорость цели в течение этого периода тоже постоянна. Такие допущения могут быть довольно неудобными в некоторых динамичных играх, но к счастью, в MWO мехи поворачиваются, ускоряются и замедляются медленно и плавно, так что эти условия нам подходят.
Приняв эти допущения, мы можем разбить проблему на две части. В первой части мы хотим узнать, возможно ли вообще столкновение с целью. Если столкновение с целью возможно, то мы выполняем расчёт для определения времени, в которое должно случиться столкновение в период «перемотки назад». Во второй части мы используем эту информацию для выполнения обычной «перемотки назад» прямолинейного огня с изменённым значением пинга, чтобы определить, было ли попадание.
Для проверки на наличие возможности попадания, мы сначала определяем положение «перемотанной назад» цели на основании текущего пинга игрока и вектора, представляющего траекторию снаряда в период «перемотки назад». Это похоже на то, что мы делали при «перемотке назад» прямолинейного огня. Мы хотим узнать, пересекаются ли эти два объекта при перемещении по собственным траекториям. Допущения, принятые ранее, позволяют свести эту проблему к трассировке единственной линии, сделав скорость снаряда относительной к стационарной «перемотанной назад» цели. Можно увидеть это на изображении ниже.
Применяя наши допущения, сервер может свести проблему, представленную слева, к проблеме справа, которую гораздо проще решить
Решение этой проблемы аналогично решению проблемы прохождения сквозь объекты, которая упомянута выше. Можно представить, как снаряд перемещается из своего начального положения к концу своего периода «перемотки назад» за один цикл; наличие столкновений во время движения этих объектов между начальной и конечной точкой выполнятся непрерывной проверкой обнаружения столкновений. Важно также заметить, что эта проверка не полностью аналогична обычной трассировке линий. Мы намеренно игнорируем остальную часть мира и рассматриваем только «перемотанную назад» цель и модифицированную траекторию — это очень важная деталь. Если мы не будем игнорировать остальную часть мира, мы можем ошибочно получить результат, сообщающий о невозможности возникновения столкновения, в то время как оно может быть. Например, если игрок стоит рядом со стеной, у нас может получиться ложноотрицательный результат. Можно видеть это на картинке ниже.
Возможная ситуация, в которой объект мира приводит к ошибочному результату при проверке модифицированной траектории
Помните, что эта проверка сообщает нам только о возможности столкновения; она не гарантирует, что столкновение обязательно произойдёт. На пути могут находиться другие движущиеся или статичные объекты. Итак, мы всё ещё хотим выполнять какую-нибудь проверку «перемотки назад» вдоль исходной траектории снаряда, похожую на ту, которую мы делали для оружия прямолинейного огня. Если первая часть этой проверки проходит успешно, она даёт нам точно ту информацию, которая нам нужна для этого. Она сообщает, что столкновение возникло, оно возникло на определённом расстоянии в процентах вдоль пути от «перемотанного назад» положения до текущего положения цели. Поскольку и снаряд, и цель двигаются в течение одного временного промежутка, этот процент равен проценту вдоль модифицированной траектории, где происходит столкновение — иногда он называется «временем попадания». Мы можем использовать время попадания для расчёта значения модифицированного пинга, которое «перематывает назад» цель к этому среднему положению.
Состояния «перемотки назад» снарядов на сервере. Чёрная линия — исходный вектор огня в течение периода «перемотки назад». Синяя линия — модифицированной вектор огня для непрерывной проверки обнаружения столкновений. Чёрный мех — текущее положение меха-цели при выстреле снарядом. Синий мех — «перемотанное назад» положение меха-цели на основании пинга игрока. Жёлтый мех — потенциальное положение для финальной проверки «перемотки назад»
Это среднее положение и есть «перемотанное назад» положение, для которого мы и хотим выполнить проверку «перемотки назад» прямолинейного огня. Мы можем выполнить обычную проверку «перемотки назад» прямолинейного огня с модифицированным значением пинга, чтобы определить, было ли на самом деле попадание. Если попадание было, мы наносим цели соответствующее повреждение и обходимся без создания снаряда. Если попадание отсутствует, мы создаём снаряд в его синхронизированный период, как было описано выше. В случае изображения выше мы видим, что в данной ситуации попадание есть.
Заключение
Оружие с компенсированием лага — это только один из примеров сложных проблем, с которыми обычно сталкивается MWO и другие многопользовательские игры; в этой статье мы показали только самую малую часть. Мы даже не начинали обсуждать эффекты взрывов, трассеры, несоответствия, которые может привнести «перемотка назад» между игроками с лагами и без них, а также способы, которыми сервер может эффективно выполнять всю эту работу для нескольких целей. Наряду с компенсацией лагов оружием есть ещё множество проблем, с которыми приходится иметь дело многопользовательским играм, в том числе ошибки в прогнозировании клиентов, читерство, управление пропускной способностью, управление производительностью сервера и многие другие. Теперь вы можете понять, сколько сил и навыков вкладывается в создание того, что кажется простым элементом многопользовательской игры. Надеюсь, эта статья прольёт немного света на то, насколько может быть сложной разработка многопользовательской игры, и в следующий раз, играя в любимую онлайн-игру, вы достойно оцените всю эту работу.