«Делать онлайн-игры очень тяжело»: как создатели MechWarrior Online борются с сетевыми задержками

Отправляем письма
с главными игровыми новостями недели

Ведущий сетевой инженер студии Piranha Games Нима Теймори написал для Gamasutra колонку о том, как компания, на примере мультиплеерного экшена MechWarrior Online поборола одну из самых серьезных проблем, с которыми сталкиваются авторы онлайн-игр — сетевые задержки.

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

Редакция DTF публикует перевод материала.

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

В этой статье я расскажу, с какими проблемами мы столкнулись при работе над боевой системой MechWarrior Online и как боролись с лагами.

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

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

MechWarrior Online (MWO) в этом плане не особо отличается от других онлайн-проектов — мы используем распределенные выделенные серверы, снижающие задержку. Но зачастую этого недостаточно, поэтому мы делаем еще кое-что.

Прогнозирование клиентской части

«Прогнозирование клиентской части» (client-side prediction) — вы, наверное, слышали этот термин, если когда-нибудь обсуждали техническую сторону онлайн-игры, использующей систему «клиент-сервер». Это важная техника, благодаря которой геймплей мультиплеерных игр, особенно динамичных, кажется плавным и беспрерывным. Без нее любые, даже самые незначительные лаги были бы заметны и выводили бы игроков из себя.

Идея здесь проста: вместо того, чтобы ждать, пока сервер отреагирует на действие клиента, результат можно просто предсказать, как будто действие было совершено мгновенно. Пример: если игрок хочет сделать шаг, игра сначала показывает ему анимацию движения героя вперед, а уже потом отправляет на сервер запрос на перемещение. В итоге игрок думает, что он двинулся в ту же секунду, когда нажал кнопку, хотя на самом деле сервер об этом еще даже не узнал. Если прогнозирование работает исправно, создается стойкое ощущение, что игра вообще не «лагает». Конечно, иногда метод дает сбой, и его нужно дорабатывать, но это тема для другой статьи.

MWO использует прогнозирование клиентской части при обработке передвижения роботов.

Компенсация лага

У нас есть клиентская часть, которая моментально реагирует на действия игрока — при движении кажется, что задержек нет. Но есть другая проблема.

Несмотря на то, что игрок думает, будто его действия мгновенно влияют на окружающую среду, мир игры «отстает» от него. Помните, мы только производим впечатление, что лага нет, но в это время информация все так же отправляется на сервер и какое-то время доходит до него. Это значит, что движущаяся цель, которую игрок видит у себя на экране, на самом деле находится уже в другом месте. Человек жмет кнопку выстрела, видит анимацию, но пока нужный запрос достигнет серверной части, цель еще больше отдалится от своей первоначальной позиции. Время, которое требуется системе «клиент-сервер» для взаимного обмена одним пакетом данных называется громоздким термином «время между отправкой запроса и получением ответа» (round trip time, RTT). Простые смертные называют его пингом.

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

Что видит игрок, когда у него «лагает» игра
Что «видит» сервер, когда игрок «лагает»

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

Вы можете задаться вопросом – если у нас уже работает прогнозирование, разве нельзя с его помощью просто сообщить клиенту, попал он по врагу или нет? Ответ прост — можно. Но есть очень серьезная загвоздка.

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

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

Оружие, компенсирующее лаги

В MechWarrior Online есть два вида оружия, каждый из которых нуждается в компенсации лагов. Первый: пушки, стреляющие потоками или очередями — лазеры, пулеметы. Второй — метательные орудия, выпускающие ракеты или другие типы одиночных снарядов. Начнем разбор технологии с пулеметов и лазеров, потому что с ними гораздо проще иметь дело.

Скорострельное и энергетическое оружие открывает огонь мгновенно, достигая цели в момент выстрела. Они делают это, формируя перед персонажем прямую линию (игрок видит ее в качестве луча или пулеметной очереди), которая регистрирует столкновение с объектом, то есть целью. Поэтому нам нужно компенсировать задержку, связанную как раз с формированием этой линии.

Если сервер хранит информацию о точках расположения объекта в прошлом, значит, он, используя данные о пинге, может узнать, где этот объект находился, когда игрок выстрелил в него. Именно это положение объекта в прошлом изображает синий робот на картинке ниже. Взгляните на самый первый скриншот — точно так же противника видит и игрок, и стреляет он тоже, по сути, в синего, «ненастоящего» робота. MWO обращается к позиции синего робота, «перематывает» положение реальной цели назад, формирует линию и проверяет, попали ли мы во врага или нет (в игровых движках, например, в Unreal Engine 4, этот метод называется Line Trace, поэтому далее в статье он будет упоминаться именно под этим названием – примечание переводчика).

Дальше – дело техники. Регистрируется попадание и нанесение урона (если мы, конечно, не промахнулись), а сервер «возвращает» цель на свою настоящую позицию. Такая операция позволяет серверу точно прогнозировать каждую подобную ситуацию и проверять, сталкивается ли создаваемая персонажем линия с нужным объектом.

«Перемотанная» позиция игрока с точки зрения сервера

В игре есть ключевая механика — каждая часть робота повреждается по-разному — это отражается как во внешнем виде, так и в геймплее. Именно поэтому на скриншоте видна разница в «походке» одного и того же робота в разные моменты времени.

Теперь давайте разберемся с метательными орудиями.

Они работают совсем иначе. Такой вид вооружения выпускает геометрический объект (пулю или ракету), который самостоятельно движется по своей траектории и наносит урон при контакте с целью. Простая «перемотка» тут не сработает. Если бы использовался тот же метод, получилось бы, что снаряд долетает до противника моментально — а ведь вражеский робот может уйти с линии огня, пока ракета еще находится воздухе. Кроме того, игроки стреляют из таких орудий с опережением, руководствуясь элементарными законами баллистики. В таком случае они бы постоянно промахивались, и это портило бы впечатление от игры.

На стороне клиента происходит выстрел

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

К тому времени, когда сервер получит запрос на открытие огня, пройдет половина времени между отправкой запроса и получением ответа. Но не забывайте, что сервер должен обгонять клиентскую часть на такое же время — на половину пинга. Поэтому в этот период снаряд обязан существовать (если пинг составляет 0.1 секунды, то снаряд должен «жить» в течение 50 миллисекунд), чтобы иметь возможность взаимодействовать с окружающим миром. В течение этого времени снаряд уже может достичь цели, например, если ракета была выпущена в упор. Сервер должен корректно обработать и эту ситуацию, но давайте не будем сейчас её рассматривать. Назовем первый этап жизненного цикла снаряда «периодом перемотки».

Все, что происходит с ним после — это этап, во время которого серверная симуляция снаряда и клиентское прогнозирование его движения должны синхронизироваться. Очень важно отметить, что в данном контексте под синхронизацией подразумевается не одновременное нахождение снаряда в одной точке и на сервере, и на стороне клиента. Учитываем отставание клиента на половину пинга, поэтому в нашем случае синхронизация означает одинаковое расположение снаряда относительно других объектов. С первым этапом дела обстоят чуть сложнее, поэтому для начала проанализируем второй.

Давайте «упростим» физику снаряда и представим, что он движется по прямой и с постоянной скоростью. В этом случае, к тому времени, когда запрос на выстрел дойдет до сервера, на стороне клиента снаряд уже пройдет путь, равный произведению его скорости на пинг. Даже если в дело вступают эффекты, усложняющие физику, например, центробежная сила или притяжение, путь можно рассчитать, используя более комплексные формулы.

Траектория движения снаряда с точки зрения сервера – взгляд сверху

Если мы берем во внимание «период перемотки», сервер может сделать правильный прогноз или синхронизироваться с клиентом, просто создав копию снаряда в том месте, куда он должен долететь согласно расчетам. Другими словами, сервер «выстреливает» не в точке, где находится пушка, а на определенном расстоянии от начала траектории. Таким образом можно рассчитать любой тип движения, главное, чтобы оно было детерминированным — то есть предопределенным, а не непредсказуемым. А что насчет «периода перемотки»?

Если бы мы всегда «создавали» снаряды сразу на расстоянии от персонажа, это бы вызвало трудности – патроны и ракеты начали бы проходить сквозь цели или элементы окружения. Со статичными мишенями и окружением разобраться можно — вспоминаем прием с формированием «проверочной» линии перед игроком. Но что насчет движущихся целей?

Если объект оказалась на пути снаряда еще во время «периода перемотки», может получиться так, что попадание осуществилось на стороне клиента еще до того, как соответствующий сигнал дошел до сервера. Игра должна правильно обрабатывать такие моменты, потому что это может стать серьезной проблемой — к примеру, если в игре есть ситуации, когда герои сражаются в ближнем бою.

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

К сожалению, у этого решения есть несколько недостатков. Во-первых, быстро движущиеся снаряды будут каждый тик (во время одного тика происходит обмен информацией между клиентом и сервером, компенсация лагов и другие необходимые технические процессы; частота обновления сервера определяется количеством тиков в секунду, высокая частота обновления повышает плавность и отзывчивость онлайн-игры, но его поддержание требует высокой производительности как от клиентской, так и от серверной части, — примечание переводчика) проходить сквозь окружающие объекты и цели без какой-либо непрерывной проверки на столкновение. Во-вторых, эта технология обходится недешево. Если у игроков высокий пинг, обработка каждого выстрела на сервере может влететь компании в копеечку. В MWO происходит много перестрелок, а снаряды летят очень быстро, поэтому мы решили отказаться от такого подхода.

Чтобы прийти к методу, который не будет основываться на итерациях, давайте сделаем несколько предположений для упрощения задачи. Что мы знаем о наших снарядах? Мы можем принять их за материальную точку, чтобы аппроксимировать их траекторию с помощью Line Trace (помните, как мы компенсировали лаги для скорострельного оружия?). Траекторию «периода перемотки» тоже можно аппроксимировать сравнительно точно с помощью той же прямой линии. В течение «периода перемотки» снаряд имеет постоянную скорость. В MWO используются законы баллистики, так что снаряды не всегда движутся по идеальной прямой, но по сравнению с роботами они бесконечно малы и имеют очень высокую, не снижающуюся со временем скорость. Поэтому, опять же, мы имеем полное право принять их за материальную точку для упрощения физических расчетов.

Также нам надо сделать несколько предположений касательно движения целей. Предположим, что движение объекта между текущей и «перемотанной» позицией (вспоминаем синего робота) может быть аппроксимировано сервером, используя прямую линию и знание того, что скорость объекта в этот период постоянна. Все эти предположения с трудом можно применить к некоторым играм с динамичным геймплеем, но, к счастью, в MWO роботы перемещаются, поворачиваются, ускоряются и замедляются медленно и плавно, поэтому в нашем случае такой подход сработает.

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

Чтобы проверить, возможно ли попадание, мы сначала определяем «перемотанную» позицию цели, основываясь на текущем пинге игрока и векторе, который изображает траекторию снаряда в течение «периода перемотки». Это похоже на то, чем мы занимались при работе со скорострельным оружием. Чего мы хотим добиться — узнать, пересекаются ли два этих объекта, пока они движутся по своим траекториям. Используя вышеупомянутые предположения, мы изменяем задачу — используем метод Line Trace, считая скорость снаряда относительно статичного «перемотанного» объекта. Посмотрите на изображение:

Основываясь на предположениях, сервер может трансформировать задачу слева в задачу справа – её проще решить

Решение этой задачи эквивалентно решению задачи с попаданием в сторонние объекты, о которой я рассказывал чуть раньше. Вы можете представить, что снаряд движется от стартовой позиции до конца своего «периода перемотки» в течение одного тика. Проверка на наличие столкновений этого объекта с другими — процесс непрерывный.

Этот процесс не аналогичен классическому Line Trace. Мы намеренно игнорируем окружающие препятствия и рассматриваем только «перемотанную» цель и скорректированную траекторию — это очень важный момент. Потому что если мы не будем их игнорировать, может получиться так, что игра зарегистрирует промах, когда игрок открывает огонь по цели, которая только что вышла из-за укрытия:

Случай, при котором геометрический объект «обманывает» сервер, в итоге получаем ошибочный результат анализа траектории и ложный промах

Не забываем, что использование этого метода лишь сообщает о возможности столкновения , но не проверяет, произошло ли оно. А нам нужно это сделать. Если первичная проверка дала положительный результат, мы получаем все необходимые данные, чтобы провернуть что-то вроде процесса «отмотки» первоначальной траектории, с помощью которого мы компенсировали лаги скорострельного оружия. Проверка сообщает, что если столкновение происходит, то это случается где-то между «перемотанной» и текущей позицией цели. Так как снаряд и цель движутся в течение одного и того же промежутка времени, это «где-то» — именно та зона на скорректированной траектории, где происходит столкновение. Иногда она называется «временем попадания».

Мы можем использовать его, чтобы рассчитать скорректированное значение пинга и на его основе «отмотать» цель обратно — к позиции, расположенной примерно в середине траектории.

Стадии «перемотки» снаряда на стороне сервера: Черная линия – первоначальное направление стрельбы в «период перемотки». Голубая линия – скорректированный вектор для непрерывной проверки на столкновения с объектами. Черный робот – текущая позиция цели, когда был произведен выстрел. Голубой робот – «перемотанная» позиция цели, вычисленная на основе пинга игрока. Желтый робот – позиция основного «кандидата» на окончательную проверку с помощью «перемотки»

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

Заключение

Орудия, компенсирующие лаги — только одна из сложностей, с которыми сталкивается не только MWO, но и мультиплеерные игры в целом.

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

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

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

#технологии

Комментарии
Последние Лучшие

Спасибо за перевод. Хотелось бы рассказов об античит-техниках в подобных играх.

Спасибо, что читаете! Про античит напишем, есть на примете интересные материалы.

Интересная статья. Спасибо!

Прямой эфир
Лучшие материалы
Посмотреть все
Узнавайте первым
о важных новостях
Мы будем присылать вам только срочные уведомления в браузере
Хидэо Кодзима покинул Konami и перешел в Sony
Хочу знать!
Не нужно