Статья была написана на основе инструкции от разработчиков на http://developer.valvesoftware.com на английском и на собственных наблюдениях. Оригинальная инструкция морально устарела, потому добавлены актуальная инфа, важные примечания и более подробное руководство. Акцент сделан на Half-Life 2 Deathmatch, поэтому все рекомендуемые значения указаны для нее, но сетевой код один во всех играх на том же двигле и различаются только требования и величины. Итак, многабукафф...
Сетевой код в Source Сетевые игры на движке Source Engine используют сетевую технологию клиент-сервер. Обычно сервер представляет собой выделенный хост, который обрабатывает игру: симуляцию игрового мира, правила игры и входящие данные о действиях игроков. Клиентом при этом является запущенная игра на компьютере игрока, подключенная к данному игровому серверу. Клиент и сервер связываются посредством обмена небольшими пакетами данных на определенной частоте (по умолчанию это обычно 20 или 30 штук в секунду). Клиент получает информацию о текущей обстановке игрового мира и воспроизводит изображение и звук согласно этой информации. Клиент также считывает информацию со своих устройств ввода (как мышь, клавиатура и микрофон) и отправляет считанное серверу для обработки. В оличие от пиринговых сетей, клиенты не могут непосредственно связываться один с другим, а только получают информацию друг о друге с сервера. Сетевая игра кардинально отличается от одиночной, потому что перед ней встает множество проблем, вызванных несовершенством сетей и игровым процессом, основанном на обмене пакетами данных. Пропускные способности линий ограничены искусственно (программное обеспечение клиента и тарифы провайдера) и физически (возможностями аппаратного обеспечения узлов и клиента), из-за чего сервер не способен всегда и своевременно снабдить клиента информацией о любых изменениях в игровой обстановке. Поэтому сервер непрерывно делает снепшоты (снимки) изменений игровой обстановки с определенной частотой и сразу отправляет их на клиент. Биту информации требуется некоторое время, чтобы добраться до клиента (т.н. время PING). Это означает, что клиент всегда отстает от сервера во времени. Кроме того, данные, которые клиент отправляет на сервер, тоже задерживаются, поэтому сервер обрабатывает запоздалые сообщения клиента. Вдобавок, у всех клиентов разные задержки, которые еще и изменяются в зависимости от коммуникаций и от фреймрейта клиента. Все эти несоответствия во времени вызывают логические нестыковки, которые усугубляются при повышенных задержках линий. В быстрых и динамичных экшн-играх даже разница в несколько миллисекунд может серьезно повлиять на игровой процесс, сделать попадания из оружия и взаимодействие с игровым миром крайне проблематичными. Кроме задержек и невысокой пропускной способности случаются такие проблемы, как повреждение пакетов данных на пути к клиенту или обратно, которая выливается в потерю этих пакетов (packet loss). Потеря может вызываться такими явлениями, как джиттер из-за плохого соединения, который можно проверить здесь (в последнем случае потеря неизбежна).
Чтобы сгладить последствия всех этих проблем, движок Source снабжен несколькими алгоритмами, которые включают в себя сжатие данных, интерполяцию, предсказание, компенсацию лагов (задержек). Эти алгоритмы тесно взаимосвязаны, потому изменения в работе одного могут затронуть действие другого.
Основы работы сетевого кода Сервер просчитывает игровой процесс пошагово, тактами ("ticks"). По умолчанию в большинстве игр Valve обрабатывается 66 тактов в секунду, то есть 66-ти равен tickrate (буквально "тактовая частота", не путать с фреймрейтом сервера!). На каждом такте сервер обрабатывает входящие пакеты с действиями клиентов, делает шаг симуляции физики, проверяет правила игры и полностью обновляет игровую обстановку. В конце расчета такта сервер, если этого в данный момент требует частота cl_updaterate, делает снепшот игровой обстановки для клиента. Более высокий тикрейт делает отображение игровой обстановки более точным, но и требует больше мощности процессора от сервера и клиентов. Администратор сервера может изменить стандартное значение тикрейта, используя в параметрах запуска сервера команду -tickrate xx, но это делать не рекомендуется из-за возможной неправильной работы сервера.
Примечание: Команда -tickrate по стандарту не действует для серверов CSS, HL2DM, TF2, L4D и L4D2, потому что изменение тикрейта вызывает неправильный расчет игрового процесса, искажения времени (в CSS плагинами принудительно ставят тик 100). Стандартный тикрейт равен 30 для L4D и L4D2, 66 для HL2DM, CSS и TF2.
Клиенты часто имеют небольшую пропускную способность линии, в худшем случае это телефонное соединение со скоростью 4-7 килобайт в секунду. Если сервер попытается отправлять им обновления на более высокой скорости, неизбежны потери пакетов. Поэтому клиент должен сообщать серверу максимальную доступную для игры скорость линии путем установки консольной команды rate (в бАйтах в секунду). Это самая важная переменная, для наилучшей оптимизации игра она должна быть установлена правильно (т.е. как можно выше, но чуть ниже скорости линии). Конфиг сервера может установить рамки значения rate своим клиентам с помощью команд sv_minrate и sv_maxrate (в HL2DM следует ставить sv_maxrate не ниже ~90000 или не ставить вовсе). Это ограничение временное и действует только пока клиент подключен к данному серверу. Клиент сообщает серверу требуемую частоту отправки пакетов параметром cl_updaterate (20 по умолчанию). Чем выше это значение, тем большая скорость канала потребуется для успешной передачи всех пакетов. Конфиг сервера может установить рамки клиентского cl_updaterate параметрами sv_minupdaterate и sv_maxupdaterate (sv_maxrate не должен быть ниже 1 + тикрейт!). Этот лимит тоже временный. Клиент генерирует пользовательские команды, считывая устройства ввода и консоль игры. Он отправляет их не по мере ввода, а в пакетах. Частота генерации и отправки пакетов задается на клиенте командой cl_cmdrate, но не будет выше 1 + тикрейт сервера. Повышение этого значения уменьшит задержку и сделает игру более отзывчивой, но потребует больше скорости исходящего канала и соответствующий фреймрейт на клиенте. Данные игры используют дельта-сжатие, чтобы снизить нагрузку на линию. Это значит, что сервер не отправляет клиенту снепшот всей игровой обстановки каждый раз, а только изменения в ней по сравнению с предыдущим. Каждый пакет данных имеет порядковый номер, чтобы сервер и клиент легко следили за потоком. Обычно полные снепшоты со всеми данными отправляются только в начале игры или если нормальная связь с клиентом теряется (при получении полного апдейта из-за его веса клиент может терпеть сильные лаги, зависимо от параметра rate). Клиент может запросить полный апдейт командой cl_fullupdate (чит). Отзывчивость, время между действием в игре и видимым ответом на него игрового мира зависит от многих факторов: загруженность процессора сервера и клиента, тикрейта, скорости потока данных и частоты cl_updaterate, но больше всего от - времени прохождения пакета. Время между посыланием клиентом серверу команды и получением ответа на нее называется задержкой или временем PING. Низкие задержки дают большое преимущество в онлайн-игре. Такие техники, как компенсация лагов и предсказание, призваны свести это преимущество к минимуму и обеспечить баланс между игроками с разным соединением. Регулировка настроек сетевого кода может помочь сделать игру лучше при достаточном запасе мощности.
Интерполяция обьектов По умолчанию клиент получает с сервера 20 обновлений в секунду. Если бы игра отображала мир только по этим снепшотам, то все движения происходили бы рывками, а при потерях пакетов происходили заметные провалы. Чтобы предотвратить это, используется техника, которая состоит в том, что клиент дожидается отправки сервером нескольких обновлений подряд с учетом cl_updaterate, а затем интерполирует (сглаживает) движения обьектов между полученными снепшотами. При частоте обновлений 20 снепшотов в секунду новый снеп отправляется каждые 50 миллисекунд. Если клиент ждет 50 миллисекунд, то может отображать сглаженные движения между последним полученным пакетом и предыдущим. В Source по умолчанию эта задержка ("lerp") составляет 100 мс (что равно cl_interp 0.1). То есть при апдейте в 20 пакетов задержка охватит 3 из них, а в случае потери одного останутся еще 2 пакета – столько необходимо для нормальной интерполяции.
Последний снепшот был получен, когда на клиенте был такт 344 или 10,30 секунд. Игра на клиенте продолжается на основе этого снепшота и фреймрейта. Если отрисовывается очередной кадр изображения, время отрисовки на клиенте составляет уже 10,32 сек минус задержка интерполяции 0,1 сек. В нашем случае это было бы 10.22 и все предметы интерполировались бы в нетронутой паре пакетов между снепшотами 340 и 342. При том, что у нас стоит задержка 100 мс, интерполяция сработала бы даже в случае потери снепшота 342. Могли бы просто использоваться 340 и 344. Но в случае потери 2х или больше пакетов подряд интерполяция бы не сработала, потому клиент применил бы экстраполяцию, используя снепшоты, полученные за последние 0,25 сек (параметры cl_extrapolate 1 и cl_extrapolate_amount 0.25 нельзя изменить при sv_cheats 0).
Примечание: В движках до Source 2009 можно было включить отображение хитбоксов (физических моделей) предметов командой sv_showhitboxes 1. При этом хитбоксы опережали изображения на время lerp, что не было плохим признаком.
Подсказка: В более новых версиях движка есть переменная cl_interp_ratio, которая при cl_interp 0 автоматически задает нужное время lerp. Она указывает количество интервалов между снепшотами с сервера (1 - минимально возможная интерполяция между двумя пакетами), и устанавливает задержку lerp с учетом sv_minupdaterate и cl_updaterate.
Предсказание действий Предположим, игрок с пингом 150 миллисекунд начинает двигаться вперед. Инфромация о нажатии клавиши +forward отправляется на сервер. Затем сервер просчитывает действие и в следующем апдейте отправляет изменение всем клиентам. При этом игрок увидит свое действие через 150 мс. Чтобы избежать такой крайне неудобной работы игры, все действия игрока условно просчитываются и отображаются на клиенте мгновенно. Функция включается/выключается командой cl_predict 1/0. На сервере sv_client_predict 1/0 задает принудительное значение для клиента, а sv_client_predict -1 снимает ограничения (опять же, временно). Через 150 миллисекунд клиент получает снепшот с сервера с выполненными клиентом действиями. Клиент проверяет сверяет свое предсказание со снепшотом. Если соответствия нет, значит произошла ошибка предсказания из-за недостатка информации на клиенте или по другим причинам. Поскольку только сервер принимает решения, клиент исправляет несоответствие, подстраивая свою обстановку под сервер (меняет позицию игрока). При cl_showerror 1 на клиенте будут показываться ошибки предсказания. Исправление ошибок бывает очень ощутимым по "телепортации" и дерганию во время движения. Включение плавного исправлении ошибок задается cl_smooth 1/0. Сама степень плавности задается cl_smoothtime в секундах (на самом деле чем больше плавность, тем труднее управление. Дефолтное значение в 0.1 сек вызывает в HL2DM периодическое "прилипание" персонажа к полу и стенам при распрыге. Чтобы более наглядно увидеть суть, поставьте cl_smoothtime 2.0 и воспользуйтесь лифтом и телепортом на dm_biohazard). Предсказание движений предметов адекватно работает только если клиент знает ту же информацию, что и сервер. Обычно так не происходит, потому что сервер узнает раньше и в б0льших масштабах. Клиенты получают только часть информации о происходящем, достаточную для отрисовки игровой обстановки. Так что предсказание работает только для вашего персонажа и его оружия. Большее не представляется возможным.
Компенсация задержек Представьте, что игрок стреляет в 10,5 по клиентскому времени игры. Пакет с информацией о выстреле отправляется на сервер. Пока пакет доходит, игра на сервере продолжается, а ко времени получения пакета цель могла переместиться. Пакет доходит ко времени 10,6 сервер уже не засчитал бы попадание даже если игрок выстрелил точно в цель. Это исправляется компенсацией задержек на сервере. Компенсация задержек работает путем сохранения на сервере всех позиций всех обьектов за последнюю секунду. Если получена команда ввода от игрока, сервер расчитывает: Время ввода команды = время сервера - время прохождения пакета - время интерполяции клиента. Сервер вспоминает позиции всех игроков (и только игроков) в полученное время и засчитывает попадание или промах.
Примечание: Поскольку всегда учитывается время lerp, выключенная интерполяция приводит к нежелательным результатам (с учетом формулы расчета выше данная ремарка наводит на мысль о том, что lerp для учета берется не с клиента, а с потолка).
На локальном сервере, встроенном в игру, можно включить sv_showimpacts 1, чтобы увидеть разницу между клиентской и расчетной физической моделью:
Скриншот был сделан с искуственным пингом в 200 мс (net_fakelag 200) сразу после засчитанного попадания. Красные хитбоксы показывают позицию цели на клиенте 100 мс назад. С тех пор цель переместилась влево, пока информация о выстреле доходила до сервера. Когда информация дошла, сервер условно возвращает информацию о положении цели в расчитанное время клиента (синие хитбоксы). Затем сервер подтверждает попадание, от чего на клиенте видно кровь.
Клиентские и серверные хитбоксы могут не совпасть из-за недочетов в расчете времени. Даже несоответствине в несколько миллисекунд может вылиться в погрешность в десяток сантиметров для быстрого движения (про распрыжки в HL2DM мы вообще молчим). Хитрег (от англ. hit registration - регистрация попаданий) в мультиплеере не является точным до пикселей из-за тикрейта и скорости обьектов. При повышении тикрейта он улучшается, но при этом повышаются системные требования. Встает вопрос, почему все так усложнено? Ведь расчет попаданий мог бы происходить на клиенте с точностью до пикселя, а попадание бы просто отправлялась на сервер. Это потому, что мы не можем позволить клиенту принимать такие важные решения. В таком случае даже если клиент чист под защитой VAC, пакеты могли бы быть модифицированы на машине-посреднике между клиентом и сервером (своеобразном читерском прокси-сервере).
Пинг может меняться много раз в секунду, потому расчет времени работает паршиво. Вдобавок колебаться может (и колеблется) тикрейт, да и все остальное. При современных линиях совсем не было бы проблемой сделать регистрацию попаданий путем отправки с клиента всех физических моделей по-своему вместе со своим положением и углом взгляда на сервер при каждом выстреле, а уже там попадание засчитывалось бы как надо. Или можно было бы отправлять с клиента точное клиентское время (дробное, а не по номеру пакета т.к. есть еще интерполяция).
Неизбежные задержки на линии и компенсация задержек вызывают парадоксы и "чудеса" на подобие вашей гибели от попадания другого игрока тогда, когда уже укрылись от него.
Net Graph В Source есть инструменты для диагностики игры по сети. Главный из них - net_graph. Как настроить эту табличку для удобства постоянного использования, прочтите здесь.
Диаграмма сверху показывает обьем (высота) и состав (цвета) информации в пакетах. Провал в диаграмме - пакет потерян или пришел невовремя. Для проверки соединения и настроек важны такие значения: Первая строка: fps: фреймрейт на клиенте; ping: средняя задержка пакетов; текущий cl_updaterate. Вторая строка: размер входящего пакета; средняя скорость входящих пакетов (килобайт/сек) за последнюю секунду; задержка интерполяции; частота входящих пакетов в секунду. Треться строка - то же самое для исходящих пакетов. Четвертая строка: loss: количество потерянных (битых) пакетов за секунду; choke: (предположительно) пакеты которые сервер не смог отправить (лаги); текущий cl_cmdrate. Пятая строка: sv: (предположительно) фреймрейт сервера.
Порядок оптимизации настроек Все вышеописанные стороны работы сетевого кода можно и нужно настраивать, ибо дефолтные значения переменных сильно усреднены и к тому же подбирались во времена, когда до интернета нужно было дозваниваться, а фреймрейт чуть выше частоты обновления монитора был роскошью. Все ваши окончательные настройки должны попадать в ваш autoexec.cfg или отдельный файл .cfg и, желательно, забиндены все на одну кнопку.
1) rate Вам понадобится сайт http://speedtest.net/, чтобы узнать реальную скорость вашего соединения с миром в бИтах (учитывайте минимальную скорость скачки в нормальное время). Добавьте в конфиги rate с полученной скоростью в бАйтах, например: 0.128 Mb/s ~ 128 kb/s: rate 16000 0.256 Mb/s ~ 256 kb/s: rate 32000 0.384 Mb/s ~ 384 kb/s: rate 48000 0.512 Mb/s ~ 512 kb/s: rate 64000 0.768 Mb/s ~ 768 kb/s: rate 96000 1.000 Mb/s ~ 1024 kb/s: rate 128000 HL2DM в самых тяжелых случаях требует меньше мегабита, потому ставить rate выше 128k смысла не имеет, но Team Fortress 2 или другие игры могут потребовать больше.
2) cl_cmdrate Для HL2DM исходящего канала в 128 килобит должно хватать с запасом для максимального cmdrate в 67 (101 для игр, где иногда используют тик 100) пакетов в секунду, потому смело ставьте максимум в консоль и конфиги, если только ваш FPS в игре не падает сильно ниже этого значения (иначе оптимум определяется по ощущениям в игре и минимальному фреймрейту).
3) cl_updaterate На серверах могут быть разные конфиги, которые иногда достаточно неадекватны. Например, OMOS разрешает ставить rate до 30000, что вызывает при нормальном cl_updaterate чоук и лаги. Бывают серверы, которые нетолерантны к игрокам с плохим соединением или стандартными настройками - к лоурейтерам. Чтобы установить это и последующие значения, вам необходим сервер с определенными качествами (в спойлере).
-Его конфиг должен позволить вам поставить любые нужные вам значения (чтобы проверить, подключитесь и введите в консоль нужный лимит sv_min/max... или sv_client_min/max...): rate 30000-60000, updaterate 30-67(101), cmdrate 30-67(101), interp_ratio 1-5; 0 - лимита нет. -Он не должен тормозить. Во время напряженной игры sv: по net_graph не падает сильно ниже 250-300 -Тикрейт его максимальный, который используют в этой игре (66 для HL2DM и 100 для CSS, например). Для HL2DM можно использовать один из серверов Clans-United для матчей.
Поставьте cl_updaterate в консоль на максимум, зайдите на такой сервер с как можно большим количеством других игроков (то есть всего не меньше 8 рыл для HL2DM), чтобы дать нагрузку на линию. При этом желательно поставить карту dm_killbox_kbh, встать в самом верху карты и охватить взглядом как можно больше игроков. Наблюдайте за net_graph: если в нормальных условиях loss > 0, то у вас либо проблемы на линии, которые вы скорее всего не можете исправить (джиттер или др.), либо rate выше, чем скорость линии. А если choke > 0, то ваше значение rate слишком маленькое для такой нагрузки. Чтобы убрать choke, в процессе интенсивной игры постепенно снижайте cl_updaterate на единицу до тех пор, пока choke не перестанет появляться (удостоверьтесь, что sv_minupdaterate позволяет снижать еще). Оптимальное значение занесите в конфиги.
4) интерполяция Добавьте в конфиги и в консоль параметр cl_interp 0, то есть задержка на интерполяцию будет всегда минимальна. Исходя из лосса, поставьте в конфиг нужный cl_interp_ratio. Так, если у вас идеальная линия и лосса почти никогда нет, ставьте ратио на 1, то есть у вас клиент будет ждать только один промежуток между пакетами и интерполировать из двух пакетов подряд, а если посреди игры вдруг появится лосс, например, 1, то интерполяция будет давать 1 косяк в секунду. При небольшом лоссе до 5 вам понадобится уже cl_interp_ratio 2, то есть интерполяция из 3 пакетов: 1 выпал - 2 осталось.
5) предсказание Добавьте в конфиги и консоль cl_smooth 1 (значение по умолчанию) и cl_smoothtime 0.01 (минимум). Можете поставить cl_smooth 0, но не советую этого делать т. к. дергание в игре будет слишком резким. cl_smoothtime ставить выше - дергает меньше, но при этом труднее контролировать свои движения.
В: А какие рейты для меня будут оптимальными? О: Читайте статью (еще раз).
В: У меня на net_graph показывает choke или loss, как исправить? О: Одна из причин чоука: rate < скорость апдейтов. Одна из причин лосса: скорость линии < скорость апдейтов < rate (подробности в статье). Если скорость апдейтов < rate < скорость линии, чоук и лосс минимальны. Подробности есть в статье.
В: У меня все время оранжевый lerp, это плохо? О: Достоверной информации по расшифровке цветов лерпа пока нет. По наблюдениям, если у вас на net_graph все время оранжевый lerp, то это не является признаком реальной проблемы, просто на сервере может быть sv_minupdaterate выше апдейта в вашем конфиге, а соответствие lerp нужному времени игра меряет по конфигу (одна из причин). В других случаях проблем с попаданиями при оранжевом цвете не было.
В: У меня желтеет/краснеет lerp, это плохо? О: Если лерп мигает желтым - это мелкие лаги, в результате которых интерполяция работает с перебоями (нужно в консоль вбить cl_interp_ratio выше). Обычно так происходит, когда сервер тормозит и при этом снижается апдейт. Красный лерп обозначает (предположительно) лаг уже такой, что сбой дает и экстраполяция.
В: Почему можно без вреда ставить cl_updaterate и cl_cmdrate на 1 выше тикрейта сервера, если это позволяет его конфиг, и получать/отправлять на 1 пакет в секунду больше? О: Необъяснимо, но факт. Откуда берется этот пакет и что он из себя представляет (или где ошибка в подсчете этих пакетов) - мне неизвестно. Точно установил, что при cl_updaterate 1 обновление приходит четко раз в секунду, потому апдейт 67 должен давать 67 пакетов.
В: Почему из спеков всегда кажется, что у игрока аимбот: он попадает все время во врага, целясь в воздух? О: В спеках не работает компенсация задержек. Вы видите все так, как видит сервер. Поэтому поосторожнее с определением аимбота у игрока!
fck yeah ! и пусть буржуи нам завидуют. в свое время такая темка валялась толи на клансюнайтед толи на буржуйском юниве. все ограничилось справкой о 4-х клиенстких командах для рейтов и обсуждением у кого какие стоят. аффтор +100500 ,хотя ясен пень спизженно откуда нить ) но все равно классная статейка.