Как будет реализована система снимков состояния игры для сетевых игр в реальном времени?


12

Я хочу создать простую многопользовательскую игру клиент-сервер в реальном времени в качестве проекта для моего сетевого класса.

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

Я хочу сделать что-то похожее на сетевую модель Quake 3: в основном, сервер хранит снимок всего игрового состояния; после получения входных данных от клиентов сервер создает новый снимок, отражающий изменения. Затем он вычисляет различия между новым снимком и последним и отправляет их клиентам, чтобы они могли синхронизироваться.

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

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

  • Все данные отделены от логики
  • Различия могут быть рассчитаны между снимками состояний игры
  • Игровыми сущностями все еще легко манипулировать с помощью кода

Как реализован класс снимка ? Как хранятся сущности и их данные? Каждый ли объект клиента имеет идентификатор, который совпадает с идентификатором на сервере?

Как рассчитываются различия между снимками?

В целом: как будет реализована система снимков состояния игры?


4
+1. Это слишком широкий вопрос для одного вопроса, но IMO - это интересная тема, которая может быть примерно раскрыта в ответе.
Кромстер

Почему бы вам просто не сохранить 1 снимок (действительный мир), сохранить все входящие изменения в этом обычном состоянии мира И сохранить изменения в списке или чем-то еще. Затем, когда пришло время отправить изменения всем клиентам, просто отправьте содержимое списка всем им и очистите список, начните с нуля (изменения). Может быть, это не так хорошо, как хранить 2 снимка, но при таком подходе вам не нужно беспокоиться об алгоритмах ускорения создания снимков diff 2.
tkausl

Читали ли вы это: fabiensanglard.net/quake3/network.php - обзор сетевой модели Quake 3 включает обсуждение реализации.
Стивен

Какую игру пытаются построить? Настройка сети сильно зависит от типа игры, которую вы делаете. RTS не ведет себя как FPS с точки зрения сети.
AturSams

Ответы:


3

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

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

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

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

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

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


2

Во-первых, вам необходимо знать, как представлять соответствующие данные в соответствии с протоколом. Это зависит от данных, относящихся к игре. Я буду использовать игру RTS в качестве примера.

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

Игроки должны иметь данные, относящиеся к ним (например, все видимые единицы):

  • Они живы или мертвы?
  • Какого они типа?
  • Сколько здоровья у них осталось?
  • Текущее положение, вращение, скорость (скорость + направление), путь в ближайшее время ...
  • Деятельность: атака, ходьба, строительство, исправление, исцеление и т. Д.
  • эффекты статуса бафф / дебафф
  • и, возможно, другие характеристики, такие как мана, щиты, а что нет?

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

Каждый блок имеет целочисленный идентификатор. Атрибуты перечисляются и, следовательно, также имеют интегральные идентификаторы. Идентификаторы устройств не должны быть длиной 32 бита (это может быть, если мы не бережливы). Это вполне может быть 20 бит (оставляя 10 бит для атрибутов). Идентификатор юнита должен быть уникальным, он вполне может быть назначен счетчиком при создании экземпляра юнита и / или добавлении его в игровой мир (здания и ресурсы считаются неподвижным юнитом, а ресурсам может быть присвоен идентификатор, когда карта загружен).

Сервер хранит текущее глобальное состояние. Последнее обновленное состояние каждого игрока представлено указателем listпоследних изменений (все изменения после указателя еще не были отправлены этому игроку). Изменения добавляются к тому, listкогда они происходят. Как только сервер завершит отправку последнего обновления, он может начать перебирать список: сервер перемещает указатель игрока по списку к его хвосту, собирая все изменения по пути и помещая их в буфер, который будет отправлен игрок (т. е. формат протокола может быть примерно таким: unit_id; attr_id; new_value) Новые юниты также считаются изменениями и отправляются со всеми значениями своих атрибутов принимающим игрокам.

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

Некоторые вопросы, которые вы не подняли, и я думаю, являются интересными:

  1. Должны ли клиенты получать снимок со всеми данными в первую очередь? А как насчет предметов вне их поля зрения? А как насчет тумана войны в играх РТС? Если вы отправите все данные, клиент может быть взломан для отображения данных, которые не должны быть доступны для игрока (в зависимости от других мер безопасности, которые вы принимаете). Если вы отправляете только соответствующие данные, проблема устранена.
  2. Когда необходимо отправлять изменения, а не отправлять всю информацию? Учитывая пропускную способность, доступную на современных машинах, получаем ли мы что-либо от отправки «дельты» вместо отправки всей информации, если да, то когда?
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.