Первые компьютерные игры работали так: запустил, поиграл один, выключил. Или поиграл на пару с кем-то на одном компьютере в пинг-понг или гонки. Но нельзя было поиграть с соседом, который живёт парой этажей выше, или с друзьями, живущими в другом городе. Сейчас всё иначе — появились мультиплеерные игры, в которые могут играть сразу много человек, каждый на своём компьютере, и при этом они могут взаимодействовать и с игрой, и друг с другом.
Для этого разработчикам нужно было решить такую проблему: как синхронизировать всё происходящее на экране с действиями каждого игрока. Например, чтобы каждый мог видеть, что делают другие игроки, а игра учитывала их действия в общем игровом процессе. Сегодня расскажем, какие технологии для этого нужны и как вообще работает мультиплеер в играх.
Как игроки связываются друг с другом в мультиплеерных играх
Чтобы люди с разных устройств могли сыграть вместе, нужно как-то связать эти устройства между собой. Для этого используется несколько технологий, но общий принцип такой: есть какой-то общий элемент синхронизации действий игроков, который отвечает за мультиплеер, и все устройства ориентируются на него. Это может быть реализовано несколькими способами.
Клиент — сервер. Вся игровая логика размещается на отдельном сервере, к которому подключаются игроки — часто для этого нужно авторизоваться. От сервера зависят скорость и качество соединения:
Хост. В этой модели сервером служит компьютер одного из игроков, а другие подключаются к этому компьютеру. В остальном всё работает точно так же, как и в модели «Клиент-сервер».
Если не нужна сложная инфраструктура выделенного сервера, это самый рабочий вариант. Хост-модель часто используют в компьютерных клубах и при игре по локальной сети:
Модель peer to peer. В качестве сервера выступает компьютер каждого игрока. Модель одновременно управляет связями между игроками и общим состоянием игры. Такая модель удобна тем, что если у кого-то плохое соединение, то это не сильно сказывается на общем быстродействии игры, потому что данные для обработки перераспределяются между более быстрыми компьютерами.
Раньше peer to peer использовали почти все мультиплееры, но с оптимизацией технологий разработчики чаще предпочитают модель «клиент — сервер».
Асинхронный режим не требует одновременного присутствия в сети всех участников процесса. Так, например, работают шахматы без ограничения на время хода.
Как используется модель OSI в играх
Независимо от того, общаются ли компьютеры через интернет или по локальной сети, они используют один и тот же протокол — сетевую модель передачи данных OSI.
Эта модель делится на 7 уровней, на каждом из которых происходит своя часть работы. Вот что находится на этих уровнях для онлайн-мультиплеера между игроками и сервером в разных точках Земли:
- Физический уровень. Сначала информация об игре на компьютере игрока отправляется на роутер через Wi-Fi или по Ethernet-кабелю. Тут используются физические параметры связи: частоты, Wi-Fi-протоколы, соединения по проводам и всё такое.
- На канальном уровне настраивается соединение между узлами — например, компьютером и роутером.
- Сетевой уровень. Все данные об игре делятся на небольшие части — пакеты. Пакеты получают номер, находят сервер по IP-протоколу и отправляются туда.
- Транспортный уровень работает для игр в двух вариантах: протокол TCP или UDP. Протокол TCP медленный, но точный: с ним все данные точно дойдут до получателя. UDP быстрый, но не такой аккуратный: часть пакетов может потеряться по дороге. UDP удобнее для передачи данных в реальном времени, например изображения и видео. Если вы играете в онлайн-шутер — там точно будет UDP. TCP иногда используется для установки соединения и передачи пользовательской информации.
- На сеансовом уровне между сервером и игроком устанавливается связь. Например, сервер может попросить частника многопользовательской игры ввести логин и пароль.
- Уровень представления отвечает за сжатие и шифрование данных, чтобы обмениваться ими быстро и безопасно.
- Прикладной уровень. Здесь находятся приложения и протоколы для ответов и запросов. Например, при общении с сайтом мы используем интернет-браузер и HTTP-протокол. В играх вместо браузера — приложение игры.
Чтобы два компьютера могли общаться между собой, они открывают сокет: конечную точку соединения для обмена данными. Сокет будет общим для обеих машин:
Для открытия сокета компьютеру нужен IP-адрес и номер порта. IP-адрес не всегда уникален для каждого компьютера. Между домашним компьютером и интернетом есть узел NAT (Network Address Translator), который объединяет много машин в один IP-адрес. Чтобы данные не заблудились в сети, при адресации используют номер порта, который для каждого компьютера свой:
Что такое состояние игры и как его передавать между участниками
Состояние игры — это информация о текущем моменте игры, которую можно преобразовать на одном компьютере и передать на другой. Например, в шахматах мы можем разбить весь процесс на 64 элемента-клетки и передавать данные о каждой: она пустая или какая фигура там стоит.
Чтобы информацию можно было передать по сети или записать на диск, её сериализуют: превращают в строку, из которой можно восстановить данные в исходном виде. После этого компьютер и сервер просто обмениваются этими строками и берут из них нужные данные. Так можно сделать с чем угодно: с трёхмерными моделями, звуками и координатами. В зависимости от игры объём передаваемых данных и частота обновления будут разными.
Частоту обновления состояния отражает показатель tick rate, или simulation rate, который измеряется в герцах. Tick rate в 60 герц означает, что за одну секунду компьютер-сервер 60 раз обрабатывает состояние и отправляет его компьютерам-клиентам. Клиент берёт данные и отрисовывает состояние игры у себя. Сервер между отправкой состояния и началом новой обработки бездействует:
Как синхронизируется состояние игр в реальном времени
В онлайн-шутерах и других подобных играх обмен данными должен происходить быстро, и в этом случае используется протокол UDP. Он передаёт пакеты данных быстро, но необязательно в том же порядке, в каком их отправляет сервер. На примере анимации — если клиент начнёт воспроизводить состояние сразу после получения каждого отдельного пакета, изображение может начать прыгать вперёд-назад, потому что сначала пришёл пакет №5, и только потом пакет №3.
Чтобы видео воспроизводилось корректно и плавно, используют буферизацию и прогнозирование позиции объекта. Чаще всего в технологиях создания мультиплеерных игр это уже предусмотрено, и разработчику погружаться в это сильно не нужно (но хорошо бы понимать при этом, что именно там происходит под капотом).
При воспроизведении состояния часть данных загружается заранее, чтобы успеть расположить их в правильном порядке или запросить повторно, если что-то потерялось по дороге. Тогда в случае проблем со связью воспроизведение не прервётся. Это и есть буферизация.
Такой способ используется при потоковой передаче видео на стримингах. В плеерах буфер видно при просмотре, он показывает, сколько видеоданных уже загрузилось:
Для создания буфера проверятся пинг — время, за которое данные доходят от сервера до клиента. Объём буфера устанавливается примерно в два раза больше пинга.
Чтобы сделать анимацию ещё более плавной, на клиенте работает прогнозирование позиции. Для этого приложение игры берёт данные сервера и заранее просчитывает, где (скорее всего) будет находиться объект в движении:
Удалённый вызов функций на других машинах
Иногда нужно специально вызвать синхронизацию игры на других машинах. Например, после какого-то действия игрока, которое оказывает влияние на всех остальных. Для этого есть свой механизм — RPC, или Remote Procedure Call («удалённый вызов процедур»). Это часть кода, которая вызывается на одной машине, а исполняется на другой. Сервер может вызывать RPC для клиента, и наоборот.
Как показывается состояние игры на разных клиентах
Если для перемещения персонажа игроку нужно отправить данные на сервер и потом дождаться, пока эти данные дойдут до всех участников, играть будет неудобно. Нам придётся ждать доли секунды между нажатием на кнопки управления и видимым действием.
Один из вариантов решения этой проблемы — сначала отрисовывать состояние игры на экране клиента без задержки — а уже потом передавать состояние на машины других игроков.
Чаще всего небольшая разница в 100 миллисекунд между двумя участниками не играет роли, но в некоторых соревновательных играх от этого зависит исход раунда или всей игры. Например, серверу приходится решать, кто выстрелил первым — обычно в такой ситуации победителем становится игрок, который попал в другого на своём экране. Из-за этого в шутерах бывает так, что вы вроде бы успели спрятаться или забежать за укрытие, но всё равно получили попадание от другого игрока.
Как всё это использовать
В большинстве технологий для разработки мультиплеера уже встроены все нужные возможности. Например, для движка Unity есть сетевые реализации Mirror, Netcode или Fishnet. Нельзя сказать, что какой-то лучше или хуже, но их возможности немного различаются, и для конкретной игры нужно изучить имеющиеся варианты и выбрать подходящий.
Но основная идея мультиплеера всегда одна — нужно синхронизировать состояние игры для всех и передавать его на клиенты самым быстрым способом.
Что дальше
В следующий раз посмотрим, как устроен мультиплеер в какой-нибудь игре и разберём его плюсы и минусы. Или напишем свой.