Сеть для стратегий в реальном времени

16

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

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

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

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

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

TL; DR делает RTS с> 50 событиями в секунду, как синхронизировать клиентов?

Maxov
источник
Вы можете реализовать то, что делает Eve-online, и «замедлить» время, чтобы все прошло правильно.
Райан Эрб
3
Вот обязательная ссылка на модель клиент / сервер Planetary Annihilation: forrestthewoods.ghost.io/… Это альтернатива модели lockstep, которая, кажется, работает для них очень хорошо.
DallonF
Подумайте об уменьшении количества событий, отправив одно обновление для всех взятых плиток вместо обновлений для каждой плитки или, как ответил Илмари, путем децентрализации действий, не связанных с игроками.
Лилиенталь

Ответы:

12

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

В одном предложении: единственный способ синхронизировать слишком много элементов за слишком короткое время по слишком медленной сети - это НЕ синхронизировать слишком много элементов за слишком короткое время по слишком медленной сети, а вместо этого детально определять состояние на всех клиентах и ​​синхронизировать только голые потребности (пользовательский ввод).

Леннарт Роллан
источник
6

каждое действие, предпринимаемое игроком, является детерминированным, однако, есть события, которые происходят в запланированные интервалы

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

Запуск игры независимо от реального времени имеет и другие преимущества:

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

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

congusbongus
источник
1
+1: основанный на кадрах правильный выбор, а не основанный на времени. Конечно, вы можете попытаться сохранить N кадров в секунду. Небольшая заминка лучше, чем полная рассинхронизация.
PatrickB
@PatrickB: Я вижу, что во многих играх используется «смоделированное» время, которое не привязано к видеокадрам. World of Warcraft обновляет только такие вещи, как мана, каждые 100 мс, а Dwarf Fortress по умолчанию составляет 10 тиков на кадр.
Mooing Duck
@Mooing Duck: Мой комментарий был специфичен для RTS. Что-то, где небольшие ошибки могут быть допущены и исправлены позже (например, MMORPG, FPS), тогда использование непрерывных значений не только хорошо, но и критично. Тем не менее, детерминированные моделирования, которые должны быть синхронизированы на нескольких машинах? Придерживайтесь рамок.
PatrickB
4

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

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

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


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

  • Сделайте свою игру пошаговой, чтобы каждый ход или «тик» занимал, скажем, 1/50 секунды. (На самом деле, вы, вероятно, могли бы уйти с 1/10 секунды или дольше.) Любые действия игрока, происходящие в течение одного хода, должны рассматриваться как одновременные. Все сообщения, по крайней мере от сервера к клиентам, должны быть помечены номером поворота, чтобы каждый клиент знал, в каком повороте происходит каждое событие.

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

  • Определите согласованный порядок для «одновременных» событий, происходящих в один и тот же ход, чтобы каждый клиент выполнял их в одном и том же порядке. Этот порядок может быть любым, если он детерминирован и одинаков для всех клиентов (и сервера).

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

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

Илмари Каронен
источник
1
Кроме того, синхронизируйте RNG для запуска и извлекайте синхронизированный RNG только тогда, когда сервер говорит вам об этом. В Starcraft1 долгое время была ошибка, когда семя ГСЧ не сохранялось во время повторов, поэтому повторы будут медленно отклоняться от реальных игр.
Mooing Duck
1
@MooingDuck: Хороший вопрос. Фактически, я бы предложил передавать текущее начальное число ГСЧ на каждом повороте, чтобы десинхронизация ГСЧ обнаруживалась немедленно. Кроме того, если вашему коду пользовательского интерфейса нужна случайность, не извлекайте его из того же экземпляра RNG, который используется для игровой логики.
Ильмари Каронен
3

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

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

Кромстер говорит, что поддерживает Монику
источник
1

Прежде всего, вы должны понимать, что PC float / double math НЕ является детерминированным, если только вы не укажете строго использовать IEEE-754 для своих вычислений (будет медленным)

Тогда вот как я бы это реализовал: клиент подключается к серверу и синхронизирует время (позаботьтесь о задержке пинга!) (Для длительного игрового процесса может потребоваться повторная синхронизация метки времени / поворота)

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

У клиентов будет 2 «мира»: один синхронизируется с конечным ходом, другой рассчитывается, начиная с конечного хода, суммируя поступившее в очередь действие до текущей очереди клиента / метки времени.

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

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

Я использую этот алгоритм в стреляющей игре в режиме реального времени, и работает нормально (с клиентом и без него, выполняющим собственные действия, но с пингом сервера, равным 20/50 мс), также каждый X-сервер конечной очереди посылает специальное «все» «Карта клиента», для исправления смещенных значений.

Lesto
источник
Математические проблемы с плавающей точкой, как правило, можно избежать как таковые - в RTS вы обычно можете легко выполнять симуляцию и движение с целой / фиксированной точкой и использовать плавающую точку только для слоя отображения, который не влияет на поведение игры.
Петерис
С целым числом сложно делать горизонтальные плитки, если только это не восьмиугольная доска. Для фиксированной точки ускорение hw отсутствует, поэтому оно может быть медленнее, чем float ieee754
Lesto