Как синхронизировать игроков в браузерной пошаговой игре?
Здравствуйте! Подскажите, пожалуйста, как можно решить проблему с синхронизацией в такой игре.
Вот что у меня уже готово и более менее работает.
1. Клиенты заходят в игру и после того, как игра началась запускаются следующие процедуры:
1.1. каждые 10 секунд отправляют запросы серверу: завершил ли ход соперник?
1.2. у игроков стартует в обратный отсчет таймер конца хода - 90 секунд;
1.3. начинают считаться игровые такты в каждом из клиентов игроков. Т. е. каждые 10 секунд значение переменной игрового такта увеличивается на 1.
2. Сервер каждые 10 секунд увеличивает у себя игровые такты на 1.
3. Если клиент игрок получает сообщение о том, что очередь хода перешла к нему, то клиент обновляет и показывает данные о ходе в следующем игровом такте.
Сейчас вроде бы все работает, но таймеры на клиентах игроков бывает уходят в минус.
Может быть попробовать отправлять данные чаще чем раз в 10 секунд?
Клиент написан на angular. Сервер на php. Думаю может быть не те технологии для разработки выбраны?
Сейчас вроде бы все работает, но таймеры на клиентах игроков бывает уходят в минус.
Что мешает останавливать таймер в 0? Если ноль то автоматически считать что оппонент сделал/пропустил ход, на всякий случай принудительно проверит что на сервере произошло(ходил ли игрок) и передать ход.
То есть когда время кончилось, то отправлять внеочередной запрос серверу? Но у игроков могут отличаться игровые такты.. Может быть сделать опрос чаще чем раз 10 секунд?
Programep, это абсолютно пофиг, пару секунд в пошаговой игре никак не влияет, а ориентироваться нужно на время сервера, так как никто не знает какое время на клиенте.
комет тоже не фонтан, но уже лучше чем интервал, сокеты как пример хорошего решения связи без задержки.
Понял. Спасибо! Попробую сначала отправлять запрос, когда таймер ушел в ноль сразу же, без ожидания следующей отправки.
Можете посоветовать хорошую статью по технологиям комет и сокетам (вы же про вебсокеты писали)?
Programep, у вас слишком разброс большой по задачам, вам нужно решать по 1 задаче за 1 раз, а вы пытаетесь сделать все и сразу. Сначала на интервалах запустите чтобы все работало. Потом настройте взаимодействие js-php, затем уже тонкая настройка. Учитесь разбивать задачи, иначе и решений не сможете искать, и работа будет хаотичная.
Сначала на интервалах запустите чтобы все работало. Потом настройте взаимодействие js-php, затем уже тонкая настройка
. То есть сначала чтобы у одного клиента все работало и переключало ход при таймере = 0?
Я вроде бы одну задачу поставил) Синхронизация клиентов игры с сервером.
До этого я много читал и на хабре и другие статье в интернете про то, что для реалтайм php плохо подходит, поэтому сейчас и думаю, что может быть на php это не решаемая задача.
1.1. каждые 10 секунд отправляют запросы серверу: завершил ли ход соперник?
где это хранится и как определяется? В базе? В мемкеше? Редис? Сессия?
2. Сервер каждые 10 секунд увеличивает у себя игровые такты на 1.
Зачем? Есть время начала, есть время завершения, в любой момент можно запросить текущий такт, вычислив разницу от старта до текущего момента, что у вас там сервер считает и зачем?
3. Если клиент игрок получает сообщение о том, что очередь хода перешла к нему, то клиент обновляет и показывает данные о ходе в следующем игровом такте.
не очень понятно как у вас сделано, просто когда на сервере состояние хода сменилось, при следующем запросе (который раз в 10 секунд) логика фронтенда должна поменяться на "вы ходите".
ThunderCat, 1.1. На клиенте есть массив в котором есть переменная где хранится информация о том его ли ход, и есть token(ключ-пароль этого клиента) по которому сервер ему отправляет ответы о текущем состоянии игры.
2. Сервер считает, чтобы определить нужно ли переключать ход или нет. Если вы предлагаете переключать ход клиентам, то я этот вариант пробовал, и в этом случае задержка будет как раз ~10 секунда, т.е. на время следующего запроса. Плюс в данном случае не будет контроля за тем вышел соперник или нет.
3. Сейчас сделано так, что такты игры на сервере "расчитываются" на 1 такт вперед клиентов. Например, если на сервере сейчас такт 23, то на клиентах такт 22. В клиентах хранится информация о следующем такте, т.е. что нужно сделать. Вот этот момент пока работает нестабильно.
Можно сделать наоборот. Не использовать таймер обратного отсчёта, но в момент начала хода рассчитать время его окончания и сравнивать по таймеру текущее время с рассчитанным. Как только рассчитанное время наступило спросить у сервера текущее состояние игры. Такой вариант позволит сгладить фризы таймера на клиенте.
Каждый клиент сам для себя вычисляет своё время. Т.е.берет то что на часах, прибавляет 90 секунд и получает своё локальное время окончания хода. Даже если клиент поменяет свои часы чтобы обмануть игру, то все равно решение о передаче хода принимает сервер, как только клиент свяжется с сервером все встанет на свои места.
Руслан, а как быть с задержкой на передачу данных? Сейчас в игре бывает ситуация, что сервер ход уже переключил, а на клиенте еще 20 секунд до конца хода.)
Так это же из-за фриза таймера на клиенте. Не будет фриза, не будет и таких задержек.
А чтобы не было фриза нужно брать абсолютное время конца хода, а не то которое обратным отсчетом по таймеру получается.
Хотя странно выходит, у вас каждые 10 сек уходит запрос на сервер, значит в худьшем случае таймер клиента должен отставать не более чем на 10 сек от таймера сервера.
Или запросы из-за фриза таймера клиента тоже уходят не каждые 10 сек, а сильно реже?
Руслан, никак не пойму, что вы имеете ввиду под фриз таймера.
Запросы от клиентов уходят каждые 10 секунд, на сервере проверка переключения тоже выполняется каждые 10 секунд. Пока у меня объяснение, что из-за выполнения проверки на сервере время сдвигается, на время выполнения функции проверки. Может быть php не подходит для решения данных задач?
Php не причем.
Вряд ли на сервере так долго проверка происходит, хотя это тоже нужно проверить.
Под фризом таймера я подразумеваю изменение времени срабатывания callback таймера в скрипте браузера. Браузер не даёт гарантии, что интервал срабатывания указанный например в параметре процедуры settimeout() будет строго соблюдаться.
Вот хорошая статья на тему: https://habrahabr.ru/company/ruvds/blog/340508/
Руслан, теперь понял о чем вы говорите. Но я использую setInterval(), он как я понял не зависит от времени выполнения callback исходя из этой статьи Таймеры javascript.
setInterval() и settimeout() аналогичны по смыслу, они в очередь событий помещают процедуру callback, только settimeout выполняет это один раз, а setInterval пока не придет clearInterval.
Но если в процедуру callback поместить например sleep(10000), то раньше чем через 10 сек callback не сработает.
Руслан, что-то я немного запутался. То есть задержка может быть и на стороне браузера, например, если открыта какая-та вкладка с сайтом, которая нагружает ПК пользователя под >90%, то setInterval() или setTimeout() отработают неверно с точки зрения работы приложения, верно? Тогда в данной ситуации я думаю только вариант отображения пользователю сообщения, что нужно синхронизировать клиент игры в его браузере..
Именно, скорее всего там где счётчик сильно отстаёт от сервера это задержка на стороне клиента и setInterval() или setTimeout() отрабатывают не по плану.
Писать клиенту о том что нужно синхронизировать игру не очень юзерфрендли.
Сгладить проблему фризов можно, если не делать обратный отсчёт по таймеру, а использовать таймер для вычисления оставшегося времени до конца хода.
Т.е. в момент получения хода нужно вычислить время его окончания и далее по таймеру находить разницу между временем окончания хода и текущим временем.
В случае залипания таймера отсчёт будет идти не равномерно, но зато время просрочки клиентского таймера относительно серверного если и будет, то будет не большим.
Время можно получать Date.now() сразу в миллисекундах.
Т.е. в начале хода
DateEnd = Date.now() + 1000*60
В таймере:
CurrDate = Date.now()
Delta = (DateEnd-CurrDate)/60
Как только Delta <= 0 происходит смена хода.
Значение Delta можно также показывать клиенту как значение таймера, по сути это время до конца хода от текущего момента.
Руслан, спасибо за подробный ответ! Попробую создать версию клиента по этому алгоритму и сравнить. У меня еще на основе ваших предложений появилась идея сделать так, что на клиенте секунда будет равна, например, 1200 мс, т.е. немного длиннее, чтобы компенсировать задержки на отправку и получение ответа от сервера. Вроде бы что-то подобное используется в игре Starcraft.