Interpolation

From Valve Developer Community
< Zh
Revision as of 05:24, 26 March 2025 by MoRanYue (talk | contribs)
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)中文 (zh)Translate (Translate)
Info content.png
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.
Also, please make sure the article complies with the alternate languages guide.(en)
This notice is put here by LanguageBar template and if you want to remove it after updating the translation you can do so on this page.


A multiplayer client will typically need to render three or more frames with each server update it receives (assuming 60fps+ and cl_updaterate(en) 20).

Source's interpolation system prevents the jittery motion this would ordinarily lead to by buffering server updates then playing them back with the gaps smoothly interpolated between. It can also protect against glitches caused by packet loss.

The server knows how much interpolation each client has, and adjusts lag compensation(en) accordingly.

Impact and management

Interpolation adds artificial latency to a player's view of the game world and as such should be kept to the barest minimum. Unfortunately, Valve's games still default to a minimum interpolation delay ("lerp") of 100ms, a value tuned for the era of dial-up modems!

  • Players should set cl_interp 0, as this will ensure that the lerp is the precise length needed to accommodate the current server update rate(en). Raising the update rate will reduce lerp further.
  • Modders should consider removing or renaming cl_interp to prevent confusion.
  • Server operators can regulate lerp with sv_client_min_interp_ratio and sv_client_max_interp_ratio.

Players who suffer from packet loss may want to raise cl_interp_ratio to 3 (protects against one dropped packet) or even 4 (protects against two consecutive dropped packets).

Implementation

This simple entity prints an interpolated float every frame.

Server:

#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;

	// without this, LATCH_SIMULATION_VAR will never be triggered
	SetSimulationTime( gpGlobals->curtime ); 

	SetNextThink(gpGlobals->curtime + 0.0001);
	BaseClass::Think();
}

Client:

#include "cbase.h"

class C_InterpDemo : public C_BaseEntity
{
public:
	DECLARE_CLASS(C_InterpDemo, C_BaseEntity);
	DECLARE_CLIENTCLASS();
 
	C_InterpDemo();

	bool ShouldInterpolate() { return true; } // ordinarily only entities in PVS are interpolated
 
	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") // just a debug name, can be anything unique
{
	// This is a simulation latch, so the variable will only be interpolated
	// if the entity is moving or has a new SimulationTime.
	AddVar( &m_MyFloat, &m_iv_MyFloat, LATCH_SIMULATION_VAR );
	UpdateMsg = "";
}
 
void C_InterpDemo::PostDataUpdate(DataUpdateType_t updateType)
{
	UpdateMsg = " (from server)";
	SetNextClientThink(CLIENT_THINK_ALWAYS);
	BaseClass::PostDataUpdate(updateType);
}
 
void C_InterpDemo::ClientThink()
{
	Msg("Interpolated float: %f%s\n",m_MyFloat,UpdateMsg);
	UpdateMsg = "";
	SetNextClientThink(CLIENT_THINK_ALWAYS);
}

The important steps are:

  1. Initialise our CInterpolatedVar in the class constructor, giving it a debug name in the process.
    待完善: How to debug?
  2. Call AddVar() to latch the CInterpolatedVar onto the actual variable.
  3. Ensure that ShouldInterpolate() returns true when we want it to. (This entity isn't visible so wouldn't normally be interpolated.)
  4. On the server, call SetSimulationTime(). The simulation latch we chose in AddVar() is triggered by changes to this value or to the origin/angles. You could also choose an animation latch which is triggered by changes to the entity's current animation frame ("cycle(en)").

Troubleshooting

If your variable is not being interpolated:

  • Check C_BaseEntity::PostDataUpdate(), which is where the interp system kicks in.
  • If the interpolated value needs to be passed to other code (e.g. VPhysics positioning), make sure you are doing so in ClientThink()(en) every frame.
  • Make sure your entity is in PVS(en) on the client.

See also