这篇条目有关 Source引擎。如需详情,点击这里。

NPC Lag Compensation

From Valve Developer Community
< Zh
Revision as of 05:39, 26 March 2025 by MoRanYue (talk | contribs) (deepseek translation)
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.


如果没有延迟补偿(en),当您射击目标时,必须考虑您的网络延迟时间并相应地向前瞄准。例如,如果您的延迟是100毫秒,您必须瞄准目标头部在100毫秒后的位置,而不是当前看到的位置。

显然这并不理想,因此多人Source游戏实现了延迟补偿。在计算子弹是否命中时,会暂时将所有玩家的位置回退到射击者的延迟时间,从而根据射击者开火时看到的精确画面进行碰撞计算。该效果在NPC中缺失,但可以通过复制玩家延迟补偿代码来实现。

本教程详细说明了所有需要的修改——虽然涉及大量代码,但其中95%是从玩家延迟补偿代码中复制的。

ai_basenpc.h

在此行之前:

typedef CBitVec<MAX_CONDITIONS> CAI_ScheduleBits;

添加以下代码(取自player_lagcompensation.cpp但已修改):

#define MAX_LAYER_RECORDS (CBaseAnimatingOverlay::MAX_OVERLAYS)

struct LayerRecordNPC
{
	int m_sequence;
	float m_cycle;
	float m_weight;
	int m_order;

	LayerRecordNPC()
	{
		m_sequence = 0;
		m_cycle = 0;
		m_weight = 0;
		m_order = 0;
	}

	LayerRecordNPC( const LayerRecordNPC& src )
	{
		m_sequence = src.m_sequence;
		m_cycle = src.m_cycle;
		m_weight = src.m_weight;
		m_order = src.m_order;
	}
};

struct LagRecordNPC
{
public:
	LagRecordNPC()
	{
		m_fFlags = 0;
		m_vecOrigin.Init();
		m_vecAngles.Init();
		m_vecMins.Init();
		m_vecMaxs.Init();
		m_flSimulationTime = -1;
		m_masterSequence = 0;
		m_masterCycle = 0;
	}

	LagRecordNPC( const LagRecordNPC& src )
	{
		m_fFlags = src.m_fFlags;
		m_vecOrigin = src.m_vecOrigin;
		m_vecAngles = src.m_vecAngles;
		m_vecMins = src.m_vecMins;
		m_vecMaxs = src.m_vecMaxs;
		m_flSimulationTime = src.m_flSimulationTime;
		for( int layerIndex = 0; layerIndex < MAX_LAYER_RECORDS; ++layerIndex )
		{
			m_layerRecords[layerIndex] = src.m_layerRecords[layerIndex];
		}
		m_masterSequence = src.m_masterSequence;
		m_masterCycle = src.m_masterCycle;
	}

	// 玩家是否在本帧死亡
	int						m_fFlags;

	// 玩家位置、朝向和包围盒
	Vector					m_vecOrigin;
	QAngle					m_vecAngles;
	Vector					m_vecMins;
	Vector					m_vecMaxs;

	float					m_flSimulationTime;	
	
	// 玩家动画细节,用于正确计算腿部位置
	LayerRecordNPC			m_layerRecords[MAX_LAYER_RECORDS];
	int						m_masterSequence;
	float					m_masterCycle;
};

我们将NPC的延迟记录直接附加到NPC上以避免混淆,因此NPC代码需要能访问这些记录。

CAI_BaseNPC类定义中添加:

public:
	CUtlFixedLinkedList<LagRecordNPC>* GetLagTrack() { return m_LagTrack; }
	LagRecordNPC*	GetLagRestoreData() { if ( m_RestoreData != NULL ) return m_RestoreData; else return new LagRecordNPC(); }
	LagRecordNPC*	GetLagChangeData() { if ( m_ChangeData != NULL ) return m_ChangeData; else return new LagRecordNPC(); }
	void		SetLagRestoreData(LagRecordNPC* l) { if ( m_RestoreData != NULL ) delete m_RestoreData; m_RestoreData = l; }
	void		SetLagChangeData(LagRecordNPC* l) { if ( m_ChangeData != NULL ) delete m_ChangeData; m_ChangeData = l; }
	void		FlagForLagCompensation( bool tempValue ) { m_bFlaggedForLagCompensation = tempValue; }
	bool		IsLagFlagged() { return m_bFlaggedForLagCompensation; }

private:
	CUtlFixedLinkedList<LagRecordNPC>* m_LagTrack;
	LagRecordNPC*	m_RestoreData;
	LagRecordNPC*	m_ChangeData;
	bool		m_bFlaggedForLagCompensation;

这个"延迟记录"存储NPC的历史位置和动画信息。

ai_basenpc.cpp

CAI_BaseNPC构造函数末尾添加:

m_LagTrack = new CUtlFixedLinkedList< LagRecordNPC >();

在析构函数开头添加:

m_LagTrack->Purge();
delete m_LagTrack;

player.cpp/player.h

将函数WantsLagCompensationOnEntity的第一个参数从const CBasePlayer *pPlayer改为const CBaseEntity *pEntity

在函数中将所有pPlayer引用改为pEntity,并替换以下行:

float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat();

改为:

float maxspeed;
CBasePlayer *pPlayer = ToBasePlayer((CBaseEntity*)pEntity);
if ( pPlayer )
	maxspeed = pPlayer->MaxSpeed();
else
	maxspeed = 600;
float maxDistance = 1.5 * maxspeed * sv_maxunlag.GetFloat();

hl2mp_player.cpp/hl2mp_player.h

修改CHL2MP_Player::WantsLagCompensationOnEntity函数:

bool CHL2MP_Player::WantsLagCompensationOnEntity( const CBaseEntity *pEntity, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
{
	if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
		return false;

	return BaseClass::WantsLagCompensationOnEntity(pEntity,pCmd,pEntityTransmitBits);
}

player_lagcompensation.cpp

添加头文件:

#include "ai_basenpc.h"

添加NPC回溯函数:

static void RestoreEntityTo( CAI_BaseNPC *pEntity, const Vector &vWantedPos )
{
	// 尝试从当前位置移动到目标位置
	trace_t tr;
	UTIL_TraceEntity( pEntity, vWantedPos, vWantedPos, MASK_NPCSOLID, pEntity, COLLISION_GROUP_NPC, &tr );
	if ( tr.startsolid || tr.allsolid )
	{
		// 回溯失败处理...
	}
	else
	{
		UTIL_SetOrigin( pEntity, tr.endpos, true );
	}
}

CLagCompensationManager类中添加:

void BacktrackEntity( CAI_BaseNPC *entity, float flTargetTime );

更新FrameUpdatePostEntityThink函数,添加NPC处理循环。

修改StartLagCompensation函数,添加NPC标记初始化。

在碰撞检测部分添加NPC处理分支。

实现BacktrackEntity函数,完整复制玩家回溯逻辑并适配NPC。

最后在FinishLagCompensation中添加NPC状态恢复代码。

验证方法

在控制台输入: sv_cheats 1 sv_showlagcompensation 1 射击NPC时会出现蓝色骨骼框表示延迟补偿生效。