Назад в библиотеку

Динамичный Мультиплеер

Автор: Габриэль Гамбетта

Источник (англ): Fast-Paced Multiplayer (Part I): Introduction http://www.gabrielgambetta.com/fpm1.html

Источник (англ): Fast-Paced Multiplayer (Part II): Client-Side Prediction and Server Reconciliation http://www.gabrielgambetta.com/fpm2.html

Источник (англ): Fast-Paced Multiplayer (Part III): Entity Interpolation http://www.gabrielgambetta.com/fpm3.html

Источник (англ): Fast-Paced Multiplayer (Part IV): Headshot! (AKA Lag Compensation) http://www.gabrielgambetta.com/fpm.html

Динамичный Мультиплеер (Часть I): Введение

Введение

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

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

Проблема читеров

Все начинается с читов.

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

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

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

Ведущий сервер и ведомый клиент

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

Конечно, ваш сервер может быть уязвим, но это выходит за рамки этой серии статей. Хотя использование ведущего сервера не предотвращает широкий спектр атак. Например, вы не доверяете данным клиента, о состоянии здоровья игрока; во взломанном клиенте локальная копия этого значения может быть изменена и говорить игроку о том, что он имеет 10000% здоровья, но сервер знает, что у него есть только 10% когда игрок будет атакован в игре, он умрет, независимо от того, что думает взломанный клиент.

Так же вы не доверяете данным клиента о позиции игрока в мире. Если взломанный клиент сообщает серверу " Я в (10,10) " и через секунду " Я в (20,10) ", то возможно он прошел через стену или двигался быстрее, чем другие игроки. Вместо этого сервер знает, что игрок находится в точке (10,10), клиент сообщает серверу " Я хочу, переместиться на один квадрат вправо", сервер обновляет свое внутреннее состояние позиции игрока на (11,10), а затем отвечает игроку " Вы в (11, 10) ":

Простое взаимодействие клиент-сервер
Простое взаимодействие клиент-сервер

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

Работа с сетями

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

Давайте поговорим о физике. Предположим, что вы находитесь в Сан-Франциско и подключены к серверу в Нью-Йорке. Это примерно 4000 км, или 2500 миль (это примерно расстояние между Лиссабоном и Москвой). Ничто не может двигаться быстрее, чем свет, даже байты в Интернете (которые на нижнем уровне представляют собой импульсы света, электроны в кабеле, или электромагнитные волны). Свет проходит приблизительно 300000 километров в секунду, поэтому путешествие в 4000 км занимает 13 мс.

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

Давайте предположим, что передача данных от клиента к серверу занимает 50 мс. Это близко к лучшему случаю, но что произойдет, если вы находитесь в Нью-Йорке и подключены к серверу в Токио? Что делать, если по какой-то причине произошла перегрузка сети? Могут случаться задержки в 100, 200, даже 500 мс и это не предел.

Вернемся к нашему примеру, ваш клиент посылает некоторый ввод на сервер ("Я нажал на стрелку вправо"). Сервер получает его на 50 мс позже. Допустим, сервер обрабатывает запрос и немедленно посылает обратно обновленное состояние. Ваш клиент получает новое состояние игры ("Ты сейчас на (1, 0)") на 50 мс позже.

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

Выводы

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

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

Динамичный Мультиплеер (Часть II): Прогнозирование на стороне клиента и согласование сервера

Введение

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

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

Влияние сетевых задержек
Влияние сетевых задержек

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

Прогнозирование на стороне клиента

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

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

Давайте предположим, что у нас есть задержка в 100 мс, и анимация персонажа, движущегося от одного квадрата к другому занимает 100 мс. Используя данную примитивную реализацию, все действие займет 200 мс:

Сетевая задержка + анимация
Сетевая задержка + анимация

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

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

Анимация игры в то время как сервер подтверждает действие
Анимация игры в то время как сервер подтверждает действие

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

Проблемы синхронизации

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

Используя ранее рассмотренные методы, мы бы увидели:

Прогнозируемое состояние и его несоответствие состоянию ведущего сервера
Прогнозируемое состояние и его несоответствие состоянию ведущего сервера

При Т = 250 мс, когда приходит новое игровое состояние, мы столкнулись с интересной проблемой: прогнозируемое состояние на клиенте х = 12 , но сервер говорит , что новое состояние игры х = 11 . Поскольку сервер является ведущим, то клиент должен переместить персонажа обратно в х = 11 . Но когда при Т = 350 прибывает новое состояние с сервера в котором говорится, что позиция х = 12 , то персонаж снова перемещается вперед.

С точки зрения игрока, он нажал клавишу со стрелкой вправо два раза; персонаж перемещается на два поля вправо, после чего стоял там в течение 50 мс, а затем прыгнул на один квадрат влево, где простоял 100 мс, и снова прыгнул на одну клетку вправо. Это, конечно, неприемлемо.

Согласование с сервером

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

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

Предсказание на стороне клиента + согласование с сервером
Предсказание на стороне клиента + согласование с сервером

Теперь, при Т = 250, сервер говорит "основываясь на том, что я видел до вашего запроса #1, ваша позиция х = 11". Поскольку сервер является ведущим, он устанавливает позицию персонажа в х = 11. Теперь давайте предположим, что клиент сохраняет копию запросов, которые он отправляет на сервер. Основываясь на новом игровом состоянии, он знает, что сервер уже обработал запрос # 1, поэтому он может удалить эту копию. Но он также знает, что сервер еще должен отправить обратно результат обработки запроса #2. Таким образом, снова применяя предсказание на клиенте, он может вычислить "текущее" состояние игры на основе последнего игрового состояния, полученного с сервера, плюс ввод, который еще не был обработан.

Так, при Т=250 , клиент получает "х = 11, последний обработанный запрос = #1". Он удаляет свои копии отправленного ввода до #1, но оставляет копию #2, которая еще не была обработана сервером. Он обновляет свое внутреннее состояние игры на основе того, что послал сервер (х = 11), а затем применяет все вводы еще не обработанные сервером, хранящиеся локально, в данном случае, ввод #2, "двигаться вправо". Конечным результатом является х = 12 , что является правильным.

Продолжая наш пример, при Т = 350 с сервера приходит новое игровое состояние; на этот раз он говорит: "х = 12, последний обработанный запрос = #2 ". Теперь, клиент удаляет все свои копии ввода до #2, и обновляет состояние с х = 12 . Больше нет не обработанных вводов, поэтому обработка заканчивается с правильным результатом.

На остаток

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

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

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

Выводы

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

Динамичный Мультиплеер (Часть III): Интерполяция сущностей

Введение

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

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

В этой статье мы исследуем последствия наличия нескольких игроков, подключенных к одному серверу.

Временной шаг сервера

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

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

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

Таким образом, обновление игрового мира не зависит от наличия и количества вводов клиента, на предсказуемой скорости.

Работа с низкочастотными обновлениями

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

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

Как Клиент2 видит Клиента1
Как Клиент2 видит Клиента1

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

Навигационное счисление

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

Почему "примерно"? В течении секунды автомобиль мог немного ускориться или замедлиться, или немного повернуться вправо или влево — здесь ключевое слово "немного". Маневренность автомобиля такова, что при высоких скоростях его положение в любой момент времени, в значительной мере зависит от его предыдущей позиции, скорости и направления, вне зависимости от того, что на самом деле делает игрок. Другими словами, гоночный автомобиль не может сделать поворот на 180 градусов мгновенно.

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

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

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

Интерполяция сущностей

Есть ситуации, когда навигационное счисление вообще не может быть применено — в частности, все сценарии, в которых направление и скорость игрока может измениться мгновенно. Например, в 3D-шутере, игроки обычно бегут, останавливаются, и поворачивают за угол на очень высоких скоростях, что делает такое счисление по существу бесполезным, так как позиции и скорости больше не могут быть предсказаны на основе предыдущих данных.

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

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

Допустим, вы получили данные о местоположении при Т = 1000 . Вы уже получили данные при Т = 900, так что вы знаете, где игрок был на Т = 900 и Т = 1000. Таким образом, с Т = 1000 до Т = 1100 , вы показываете, что другой игрок делал от T = 900 до Т = 1000 . Вы всегда показываете пользователю настоящие данные о движении, за исключением того, вы показываете ему их на 100 мс позже.

Клиент2 отображает Клиента1 'в прошлом' интерполируя последние известные позиции
Клиент2 отображает Клиента1 "в прошлом" интерполируя последние известные позиции

Данные, которые вы используете для интерполяции от T = 900 до Т = 1000 зависят от игры. Интерполяция обычно работает достаточно хорошо. Если это не так, то вы можете сделать так, чтобы сервер каждое обновление отправлял более подробные данные, например, последовательность прямых отрезков следующих за игроком, или позиции разделенные на промежутки по 10 мс, это будет выглядят лучше при интерполяции (вам не нужно отправлять в 10 раз больше данных, так как вы отправляете дельты для мелких движений, это можно в значительной степени оптимизировать для конкретного случая).

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

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

Выводы

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

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

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

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

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

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

Динамичный Мультиплеер (Часть IV): Компенсация задержек

Введение

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

Краткий повтор

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

  • Сервер получает входные данные от всех клиентов, с временными метками
  • Сервер обрабатывает вводы и обновляет состояние игрового мира
  • Сервер регулярно отправляет состояние игрового мира всем клиентам
  • Клиент посылает ввод и моделирует его влияние на локальном уровне
  • Клиент получает обновления игрового мира и
    • Синхронизирует предсказанное состояние с полученным от сервера
    • Интерполирует известные прошлые состояния для других объектов

С точки зрения игрока, это имеет два важных следствия:

  • Игрок видит себя в настоящем
  • Игрок видит другие объекты в прошлом

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

Компенсация задержек

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

Но вы промахнётесь.

Почему это происходит?

Из-за архитектуры клиент-сервер описанной ранее, вы целились туда, где голова противника была 100мс ранее, чем вы сделали выстрел, а не тогда когда вы стреляли!

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

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

Вот как это работает:

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

И все довольны!

Сервер счастлив, потому что он сервер. Он всегда счастлив.

Вы счастливы, потому что вы целились в голову своего противника, выстрелили, и получили то, что хотели!

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

Но что, если он был на открытой местности, а после спрятался за стеной и получил выстрел, на доли секунды позже, когда он уже думал, что в безопасности?

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

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

Вывод

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

Хотя статьи и были рассчитаны на разработчиков игр, но они нашли и другую группу заинтересованных читателей – геймеров! С точки зрения геймера, также интересно понять, почему некоторые вещи происходят так, как они происходят.