image

Примечание переводчика: этой статье уже 17 лет, и интересна она только с исторической точки зрения. Любопытно узнать, как удавалось разработчикам добиться плавной сетевой игры в эпоху 28,8k-модемов и первых «Пентиумов».

В этой статье рассказывается об архитектуре и реализации, а также о некоторых уроках, полученных при создании многопользовательского (сетевого) кода игр Age of Empires 1 и 2. Также в ней излагаются современные и будущие подходы с созданию сетевой архитектуры, используемые Ensemble Studios в своих игровых движках.

Мультиплеер Age of Empires: требования к структуре


В начале работы над многопользовательским кодом Age of Empires в 1996 году мы поставили перед собой очень конкретные цели, необходимые для реализации требуемого игрового процесса.

  • Масштабные и эпичные исторические битвы со множеством различных боевых единиц
  • Поддержка до 8 игроков в многопользовательском режиме
  • Плавная симуляция игрового процесса по LAN, через прямое модемное соединение и по Интернету
  • Поддержка целевой платформы: Pentium 90 с 16 МБ ОЗУ и модемом на 28,8 кбит/с
  • Система коммуникаций должна работать с уже имеющимся движком (Genie)
  • Стабильные 15 кадров в секунду на машинах с минимальной конфигурацией

Движок Genie уже был готов, а игровой процесс в однопользовательском режиме начинал принимать свои формы. Движок Genie — это двухмерный однопоточный движок игрового цикла. Спрайты рендерятся в 256 цветах в состоящем из тайлов мире. Случайно генерируемые карты заполнены тысячами объектов: от деревьев, которые можно срубать, до скачущих газелей. Приблизительная разбивка (после оптимизации) времени на выполнение задач движка: 30% на рендеринг графики, 30% на ИИ и поиск путей, 30% на выполнение симуляции и служебных задач.

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

Усложняло задачу и то, что время на выполнение каждого шага симуляции могло сильно меняться: время рендеринга зависело от того, наблюдает ли пользователь за юнитами, выполняет скроллинг или смотрит на неисследованную область, а длинные пути или стратегическое планирование ИИ значительно влияли на время выполнения игрового хода: колебания составляли до 200 мс.

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

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

Одновременные симуляции


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

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

Усовершенствование базовой модели

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

Поскольку при таком подходе она должна брать на себя ответственность за одновременно перемещение сотен или тысяч объектов, система обязана оставаться жизнеспособной даже при колебаниях задержек от 20 до 1000 миллисекунд и успевать обрабатывать изменения во время обработки кадров.

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

Марк [Террано] использовал систему пометки команд, которые должны быть выполнены через два «хода обмена данными» в будущем (ходы обмена данными в AoE были отделены от самих рендерящихся кадров).

То есть команды, отданные в ходе 1000, назначаются для выполнения во время хода 1002 (см. Рис. 1). В ходе 1001 выполняются команды, отданные в ходе 0999. Это позволяло нам принимать, подтверждать и подготавливать к обработке сообщения, в то время как игра продолжала отрисовывать анимации и выполнять симуляцию.


Рисунок 1. Разметка команд, которые должны выполняться через два «хода обмена данными».

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

«Контроль скорости»



Рисунок 2. Контроль скорости.

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

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

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

Кроме того, каждый клиент также измерял «время пинга» от себя до других клиентов и обратно. Средний пинг до самого долгого клиента он отправлял тоже в сообщении о завершении хода (всего для контроля скорости использовалось 2 байта).

В каждом ходе машина, назначенная хостом, анализировала сообщения о завершении хода, вычисляла необходимую частоту кадров и поправку на задержку передачи данных по Интернету. Затем хост отправлял новую частоту кадров и длительность хода обмена данными. На рисунках 3-5 показано, как разбивался ход обмена данными в разных условиях.


Рисунок 3. Обычный ход обмена данными.


Рисунок 4. Высокая задержка передачи данных по Интернету с нормальной скоростью машины.


Рисунок 5. Низкая скорость машины с нормальной задержкой передачи данных.

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

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

Гарантированная доставка


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

Скрытые преимущества


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

Скрытые проблемы


Поначалу может показаться, что одинаковое выполнение двух экземпляров аналогичного кода реализовать легко, но это не так. Менеджер по продукции Microsoft Тим Знаменачек ещё на самых ранних этапах проекта сказал Марку: «В каждом проекте есть один упорный баг, который не сдаётся до самого финиша. Думаю, в нашем случае это будет рассинхронизация». И он оказался прав. Сложности поиска ошибок рассинхронизации множились при каждом небольшом изменении. Олень, чьё положение слегка отличается при создании случайной карты, будет двигаться немного иначе, а минуты спустя охотник слегка сместится с пути или промахнётся копьём, в результате вернувшись домой без мяса. Поэтому то, что иногда казалось всего лишь разницей в контрольных суммах количества пищи, имело причины, которые было очень сложно отследить.

Хотя мы проверяли контрольными суммами мир, объекты, поиск путей, прицеливание и все остальные системы, всегда оставалось что-то, чего мы не могли учесть. Огромные (по 50 МБ) объёмы трассировки сообщений и дампов объектов мира ещё больше усложняли проблему. Часть сложностей была концептуальной — программисты не привыкли к написанию кода, который использовал одинаковое количество вызовов генератора случайных чисел в симуляции (да случайные числа тоже порождались и синхронизировались).

image

Полученные уроки


При разработке сетевой части Age of Empires мы получили несколько уроков, которые можно применить к разработке любой игровой многопользовательской системы.

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

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

Для игр жанра RTS задержки передачи команд в 250 миллисекунд даже незаметны, при 250-500 мс геймплей вполне играбелен, а заметны тормоза становятся при 500 мс и выше. Также интересно заметить то, что игроки привыкли к «темпу игры» и мысленному ожиданию задержки между нажатием мыши и реакцией юнита. Постоянная замедленная реакция была лучше, чем скачки задержек передачи команд (допустим, от 80 до 500 мс) — в этом случае постоянные задержки в 500 мс воспринимались играбельными, а переменчивые казались «дёргаными» и усложняющими игру.

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

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

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

В целом, наблюдение за пользователями позволит вам:

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

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

Урок: часть проблема с обменом данными в AoE возникла, когда Марк выводил метрики слишком рано, и не проверял уровни сообщений (длину и частоту) заново после подготовки финального кода. Такие неожиданные вещи, как случайные гонки между ИИ, сложные в вычислении пути и плохо структурированные пакеты команд могут вызывать огромные проблемы производительности, даже когда система в остальном работает хорошо.

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

Потратьте время на объяснение тестерам работы системы обмена данными, покажите и объясните им метрики — вас может удивить то, что они заметят, когда в сетевом коде неизбежно будут возникать странные сбои.

В целом, метрики должны обладать следующими свойствами:

  • Быть человекочитаемыми и понятными тестерам
  • Указывать на узкие места, тормоза и проблемы
  • Мало влиять на выполнение и постоянно быть запущенными.

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

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

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

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

Проводите тестирование с модемами (и, если повезёт, с симуляторами модемов) как можно раньше; продолжайте модемное тестирование (как бы мучительно это ни было) на протяжении всего процесса разработки. Ведь проблемы сложно изолировать (в чём причина резкого снижения скорости — провайдер, игра, коммуникационное ПО, модем, служба поиска соперников для матча или что-то ещё?), а пользователи не хотят возиться с медленными dialup-соединениями, привыкнув к мгновенным скоростям LAN. Жизненно необходимо выполнять тестирование на модемных соединениях с тем же упорством, что и при многопользовательских играх по LAN.

image

Усовершенствования для Age of Empires 2


В Age of Empires 2: The Age of Kings мы добавили такие многопользовательские функции, как запись игр, передача файлов и постоянное отслеживание статистики на сайте The Zone. Также мы улучшили такие системы мультиплеера, как интеграция с DirectPlay и контроль скорости, чтобы справиться с багами и проблемами скорости, выявленными после выпуска Age of Empires.

Функция записи игр была одной из тех вещей, которые изначально придумывались для отладки, а в результате стали полномасштабной «фишкой» игры. Записанные игры невероятно популярны на фан-сайтах. Они позволяют игрокам обмениваться стратегиями и анализировать их, просматривать знаменитые битвы и изучать игры, в которых они участвовали. Запись игр стала бесценным инструментом отладки. Поскольку наша симуляция детерминирована и записанные игры синхронны в том же смысле, что и мультиплеер, запись игр предоставила нам отличный способ воспроизведения багов, потому что каждый раз она гарантировано проигрывалась одинаково.

Наша интеграция с системой поиска соперников (матчмейкинга) сервиса The Zone была ограничена в Age of Empires простым запуском игры. В Age of Kings мы расширили её, и это позволило нам управлять параметрами запуска и обеспечивать постоянную отчётность о статистике. Это позволяло игрокам лучше находить игры, которые им были интересны, потому что они могли видеть параметры уровня матчмейкинга, а не ждать перехода на экран настройки игры. В бэкенде мы реализовали постоянную отчётность и отслеживание статистики. Мы предоставили The Zone общую структуру, которая заполнялась и передавалась на сервер в конце игры. Данные из этой структуры использовались для создания рейтингов пользователей и их демонстрации на веб-сайте The Zone.

image

Мультиплеер RTS3: задачи


RTS3 — это кодовое имя стратегической игры Ensemble нового поколения (прим. пер.: игра вышла под названием Age of Mythology). Структура RTS3 создаётся на основе успешной формулы, использованной в серии игр Age of Empires, с добавлением множества новых функций и требований к многопользовательскому режиму.

  • Основана на наборе возможностей Age of Empires 1 и 2. Обязательны такие требования, как игра по Интернету, большие и разнообразные карты, тысячи управляемых юнитов.
  • 3D: RTS3 — это полностью трёхмерная игра с интерполируемой анимацией и недискретными положениями и поворотами юнитов.
  • Больше игроков — поддержка более чем восьми игроков.
  • Поддержка TCP/IP: наша основная цель — Интернет-подключение TCP/IP со скоростью 56 кбит/с.
  • Поддержка домашних сетей — поддержка сетевых конфигураций конечных пользователей, в том числе фаерволлов и NAT.

Ещё на ранних этапах разработки RTS3 мы приняли решение придерживаться той же внутренней сетевой модели, что и в Age of Empires 1 и 2 — синхронной симуляции — потому что структура RTS3 сможет во многом использовать преимущества такой архитектуры. В AOE/AOK мы использовали для служб передачи и управления сессиями DirectPlay, но для RTS3 решили создать базовую сетевую библиотеку, используя в качестве основы исключительно основные процедуры сокетов.

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

Завершая разработку Age of Kings, мы хотели заняться этими критически важными областями, в которых продуманный дизайн и работа с использованием инструментов сильно сократит время отладки. Также мы осознали, насколько важен в дизайне наших игр итеративный процесс плейтестинга, поэтому большой приоритет отдавался как можно более раннему выводу игры онлайн.

Архитектура обмена данными RTS3



Рисунок 6. Строгая объектно-ориентированная сетевая архитектура RTS3.

Объектно-ориентированный подход. Сетевая архитектура RTS3 обладает строгой объектно-ориентированностью (см. Рисунок 6). Требования поддержки различных сетевых конфигураций позволяют воспользоваться преимуществами ОО-подхода, абстрагироваться от специфики платформы, протокола и топологии, лежащих в основе набора обобщённых объектов и систем.

Специфичные для протокола и топологии версии сетевых объектов содержат как можно меньший объём кода. Основной функционал этих объектов изобилирован в высокоуровневые родительские объекты. Для реализации нового протокола мы расширяли только те сетевые объекты, которым требовался специфичный для протокола код (например, для клиента и сессии, которые в зависимости от протокола должны действовать немного по-разному). Никакие другие объекты системы (такие как Channels, TimeSync и т.д.) не требовали изменений, потому что они имели интерфейс с клиентом и сессией только через свои высокоуровневые абстрактные интерфейсы.

Одноранговая топология. Движок Genie поддерживал топологию сети peer-to-peer, в которой все клиенты в сессии соединяются друг с другом в конфигурации «звезда». В RTS3 мы продолжили использовать эту топологию, потому что при реализации с моделью синхронной симуляции она имеет неотъемлемые преимущества.

Одноранговая топология подразумевает использование для подключенных в сессии клиентов конфигурации «звезда» (Рисунок 7). То есть каждый клиент подключен ко всем остальным клиентам. Такая же схема использовалась в Age 1 и 2.


Рисунок 7. Конфигурация «звезда» одноранговых клиентов в сессии.

Преимущества Peer-to-peer:

  • Снижение латентности благодаря схеме передачи сообщений «клиент-клиент» вместо «клиент-сервер-клиент».
  • Нет центрального слабого звена — если клиент (даже хост) отключается от сессии, игра может продолжаться.

Недостатки Peer-to-peer:

  • Больше активных соединений в системе (сумма от n=0 до k-1 (n)), то есть больше потенциальных слабых звеньев и выше вероятные задержки.
  • Невозможность поддержки в такой схеме некоторых конфигураций NAT.

Net.lib. При разработке архитектуры обмена данными RTS3 нашей целью было создание системы, предназначенной специально для стратегических игр, но в то же время мы хотели создать систему, которую можно использовать для наших внутренних инструментов, а также расширить её для поддержки будущих игр. Чтобы достичь этой цели, мы создали многослойную архитектуру, поддерживающую такие объекты игрового уровня, как клиент и сессия, но также поддерживающую такие низкоуровневые транспортные объекты, как ссылки и сетевые адреса.


Рисунок 8. Четыре слоя служб нашей сетевой модели.

RTS3 основана на нашем движке BANG! нового поколения, в котором используется модульная архитектура с такими библиотеками компонентов, как звук, рендеринг и сеть. Подсистема сети встроена сюда как компонент, но связана с движком BANG! (а также с различными внутристудийными инструментами). Наша сетевая модель разделена на четыре слоя служб, которые почти, но не полностью, не похожи на сетевую модель OSI, применённую в игре (см. Рисунок 8).

Socks, уровень 1

Первый уровень, Socks, обеспечивает фундаментальный API уровня сокетов на языке C. Он абстрагирован для создания обобщённого набора низкоуровневых сетевых процедур для множества операционных систем. Интерфейс напоминает интерфес сокетов Беркли. Уровень Socks в основном используется более высокими уровнями сетевой библиотеки и на самом деле не предназначен для использования кодом приложения.

Link, уровень 2

Уровень 2, Link, обеспечивает службы транспортного слоя. Объекты на этом уровне, такие как Link, Listener, NetworkAddress и Packet, представляют собой полезные элементы, необходимые для установки соединения и передачи по нему сообщений (см. Рисунок 9).

  • Packet (пакет): это наша фундаментальная структура сообщений — расширяемый объект, автоматически управляющий своей сериализацией/десериализацией (исключительно с помощью виртуальных методов) при передаче по объекту ссылки.
  • Link (ссылка): соединение между двумя конечными точками сети. Она может быть также ссылкой на себя, и в этом случае обе конечные точки находятся на одной машине. Методы send и receive ссылки знают, как работать с пакетами, а также с буферами данных void*.
  • Listener (слушатель): генератор ссылок. Этот объект слушает входящие соединения и создаёт ссылку после установки соединения.
  • Data stream (поток данных): это произвольный измеряемый поток данных через заданную ссылку, используемый, например, для передачи файлов.
  • Net Address (сетевой адрес): объект сетевой адресации, независимый от протокола.
  • Ping: простой класс пинга. Сообщает о задержке сети, присутствующей при связи со ссылкой.

  • Рисунок 9. Уровень Link.

Multiplayer, уровень 3
Уровень мультиплеера — это самый высокий уровень объектов и процедур, присутствующий в API net.lib. Это слой, с которым взаимодействует RTS3 при сборе объектов более низкого уровня, таких как ссылок и их преобразовании в более полезные концепции/объекты — клиенты, сессии и так далее.

Самыми интересными объектами в сетевой библиотеке BANG! являются те, которые находятся в уровне мультиплеера. Здесь API предоставляет набор объектов, с которыми может взаимодействовать уровень игры, однако обеспечивает в реализации независимый от игры подход.

  • Client (клиент): самая базовая абстракция конечной точки сети. Может быть сконфигурирована как удалённый клиент (ссылка) или как локальный клиент (ссылка на себя). Клиенты не создаются напрямую, а порождаются объектом сессии.
  • Session (сессия): это объект, отвечающий за создание, выполнение соединений, сбор и управление клиентами. Сессия содержит все другие объекты уровня мультиплеера. Для использования этого объекта приложение просто вызывает host() или join(), передавая им или локальный, или удалённый адрес, а всем остальным занимается сессия. В сферу её обязанностей входит автоматическое создание/удаление клиентов, отправка уведомлений о событиях сессии и управление трафиком к соответствующим объектам.
  • Channel и Ordered Channel: этот объект представляет собой виртуальный канал передачи сообщений. Передаваемые по каналу сообщения автоматически разделяются и получаются соответствующим объектом канала в удалённых клиентах. Упорядоченный канал работает с объектом TimeSync, чтобы обеспечить одинаковый порядок полученных по этому каналу сообщений для всех клиентов.
  • Shared Data: представляет собой коллекцию общих для всех клиентов данных. Можно расширить этот объект для создания конкретных экземпляров, содержащих ваши собственные типы данных, а затем использовать встроенные методы для обеспечения автоматического и синхронного обновления этих элементов данных по сети.
  • Time Sync: управляет плавным изменением синхронизированного сетевого времени для всех клиентов в сессии.

Game Communications, уровень 4

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

image

Новые функции и улучшенные инструменты


Улучшенная система синхронизации. Никто из команды разработки Age of Empires не мог бы сказать, что нам не нужны более качественные инструменты синхронизации. Как и в любом проекте, при анализе процесса разработки в постмортеме оказывается, что на некоторые области тратилось больше всего времени, но его могло быть гораздо меньше, если бы мы занялись ими заранее. При начале разработки RTS3 в верхних строчках списка таких областей была отладка синхронизации.

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

Проверка синхронизации в RTS3 выполняется с помощью двух наборов макросов:

#define syncRandCode(userinfo)
gSync->addCodeSync(cRandSync, userinfo, __FILE__, __LINE__)


#define syncRandData(userinfo,
v) gSync->addDataSync(cRandSync, v, userinfo, __FILE__, __LINE__)


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

syncRandCode("syncing the random seed", seed);

Синхронные команды консоли и переменные конфигурации. Как может подтвердить любой разработчик модов для Quake, консольные команды и переменные конфигурации очень важны для процесса разработки. Консольные команды — это простые вызовы функций, выполняемые с помощью файла конфигурации запуска, внутриигровой консоли или UI, которые вызывают произвольный функционал игры. Переменные конфигурации являются именованными типами данных, предоставляемыми через простые функции get, set, define и toggle, которые мы используем для всевозможного тестирования и настройки параметров конфигурации.

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

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

Подводим итоги


Синхронная симуляция и модель peer to peer успешно использовались в серии игр Age of Empires. Несмотря на критическую важность вложения времени в создание инструментов и технологий для решения основных проблем такого подхода (таких как синхронизация и сетевые метрики), жизнеспособность этой архитектуры в жанре стратегий реального времени доказана опытом. Последующие улучшения, внесённые нами в RTS3, привели к тому, что многопользовательский игровой процесс практически неотличим от однопользовательского даже в самых ужасных условиях сетевых соединений.

Комментарии (9)


  1. Jeka178RUS
    25.07.2018 13:28
    +1

    Очень интересно читать про механику старых игр и видеть объяснение и принцип работы особенностей которые подмечал когда сам в них играл несколько лет назад.


  1. lokkiuni
    25.07.2018 13:30

    С похожими проблемами сталкивались разработчики Factorio — тоже решили всё полной детерминизацией мира, в результате чего выловили кучу ошибок в виде немного разных результатов float на 32/64бит и разных платформах. Как раз таки ссылались на эту статью в блоге, но в оригинале руки не до шли прочитать.

    В общем, спасибо за перевод, добавил в закладки)


    1. Welran
      25.07.2018 21:25
      +1

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


      1. DmitryKoterov
        26.07.2018 06:04

        По идее, сопроцессоры должны строго следовать стандарту IEEE работы с плавающей точкой. Стандарт очень строгий. Там расхождения не допускаются ни в одном бите.


        Может быть, чтобы просто работало как с сопроцессором, так и без?


    1. DjSapsan
      26.07.2018 12:42

      Зато решили огромные проблемы, которые мешались в первых версиях. Даже по локалке было сложно. То не подключается, то очень долгий сейв с перебоями, то выкидывает. А с float вопрос легко решаемый


  1. Desavian
    25.07.2018 17:50

    Тем не менее, важна еще и общая динамика игры. Да, в AoE при 250мс было комфортно, в том же старкрафте — чудовищная просадка игровой эффективности.


  1. DmitryKoterov
    26.07.2018 06:03
    +2

    Много таких игр было. Еще Doom 2, Duke Nukem 3D.


    1. lgorSL
      26.07.2018 10:22

      В Казаках тоже так было сделано. В старых точно, насчёт новой третьей части — не знаю.


  1. eee
    26.07.2018 12:04

    Помнится в StarCraft-е быстрая перемотка записи меняла исход игры при игре с ботами.