Работа с Prediction системой
Вступление
Система предсказания (prediction) движка Source спроектирована с целью уменьшить влияние задержек на динамику игры. Наиболее чувствительны к задержкам события, вызываемые нажатиями клавиш (во время чего команда отправляется на сервер и возвращается обратно), система предсказания Source имитирует эффект нажатия кнопки на клиенте немедленно. После этого, когда приходит ответ от сервера, клиент определяет, была ли корректной предполагаемая имитация.
- 99% времени, предположения клиента корректны. Поэтому, клиент может играть, не замечая задержек в передаче по сети.
- Если предположение клиента некорректно, клиент возвращается назад и повторяет имитацию всех команд, которые выполнялись с использованием ошибочных данных. В связи с этим, клиент ощущает небольшой рывок в его поле зрения, анимации оружия, и т.д. К счастью, если код написан корректно, этот случай не очень часто встречается.
Для более подробной информации можно прочитать Lag Compensation .
Подробности
Основные требования, которые требуется выполнить, чтобы энтити использовала систему предсказания:
- Одинаковые части кода энтити должны выполняться и на клиенте, и на сервере. Такой код относится к
общему коду
. - Все переменные энтити которые меняются на сервере и клиенте должны передаваться при помощи таблиц данных .
- Эти переменные должны также быть добавлены в список называемый
таблица предсказаний (prediction table)
.
Передача по сети через таблицы данных
Хорошим примером будет рассмотреть код оружия. Если у вас есть установленный исходный код, вы можете посмотреть файл src\game_shared\basecombatweapon_shared.cpp
. Там, вы можете увидеть блоки #ifdef CLIENT_DLL и #ifdef GAME_DLL. Они определяют, какой код компилируется в клиентскую DLL, а какой в серверную. Приведенный ниже (сокращенный) код соответствует шагу 2 описанной выше последовательности (помещает переменные измененные общим кодом в таблицу данных ).
BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon) #ifndef CLIENT_DLL SendPropInt( SENDINFO(m_iState ), 8, SPROP_UNSIGNED ), SendPropEHandle( SENDINFO(m_hOwner) ), #else RecvPropInt( RECVINFO(m_iState )), RecvPropEHandle( RECVINFO(m_hOwner ) ), #endif END_NETWORK_TABLE()
Создание таблицы предсказаний
Таблица предсказаний описывает данные в вашей энтити, которые должны быть одинаковыми на клиенте и сервере когда клиент спекулятивно имитирует команду. Это список переменных, которые передаются по сети и их типы. Если для значения допускается отклонению от серверного (как в случае с значением с плавающей запятой, которое передается с урезанным количеством бит), то вы можете указать, на сколько допустимо это отклонение. В коде приведенном ниже, это выполняется макросом DEFINE_PRED_FIELD_TOL
.
#ifdef CLIENT_DLL BEGIN_PREDICTION_DATA( CBaseCombatWeapon ) DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_hOwner, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ), END_PREDICTION_DATA() #endif
Консольные комманды
Если вы добавляете функциональность к оружию и забываете выполнить шаги описанные выше, работа энтити может сопровождаться рывками и странностями с анимацией. Вы можете использовать cl_predictionlist и cl_pdump , чтобы устранить эти проблемы.
Подавление сетевых данных
Большая часть оружия имеет общий код, одинаковый для клиента и для сервера. Это позволяет клиенту предсказывать события. Не требовательные к точности предсказания события, такие, как эффекты оружия, могут быть полностью выполняться клиентом, без отправки серверу данных эффекта для передачи всем клиентам. В этом случае подавление данных хорошая идея, которая даст меньшую загрузку сети.
Интерфейс IPredictionSystem
предназначен для этого.
Когда IPredictionSystem::SuppressHostEvents( pPlayer );
вызван, все сетевые данные к этому пользователю останавливаются. Отправка NULL повторно остановит подавление. Для примера:
if ( pPlayer->IsPredictingWeapons() ) IPredictionSystem::SuppressHostEvents( pPlayer ); pWeapon->CreateEffects(); IPredictionSystem::SuppressHostEvents( NULL );
Решение проблем
Предположим мы видим переменную, становящуюся время от времени красной в консоли cl_pdump
. Теперь, мы знаем что иногда клиент производит значения отличающиеся от тех, что на сервере. Обычно это можно отнести к следующим проблемам:
- Клиент не выполняет тот же код что выполняет сервер. Это может быть случай, если участок кода помещен в секцию ограниченную
#ifdef GAME_DLL
или#ifndef CLIENT_DLL
вокруг кода который влияет на переменную отмеченную красным цветом. - Другая переменная которая влияет на данное значение становящееся красным не передается через таблицу данных. В таком случае на клиенте, значение этой переменной всегда неверное (так как сервер никогда не передает его).
- Также возможно не было добавлено соответствующее отклонение при помощи макроса
DEFINE_PRED_FIELD_TOL
. Например, если передается значение с плавающей запятой в диапазоне от 0.0 до 255.0, и было задано только 4 бита точности, тогда требуется отклонение около 17.0, иначе система предсказания считает значение переменной неверным из-за того что значение сжимается до 4 бит перед отправкой клиенту.
Отслеживание проблем предсказаний обычно предмет рассмотрения различного кода который влияет на переменные которые отображаются красным, и проверки других переменных которые влияют на их значение. Вначале это может показаться утомительным, но проверив это на собственном опыте проблемы с системой предсказания быстро исчезают.