Я разрабатываю стратегическую игру в реальном времени для курса информатики, который я прохожу. Кажется, что одним из более сложных аспектов является взаимодействие между клиентом и сервером, а также синхронизация. Я прочитал эту тему (включая 1500 лучников ), но я решил использовать клиент-серверный подход в отличие от других моделей (например, через локальную сеть).
Эта стратегия в реальном времени идет с некоторыми проблемами. К счастью, каждое действие игрока является детерминированным. Тем не менее, есть события, которые происходят в запланированные промежутки времени. Например, игра состоит из плиток, и когда игрок берет плитку, «уровень энергии», значение этой плитки, должно увеличиваться на единицу каждую секунду после ее получения. Это очень быстрое объяснение, которое должно оправдать мой случай использования.
Прямо сейчас я делаю тонких клиентов, которые просто отправляют пакеты на сервер и ждут ответа. Однако есть несколько проблем.
Когда игры между игроками переходят в эндшпиль, часто происходит более 50 событий в секунду (из-за запланированных событий, объясненных ранее, накапливание), и тогда начинают появляться ошибки синхронизации. Моя самая большая проблема заключается в том, что даже небольшое отклонение в состоянии между клиентами может означать разные решения, принимаемые клиентами, которые превращаются в совершенно разные игры. Другая проблема (которая сейчас не так важна) состоит в том, что существует задержка, и нужно ждать несколько миллисекунд, даже секунд после того, как они делают ход, чтобы увидеть результат.
Мне интересно, какие стратегии и алгоритмы я мог бы использовать, чтобы сделать это проще, быстрее и более приятным для конечного пользователя. Это особенно интересно, учитывая большое количество событий в секунду вместе с несколькими игроками в игре.
TL; DR делает RTS с> 50 событиями в секунду, как синхронизировать клиентов?
Ответы:
Ваша цель синхронизации 50 событий в секунду в режиме реального времени звучит для меня так, как будто это нереально. Вот почему о локшаговом подходе, о котором говорилось в статье о 1500 лучниках , говорят!
В одном предложении: единственный способ синхронизировать слишком много элементов за слишком короткое время по слишком медленной сети - это НЕ синхронизировать слишком много элементов за слишком короткое время по слишком медленной сети, а вместо этого детально определять состояние на всех клиентах и синхронизировать только голые потребности (пользовательский ввод).
источник
Я думаю, что это твоя проблема; ваша игра должна иметь только одну временную шкалу (для вещей, влияющих на игровой процесс). Вы говорите, что некоторые вещи растут со скоростью X в секунду ; узнайте, сколько игровых шагов в секунду, и конвертируйте их в показатель X за Y игровых шагов . Тогда даже если игра может замедлиться, все остается детерминированным.
Запуск игры независимо от реального времени имеет и другие преимущества:
Вы также упомянули, что вы сталкиваетесь с проблемами, когда> 50 событий или с задержками до секунд. Это намного меньше по масштабу, чем сценарий, описанный в 1500 лучников , так что посмотрите, можете ли вы профилировать свою игру и узнать, где замедление.
источник
Во-первых, чтобы решить проблему с запланированными событиями, не транслируйте события, когда они происходят , но когда они изначально запланированы. То есть вместо того, чтобы отправлять сообщение «увеличивать энергию элемента мозаичного изображения ( x , y )» каждую секунду, просто отправляйте одно сообщение, говорящее «увеличивать энергию элемента мозаичного изображения ( x , y ) один раз в секунду, пока оно не заполнится, или до прерванный». Каждый клиент отвечает за локальное планирование обновлений.
Фактически, вы можете пойти дальше по этому принципу и передавать только действия игрока : все остальное может быть вычислено локально каждым клиентом (и сервером, если необходимо).
(Конечно, вы, вероятно, также должны иногда передавать контрольные суммы игрового состояния, чтобы обнаружить любую случайную десинхронизацию, и иметь некоторый механизм для повторной синхронизации клиентов, если это происходит, например, путем повторной отправки всех игровых данных из официальной копии сервера клиентам Но, надеюсь, это должно быть редкое событие, встречающееся только при тестировании или во время редких неисправностей.)
Во-вторых, чтобы синхронизировать клиентов, убедитесь, что ваша игра детерминирована. Другие ответы уже дали хороший совет для этого, но позвольте мне включить краткое резюме того, что делать:
Сделайте свою игру пошаговой, чтобы каждый ход или «тик» занимал, скажем, 1/50 секунды. (На самом деле, вы, вероятно, могли бы уйти с 1/10 секунды или дольше.) Любые действия игрока, происходящие в течение одного хода, должны рассматриваться как одновременные. Все сообщения, по крайней мере от сервера к клиентам, должны быть помечены номером поворота, чтобы каждый клиент знал, в каком повороте происходит каждое событие.
Поскольку ваша игра использует архитектуру клиент-сервер, вы можете сделать так, чтобы сервер выступал в качестве окончательного арбитра того, что происходит во время каждого хода, что упрощает некоторые вещи. Однако обратите внимание, что это означает, что клиенты должны также подтвердить свои собственные действия с сервера: если клиент отправляет сообщение «Я перемещаю блок Х на одну плитку влево», а в ответе сервера ничего не говорится о перемещении блока Х, клиент должен предположить, что это не произошло, и, возможно, отменить любую анимацию предсказательного движения, которую они, возможно, уже начали воспроизводить.
Определите согласованный порядок для «одновременных» событий, происходящих в один и тот же ход, чтобы каждый клиент выполнял их в одном и том же порядке. Этот порядок может быть любым, если он детерминирован и одинаков для всех клиентов (и сервера).
Например, вы можете сначала увеличить все ресурсы (что можно сделать сразу, если увеличение ресурсов в одном тайле не может мешать таковому в другом), затем перемещать юниты каждого игрока в заранее определенной последовательности, а затем перемещать юнитов NPC. Чтобы быть справедливым по отношению к игрокам, вы можете варьировать порядок движения юнитов между ходами, чтобы каждый игрок шел первым одинаково часто; это хорошо, если это сделано детерминистически (например, на основе номера хода).
Если вы используете математику с плавающей точкой, убедитесь, что вы используете ее в строгом режиме IEEE. Это может немного замедлить ход событий, но это небольшая цена за согласованность между клиентами. Также убедитесь, что во время обмена данными не происходит случайного округления (например, клиент передает округленное значение на сервер, но все еще использует необоснованное значение внутри). Как отмечено выше, наличие протокола для обнаружения и восстановления после десинхронизации также является хорошей идеей, на всякий случай.
источник
Вы должны сделать свою игровую логику полностью независимой от реального времени и по сути сделать ее пошаговой. Таким образом, вы точно знаете, в какой момент происходит «изменение энергии плиток». В вашем случае каждый ход составляет всего лишь 1/50 секунды.
Таким образом, вам нужно беспокоиться только о входах игроков, все остальное управляется игровой логикой и полностью одинаково для всех клиентов. Даже если игра на мгновение останавливается из-за задержки в сети или из-за слишком сложных вычислений, события все равно происходят синхронно для всех.
источник
Прежде всего, вы должны понимать, что PC float / double math НЕ является детерминированным, если только вы не укажете строго использовать IEEE-754 для своих вычислений (будет медленным)
Тогда вот как я бы это реализовал: клиент подключается к серверу и синхронизирует время (позаботьтесь о задержке пинга!) (Для длительного игрового процесса может потребоваться повторная синхронизация метки времени / поворота)
теперь каждый раз, когда клиент выполняет какое-либо действие, он включает метку времени / ход и может до сервера отклонить неверную метку времени / ход. Затем сервер отправляет обратно действие клиентам, и каждый раз, когда ход «закрывается» (он же сервер не примет такой старый поворот / отметку времени), сервер отправляет и завершает действие клиентам.
У клиентов будет 2 «мира»: один синхронизируется с конечным ходом, другой рассчитывается, начиная с конечного хода, суммируя поступившее в очередь действие до текущей очереди клиента / метки времени.
Поскольку сервер примет немного старое действие, клиент может добавить свое собственное действие непосредственно в очередь, поэтому время прохождения через сеть будет скрыто, по крайней мере, для вашего собственного действия.
последнее - поставить в очередь больше действий, чтобы вы могли заполнить пакет MTU, что приведет к меньшим издержкам протоколла; хорошая идея сделать это на сервере, поэтому каждое событие конечного поворота содержит действие в очереди.
Я использую этот алгоритм в стреляющей игре в режиме реального времени, и работает нормально (с клиентом и без него, выполняющим собственные действия, но с пингом сервера, равным 20/50 мс), также каждый X-сервер конечной очереди посылает специальное «все» «Карта клиента», для исправления смещенных значений.
источник