Zh/Interpolation: Difference between revisions

From Valve Developer Community
< Zh
Jump to navigation Jump to search
(Created page with "{{subst:#if: Translation of 'Interpolation' to '中文' via Template:LanguageBar buttons * * * * * * * * * * * * * * * * * * * * * * * * * * * *...")
 
No edit summary
 
(3 intermediate revisions by one other user not shown)
Line 1: Line 1:
{{subst:#if:|||{{LAuto/t}}
{{LanguageBar}}
 
--- DON'T JUST BLINDLY DELETE THIS PART. DO REPLACE THE LINKS AND CATEGORIES. THE PICTURE SHOWS HOW TO USE IT ! ---
 
SEARCH FOR:
\[\[(?!#|File(?:[ _]talk)?:|Image(?:[ _]talk)?:|Media:|Template(?:[ _]talk)?:|MediaWiki(?:[ _]talk)?:|Talk:|Category[ _]talk:|Project[ _]talk:|Valve[ _]Developer[ _]Community[ _]talk:|Help[ _]talk:|User(?:[ _]talk)?:|c:|commons:|Dictionary:|Google:|GoogleGroups:|IMDB:|M:|Meta:|Metawikipedia:|MW:|SdkBug:|SourceForge:|Steampowered:|W:|Wiki:|WikiBooks:|Wikipedia:|Wikiquote:|Wiktionary:|WP:)(:?(?:Category|Category|Help|Project|Valve[ _]Developer[ _]Community|Special|)(?:[^\|\]]+))(\|?.*?)\]\]
 
REPLACE WITH:
{{subst:LAuto|$1$2}}
 
}}{{wip}}{{translate}}
{{toc-right}}
{{toc-right}}
{{LanguageBar}}


A multiplayer client will typically need to render three or more frames with each server update it receives (assuming 60fps+ and [[cl_updaterate]] 20).
在多人游戏中,客户端通常需要在每次收到服务器更新时渲染三帧或更多帧(假设帧率为60fps+且{{L|cl_updaterate}}设为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 [[w:Interpolation|interpolated]] between. It can also protect against glitches caused by packet loss.
Source引擎的'''插值系统'''通过缓冲服务器更新并在间隙间进行平滑[[w:Interpolation|插值]]回放,避免了可能出现的画面抖动问题。该系统还能防止因丢包导致的显示异常。


The server knows how much interpolation each client has, and adjusts [[lag compensation]] accordingly.
服务器知晓每个客户端的插值量,并据此调整{{L|lag compensation|延迟补偿}}。


== 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!
插值会为玩家视角增加额外延迟,因此应将其控制在最低限度。遗憾的是,V社游戏仍默认使用100ms的最小插值延迟("lerp"),这个数值是为拨号上网时代设计的!


* Players should set <code>cl_interp 0</code>, as this will ensure that the lerp is the precise length needed to accommodate the current [[cl_updaterate|server update rate]]. Raising the update rate will reduce lerp further.
* 玩家应设置<code>cl_interp 0</code>,这将确保lerp长度精确匹配当前{{L|cl_updaterate|服务器更新速率}}。提升更新速率可进一步减少lerp。
* Modders should consider removing or renaming <code>cl_interp</code> to prevent confusion.
* Mod开发者应考虑移除或重命名<code>cl_interp</code>以避免混淆。
* Server operators can regulate lerp with <code>sv_client_min_interp_ratio</code> and <code>sv_client_max_interp_ratio</code>.
* 服务器运营者可通过<code>sv_client_min_interp_ratio</code><code>sv_client_max_interp_ratio</code>调控lerp。


Players who suffer from packet loss may want to raise <code>cl_interp_ratio</code> to 3 (protects against one dropped packet) or even 4 (protects against two consecutive dropped packets).
遭遇丢包的玩家可将<code>cl_interp_ratio</code>设为3(防御单次丢包)甚至4(防御连续两次丢包)。


== Implementation ==
== 实现 ==


This simple entity prints an interpolated float every frame.
这个简单实体每帧输出一个插值后的浮点数。


=== Server: ===
=== 服务端: ===


<source lang=cpp>
<source lang=cpp>
Line 63: Line 52:
m_MyFloat += 0.1;
m_MyFloat += 0.1;


// without this, LATCH_SIMULATION_VAR will never be triggered
// 没有这行,LATCH_SIMULATION_VAR永远不会触发
SetSimulationTime( gpGlobals->curtime );  
SetSimulationTime( gpGlobals->curtime );  


Line 71: Line 60:
</source>
</source>


=== Client: ===
=== 客户端: ===


<source lang=cpp>
<source lang=cpp>
Line 84: Line 73:
C_InterpDemo();
C_InterpDemo();


bool ShouldInterpolate() { return true; } // ordinarily only entities in PVS are interpolated
bool ShouldInterpolate() { return true; } // 通常只有PVS中的实体会被插值
   
   
void PostDataUpdate(DataUpdateType_t updateType);
void PostDataUpdate(DataUpdateType_t updateType);
Line 102: Line 91:
   
   
C_InterpDemo::C_InterpDemo() :
C_InterpDemo::C_InterpDemo() :
m_iv_MyFloat("C_InterpDemo::m_iv_MyFloat") // just a debug name, can be anything unique
m_iv_MyFloat("C_InterpDemo::m_iv_MyFloat") // 仅调试用名称,可自定义唯一标识
{
{
// This is a simulation latch, so the variable will only be interpolated
// 这是模拟锁存器,仅当实体移动或更新SimulationTime时进行插值
// if the entity is moving or has a new SimulationTime.
AddVar( &m_MyFloat, &m_iv_MyFloat, LATCH_SIMULATION_VAR );
AddVar( &m_MyFloat, &m_iv_MyFloat, LATCH_SIMULATION_VAR );
UpdateMsg = "";
UpdateMsg = "";
Line 112: Line 100:
void C_InterpDemo::PostDataUpdate(DataUpdateType_t updateType)
void C_InterpDemo::PostDataUpdate(DataUpdateType_t updateType)
{
{
UpdateMsg = " (from server)";
UpdateMsg = "(来自服务端)";
SetNextClientThink(CLIENT_THINK_ALWAYS);
SetNextClientThink(CLIENT_THINK_ALWAYS);
BaseClass::PostDataUpdate(updateType);
BaseClass::PostDataUpdate(updateType);
Line 119: Line 107:
void C_InterpDemo::ClientThink()
void C_InterpDemo::ClientThink()
{
{
Msg("Interpolated float: %f%s\n",m_MyFloat,UpdateMsg);
Msg("插值浮点数:%f%s\n",m_MyFloat,UpdateMsg);
UpdateMsg = "";
UpdateMsg = "";
SetNextClientThink(CLIENT_THINK_ALWAYS);
SetNextClientThink(CLIENT_THINK_ALWAYS);
Line 125: Line 113:
</source>
</source>


'''The important steps are:'''
'''关键步骤如下:'''


# Initialise our <code>CInterpolatedVar</code> in the class constructor, giving it a debug name in the process. {{todo|How to debug?}}
# 在类构造函数中初始化<code>CInterpolatedVar</code>并赋予调试名称。{{todo|如何调试?}}
# Call <code>AddVar()</code> to latch the <code>CInterpolatedVar</code> onto the actual variable.
# 调用<code>AddVar()</code><code>CInterpolatedVar</code>与目标变量绑定
# Ensure that <code>ShouldInterpolate()</code> returns true when we want it to. (This entity isn't visible so wouldn't normally be interpolated.)
# 确保需要插值时<code>ShouldInterpolate()</code>返回true(该实体不可见,通常不会被插值)
# On the server, call <code>SetSimulationTime()</code>. The simulation latch we chose in <code>AddVar()</code> 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 ("[[Animating a model|cycle]]").
# 在服务端调用<code>SetSimulationTime()</code>。在<code>AddVar()</code>中选择的模拟锁存器会因该值或位置/角度的变化而触发。也可选择动画锁存器,其由实体当前动画帧("{{L|Animating a model|cycle}}")变化触发


=== Troubleshooting ===
=== 故障排查 ===


'''If your variable is not being interpolated:'''
'''若变量未插值:'''


* Check <code>C_BaseEntity::PostDataUpdate()</code>, which is where the interp system kicks in.
* 检查<code>C_BaseEntity::PostDataUpdate()</code>,这是插值系统的触发点
* If the interpolated value needs to be passed to other code (e.g. VPhysics positioning), make sure you are doing so in <code>[[ClientThink()]]</code> every frame.
* 若插值需传递至其他代码(如VPhysics定位),确保在<code>{{L|ClientThink()}}</code>中每帧执行
* Make sure your entity is in [[PVS]] on the client.
* 确认实体在客户端的{{L|PVS}}范围内


== See also ==
== 参见 ==


* [[Source Multiplayer Networking#Entity interpolation|Entity interpolation]]
* {{L|Source Multiplayer Networking#Entity interpolation|实体插值}}
* [[Lag compensation]]
* {{L|Lag compensation|延迟补偿}}


[[Category:Networking]]
{{ACategory|Networking}}

Latest revision as of 10:41, 9 April 2025

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.


在多人游戏中,客户端通常需要在每次收到服务器更新时渲染三帧或更多帧(假设帧率为60fps+且cl_updaterate(en)设为20)。

Source引擎的插值系统通过缓冲服务器更新并在间隙间进行平滑插值回放,避免了可能出现的画面抖动问题。该系统还能防止因丢包导致的显示异常。

服务器知晓每个客户端的插值量,并据此调整延迟补偿(en)

影响与管理

插值会为玩家视角增加额外延迟,因此应将其控制在最低限度。遗憾的是,V社游戏仍默认使用100ms的最小插值延迟("lerp"),这个数值是为拨号上网时代设计的!

  • 玩家应设置cl_interp 0,这将确保lerp长度精确匹配当前服务器更新速率(en)。提升更新速率可进一步减少lerp。
  • Mod开发者应考虑移除或重命名cl_interp以避免混淆。
  • 服务器运营者可通过sv_client_min_interp_ratiosv_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);
}

关键步骤如下:

  1. 在类构造函数中初始化CInterpolatedVar并赋予调试名称。
    待完善: 如何调试?
  2. 调用AddVar()CInterpolatedVar与目标变量绑定
  3. 确保需要插值时ShouldInterpolate()返回true(该实体不可见,通常不会被插值)
  4. 在服务端调用SetSimulationTime()。在AddVar()中选择的模拟锁存器会因该值或位置/角度的变化而触发。也可选择动画锁存器,其由实体当前动画帧("cycle(en)")变化触发

故障排查

若变量未插值:

  • 检查C_BaseEntity::PostDataUpdate(),这是插值系统的触发点
  • 若插值需传递至其他代码(如VPhysics定位),确保在ClientThink()(en)中每帧执行
  • 确认实体在客户端的PVS(en)范围内

参见