Prediction

From Valve Developer Community
< Zh
Jump to navigation Jump to search
Under construction.png
This page is actively undergoing a major edit.
As a courtesy, please do not edit this while this message is displayed.
If this page has not been edited for at least several hours to a few days, please remove this template. This message is intended to help reduce edit conflicts; please remove it between editing sessions to allow others to edit the page.

The person who added this notice will be listed in its edit history should you wish to contact them.

Info content.png
This page needs to be translated.
This page either contains information that is only partially or incorrectly translated, or there isn't a translation yet.
If this page cannot be translated for some reason, or is left untranslated for an extended period of time after this notice is posted, the page should be requested to be deleted.
Also, please make sure the article complies with the alternate languages guide.(en)
English (en)Русский (ru)中文 (zh)Translate (Translate)

预测(Prediction)是客户端在无需等待服务器确认的情况下,预先模拟本地玩家操作效果的技术。实体的预测状态会与接收到的服务器指令进行比对,直到检测到匹配或失配。

在绝大多数情况下,服务器的确认会使客户端的预测得以延续,整个过程流畅得仿佛没有延迟。若出现失配(在预测代码正确编写的情况下较为罕见),客户端将回溯并使用修正后的数据重新模拟所有指令。根据错误严重程度,可能导致玩家位置、状态甚至世界状态出现明显卡顿。

预测与延迟补偿(en)紧密相关,但独立于插值(en)。预测仅存在于客户端。

Warning.png警告:切勿抱有“这两个值永远不会不同步”的侥幸心理。数据包丢失的可能性始终存在!
Note.png注意:由于只有玩家实体(en)支持延迟补偿,攻击其他实体(尤其是NPC)时无法受益于预测——仍需根据延迟提前瞄准。若需解决此问题,可考虑为NPC启用延迟补偿(en)(在Left 4 Dead 2(en)中,prop_physics(en)可选择性启用延迟补偿)。

实现方法

要实现实体预测:

  1. 实体必须能被本地玩家操控,否则预测无意义
  2. 需预测的功能必须在客户端和服务器端同时存在且完全一致,可通过共享代码(en)实现
  3. 实体需调用SetPredictionEligible(true)(建议在构造函数中执行)
  4. 客户端需实现bool ShouldPredict()函数,用于检测本地玩家是否持有该实体等条件
    Note.png注意:武器还需在客户端实现bool IsPredicted(),始终返回true
  5. 所有需同步的变量必须进行网络传输(en)并注册到实体的预测表中

预测表

所有受玩家输入影响的客户端变量必须加入预测表,可选择三种注册方式:

FTYPEDESC_INSENDTABLE
网络传输变量。客户端预测值会与服务器值比对,若不同步将产生预测错误
FTYPEDESC_NOERRORCHECK
允许不同步的预测值(无论是否网络传输),不会触发预测错误
FTYPEDESC_PRIVATE
既不预测也不传输,但仍注册以便通过cl_pdump查看
Note.png注意:此类变量在预测系统回溯时不会被保存/恢复。若预测函数修改其值,客户端在测试新指令时会持续累加该值

通过DEFINE_PRED_FIELD()DEFINE_PRED_FIELD_TOL()宏实现上述功能。后者可为整型/浮点型变量设置误差容忍范围,常用于处理传输前的舍入(例如浮点数常被截断为1ms精度,此时可使用专为此设计的TD_MSECTOLERANCE宏,否则需直接指定数值)。

注意:基于舍入值的推算需谨慎,传输值的微小差异可能导致计算结果大幅偏离!

待完善: DEFINE_PRED_TYPEDESCRIPTION

示例

#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

预测实体创建

标准预测系统仅维护现有实体状态,创建新实体需使用CreatePredictedEntityByName()。该函数在客户端创建临时实体,待服务器实体到达后进行替换。务必通过shared code(en)调用。

CBaseEntity::CreatePredictedEntityByName( char* classname, char* class_file, int line, bool persist = false )

参数class_file需填写类声明所在文件名(代码中称为module),line为行号(

证实:意味着实体必须有共享类声明

),二者用于生成实体指纹。persist使实体参与重预测检测。

若预测实体包含未传输数据,需在C_BaseEntity::OnPredictedEntityRemove()中将数据复制到真实实体。

调试技巧

IsFirstTimePredicted()

通过prediction->IsFirstTimePredicted()确保代码仅在首次预测时执行,避免在服务器更新校验时重复执行。需#include "prediction.h"

SharedRandom()

使用此函数生成随机数,其种子基于用户指令编号,保证客户端与服务器结果一致。

CDisablePredictionFiltering

[待完善]

抑制网络事件

非关键事件(如武器特效)可完全在客户端处理,抑制相关网络传输能有效降低带宽。

IPredictionSystem(en)::SuppressHostEvents()专为此设计。调用时会暂停向指定玩家发送网络事件,传入NULL恢复传输。例如:

if ( pPlayer->IsPredictingWeapons() )
	IPredictionSystem::SuppressHostEvents( pPlayer );

pWeapon->CreateEffects();

IPredictionSystem::SuppressHostEvents( NULL );

注意:使用此方法后需编写代码让被抑制玩家的客户端自行生成特效。

示例

此处提供可输出预测信息到控制台的简易武器(en)。配合net_fakelag 200(或更高)使用快速次要开火,可观察引擎如何处理多预测射击。

Note.png注意:若玩家实体未创建预测视图模型,延迟时武器会显示抖动。

故障排查

若新增功能未遵循上述步骤,可能导致实体抖动或动画异常。可通过cl_predictionlist(en)cl_pdump(en)调试。cl_pred_optimize(en)有时也有帮助。

假设cl_pdump面板中某变量偶尔变红,说明客户端与服务器端该变量值不一致。常见原因:

  1. 客户端未运行部分服务器端代码(可能因#ifdef GAME_DLL#ifndef CLIENT_DLL隔离了相关代码)
  2. 影响该变量的其他变量未加入数据表传输(客户端值始终错误)
  3. 未使用DEFINE_PRED_FIELD_TOL设置适当容差(例如4位精度的0.0-255.0浮点数需约17.0的容差)

排查预测问题通常需要追溯影响变量值的所有代码路径,检查相关变量同步情况。初期可能繁琐,但掌握方法后可快速定位。

另见