Interpolation
< Zh
Jump to navigation
Jump to search
This page is Machine translated
It is not recommended to use machine translation without any corrections.
If the article is not corrected in the long term, it will be removed.在多人游戏中,客户端通常需要在每次收到服务器更新时渲染三帧或更多帧(假设帧率为60fps+且cl_updaterate 设为20)。
Source引擎的插值系统通过缓冲服务器更新并在间隙间进行平滑插值回放,避免了可能出现的画面抖动问题。该系统还能防止因丢包导致的显示异常。
服务器知晓每个客户端的插值量,并据此调整延迟补偿 。
影响与管理
插值会为玩家视角增加额外延迟,因此应将其控制在最低限度。遗憾的是,V社游戏仍默认使用100ms的最小插值延迟("lerp"),这个数值是为拨号上网时代设计的!
- 玩家应设置
cl_interp 0
,这将确保lerp长度精确匹配当前服务器更新速率 。提升更新速率可进一步减少lerp。 - Mod开发者应考虑移除或重命名
cl_interp
以避免混淆。 - 服务器运营者可通过
sv_client_min_interp_ratio
和sv_client_max_interp_ratio
调控lerp。
遭遇丢包的玩家可将cl_interp_ratio
设为3(防御单次丢包)甚至4(防御连续两次丢包)。
实现
这个简单实体每帧输出一个插值后的浮点数。
服务端:
#include "cbase.h"
class CInterpDemo : public CBaseEntity
{
public:
DECLARE_CLASS(CInterpDemo, CBaseEntity);
DECLARE_SERVERCLASS();
CInterpDemo() { m_MyFloat = 0; }
void Spawn() { SetNextThink(gpGlobals->curtime + 0.0001); BaseClass::Spawn(); }
int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); }
void Think();
CNetworkVar(float,m_MyFloat);
};
IMPLEMENT_SERVERCLASS_ST(CInterpDemo, DTInterpDemo)
SendPropFloat( SENDINFO(m_MyFloat) ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( interp_demo, CInterpDemo );
void CInterpDemo::Think()
{
m_MyFloat += 0.1;
// 没有这行,LATCH_SIMULATION_VAR永远不会触发
SetSimulationTime( gpGlobals->curtime );
SetNextThink(gpGlobals->curtime + 0.0001);
BaseClass::Think();
}
客户端:
#include "cbase.h"
class C_InterpDemo : public C_BaseEntity
{
public:
DECLARE_CLASS(C_InterpDemo, C_BaseEntity);
DECLARE_CLIENTCLASS();
C_InterpDemo();
bool ShouldInterpolate() { return true; } // 通常只有PVS中的实体会被插值
void PostDataUpdate(DataUpdateType_t updateType);
void ClientThink();
float m_MyFloat;
char* UpdateMsg;
CInterpolatedVar<float> m_iv_MyFloat;
};
IMPLEMENT_CLIENTCLASS_DT(C_InterpDemo,DTInterpDemo,CInterpDemo)
RecvPropFloat( RECVINFO(m_MyFloat) ),
END_RECV_TABLE()
LINK_ENTITY_TO_CLASS( interp_demo, C_InterpDemo );
C_InterpDemo::C_InterpDemo() :
m_iv_MyFloat("C_InterpDemo::m_iv_MyFloat") // 仅调试用名称,可自定义唯一标识
{
// 这是模拟锁存器,仅当实体移动或更新SimulationTime时进行插值
AddVar( &m_MyFloat, &m_iv_MyFloat, LATCH_SIMULATION_VAR );
UpdateMsg = "";
}
void C_InterpDemo::PostDataUpdate(DataUpdateType_t updateType)
{
UpdateMsg = "(来自服务端)";
SetNextClientThink(CLIENT_THINK_ALWAYS);
BaseClass::PostDataUpdate(updateType);
}
void C_InterpDemo::ClientThink()
{
Msg("插值浮点数:%f%s\n",m_MyFloat,UpdateMsg);
UpdateMsg = "";
SetNextClientThink(CLIENT_THINK_ALWAYS);
}
关键步骤如下:
- 在类构造函数中初始化
CInterpolatedVar
并赋予调试名称。待完善: 如何调试? - 调用
AddVar()
将CInterpolatedVar
与目标变量绑定 - 确保需要插值时
ShouldInterpolate()
返回true(该实体不可见,通常不会被插值) - 在服务端调用
SetSimulationTime()
。在AddVar()
中选择的模拟锁存器会因该值或位置/角度的变化而触发。也可选择动画锁存器,其由实体当前动画帧("cycle ")变化触发
故障排查
若变量未插值:
- 检查
C_BaseEntity::PostDataUpdate()
,这是插值系统的触发点 - 若插值需传递至其他代码(如VPhysics定位),确保在
ClientThink()
中每帧执行 - 确认实体在客户端的PVS 范围内