Adding Ironsights

From Valve Developer Community
< Zh
Revision as of 20:47, 19 April 2023 by 123wer55 (talk | contribs) (Created page with "{{lang|Adding Ironsights}} 这是一个关于如何添加机瞄系统的教程,根据 Jorg的代码 . 实现他的途径是使用NetWork变量,尝试着...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
English (en)中文 (zh)Translate (Translate)

这是一个关于如何添加机瞄系统的教程,根据 Jorg的代码 .

实现他的途径是使用NetWork变量,尝试着让这个系统更容易影响到游戏的逻辑(比如说允许机瞄影响到子弹散射角度大小之类的),这个机瞄系统还允许更改v模型的角度和玩家镜头的FOV

武器脚本文件

一开始,你需要通过 武器脚本 来添加脚本变量才可以调整武器模型的坐标位置改变来实现机瞄效果

weapon_parse.h

添加 FileWeaponInfo_t: 的变量(public)

	Vector					vecIronsightPosOffset;
	QAngle					angIronsightAngOffset;
	float					flIronsightFOVOffset;

weapon_parse.cpp

在 FileWeaponInfo_t::Parse: 里添加如下代码

	KeyValues *pSights = pKeyValuesData->FindKey( "IronSight" );
	if (pSights)
	{
		vecIronsightPosOffset.x		= pSights->GetFloat( "forward", 0.0f );
		vecIronsightPosOffset.y		= pSights->GetFloat( "right", 0.0f );
		vecIronsightPosOffset.z		= pSights->GetFloat( "up", 0.0f );

		angIronsightAngOffset[PITCH]	= pSights->GetFloat( "pitch", 0.0f );
		angIronsightAngOffset[YAW]		= pSights->GetFloat( "yaw", 0.0f );
		angIronsightAngOffset[ROLL]		= pSights->GetFloat( "roll", 0.0f );

		flIronsightFOVOffset		= pSights->GetFloat( "fov", 0.0f );
	}
	else
	{
		//note: you can set a bool here if you'd like to disable ironsights for weapons with no IronSight-key
		vecIronsightPosOffset = vec3_origin;
		angIronsightAngOffset.Init();
		flIronsightFOVOffset = 0.0f;
	}

weapon_smg1.txt

这是一个如何在你的武器脚本里添加武器机瞄系统位移量数值的例子

	IronSight
	{
		"forward"	"-10"
		"right"		"-6.91"
		"up"		"0.185"
		"roll"		"-20"
		"fov"		"-20"
	}

获取位移量

现在添加一些功能性代码来得到我们从武器脚本文件里解析到的位移量数值 额外添加一些 控制台变量 来覆写掉旧的解析的数值,这样就可以很容易的通过 控制台来改变新的武器模型偏移量数值。


在那之前, 我们需要在开头包含 "c_baseplayer.h" 头文件来允许使用一些代码,因为要在客户端进行一些处理,

basecombatweapon_shared.cpp

基本的, 你需要在包含 "c_baseplayer.h" 头文件时加上 #ifdef CLIENT_DLL 标签。不然编译的时候会出错。

#ifdef CLIENT_DLL  
 	#include "c_baseplayer.h"
 #endif

你应该加在这段代码的下面:(译者注:似乎不需要?)

#if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL )
  #include "tf_shareddefs.h"
#endif

basecombatweapon_shared.h

添加在 public 段

	Vector					GetIronsightPositionOffset( void ) const;
	QAngle					GetIronsightAngleOffset( void ) const;
	float					GetIronsightFOVOffset( void ) const;

basecombatweapon_shared.cpp

添加这些功能性代码 (直接做这一步会导致错误)

Vector CBaseCombatWeapon::GetIronsightPositionOffset( void ) const
{
	if( viewmodel_adjust_enabled.GetBool() )
		return Vector( viewmodel_adjust_forward.GetFloat(), viewmodel_adjust_right.GetFloat(), viewmodel_adjust_up.GetFloat() );
	return GetWpnData().vecIronsightPosOffset;
}

QAngle CBaseCombatWeapon::GetIronsightAngleOffset( void ) const
{
	if( viewmodel_adjust_enabled.GetBool() )
		return QAngle( viewmodel_adjust_pitch.GetFloat(), viewmodel_adjust_yaw.GetFloat(), viewmodel_adjust_roll.GetFloat() );
	return GetWpnData().angIronsightAngOffset;
}

float CBaseCombatWeapon::GetIronsightFOVOffset( void ) const
{
	if( viewmodel_adjust_enabled.GetBool() )
		return viewmodel_adjust_fov.GetFloat();
	return GetWpnData().flIronsightFOVOffset;
}

basecombatweapon_shared.cpp

这些变量通常在包含头文件后定义

//forward declarations of callbacks used by viewmodel_adjust_enable and viewmodel_adjust_fov
void vm_adjust_enable_callback( IConVar *pConVar, char const *pOldString, float flOldValue );
void vm_adjust_fov_callback( IConVar *pConVar, const char *pOldString, float flOldValue );

ConVar viewmodel_adjust_forward( "viewmodel_adjust_forward", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_right( "viewmodel_adjust_right", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_up( "viewmodel_adjust_up", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_pitch( "viewmodel_adjust_pitch", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_yaw( "viewmodel_adjust_yaw", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_roll( "viewmodel_adjust_roll", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_fov( "viewmodel_adjust_fov", "0", FCVAR_REPLICATED, "Note: this feature is not available during any kind of zoom", vm_adjust_fov_callback );
ConVar viewmodel_adjust_enabled( "viewmodel_adjust_enabled", "0", FCVAR_REPLICATED|FCVAR_CHEAT, "enabled viewmodel adjusting", vm_adjust_enable_callback );

包括这些:

void vm_adjust_enable_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef sv_cheats( "sv_cheats" );
	if( !sv_cheats.IsValid() || sv_cheats.GetBool() )
		return;

	ConVarRef var( pConVar );

	if( var.GetBool() )
		var.SetValue( "0" );
}

void vm_adjust_fov_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	if( !viewmodel_adjust_enabled.GetBool() )
		return;

	ConVarRef var( pConVar );

	CBasePlayer *pPlayer = 
#ifdef GAME_DLL
		UTIL_GetCommandClient();
#else
		C_BasePlayer::GetLocalPlayer();
#endif
	if( !pPlayer )
		return;

	if( !pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() + var.GetFloat(), 0.1f ) )
	{
		Warning( "Could not set FOV\n" );
		var.SetValue( "0" );
	}
}

添加开启机瞄的函数系统

你肯定想要能使用上面定义好的机瞄系统

basecombatweapon_shared.h

添加这两个NetWork变量

	CNetworkVar( bool, m_bIsIronsighted );
	CNetworkVar( float, m_flIronsightedTime );

basecombatweapon_shared.cpp

在 CBaseCombatWeapon::CBaseCombatWeapon() 里添加两个构造函数,并给他们默认值

	m_bIsIronsighted = false;
	m_flIronsightedTime = 0.0f;


链接表 (位于DT_BaseCombatWeapon):

	SendPropBool( SENDINFO( m_bIsIronsighted ) ),
	SendPropFloat( SENDINFO( m_flIronsightedTime ) ),

并且添加

	RecvPropInt( RECVINFO( m_bIsIronsighted ), 0, RecvProxy_ToggleSights ), //note: RecvPropBool is actually RecvPropInt (see its implementation), but we need a proxy
	RecvPropFloat( RECVINFO( m_flIronsightedTime ) ),

(译者注:SendProp应该和原先的同类定义放在一起,RecvPropInt也是如此)

As mentioned, we need a RecvProxy on that variable. We don't just want the boolean to update, we also want the ironsights to toggle if it was changed only on the server.

So, here's the code for the proxy:

#ifdef CLIENT_DLL
void RecvProxy_ToggleSights( const CRecvProxyData* pData, void* pStruct, void* pOut )
{
	CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pStruct;
	if( pData->m_Value.m_Int )
		pWeapon->EnableIronsights();
	else
		pWeapon->DisableIronsights();
}
#endif

Prediction-table (CBaseCombatWeapon):

	DEFINE_PRED_FIELD( m_bIsIronsighted, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flIronsightedTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),


If you want the variables to be saved in a save-game, also add them in the data-description:

	DEFINE_FIELD( m_bIsIronsighted, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flIronsightedTime, FIELD_FLOAT ),

basecombatweapon_shared.h

Now add accessors for the ironsight-variables:

	virtual bool				HasIronsights( void ) { return true; } //default yes; override and return false for weapons with no ironsights (like weapon_crowbar)
	bool					IsIronsighted( void );
	void					ToggleIronsights( void );
	void					EnableIronsights( void );
	void					DisableIronsights( void );
	void					SetIronsightTime( void );

basecombatweapon_shared.cpp

And of course define them:

bool CBaseCombatWeapon::IsIronsighted( void )
{
	return ( m_bIsIronsighted || viewmodel_adjust_enabled.GetBool() );
}

void CBaseCombatWeapon::ToggleIronsights( void )
{
	if( m_bIsIronsighted )
		DisableIronsights();
	else
		EnableIronsights();
}

void CBaseCombatWeapon::EnableIronsights( void )
{
#ifdef CLIENT_DLL
	if( !prediction->IsFirstTimePredicted() )
		return;
#endif
	if( !HasIronsights() || m_bIsIronsighted )
		return;

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if( !pOwner )
		return;

	if( pOwner->SetFOV( this, pOwner->GetDefaultFOV() + GetIronsightFOVOffset(), 1.0f ) ) //modify the last value to adjust how fast the fov is applied
	{
		m_bIsIronsighted = true;
		SetIronsightTime();
	}
}

void CBaseCombatWeapon::DisableIronsights( void )
{
#ifdef CLIENT_DLL
	if( !prediction->IsFirstTimePredicted() )
		return;
#endif
	if( !HasIronsights() || !m_bIsIronsighted )
		return;

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if( !pOwner )
		return;

	if( pOwner->SetFOV( this, 0, 0.4f ) ) //modify the last value to adjust how fast the fov is applied
	{
		m_bIsIronsighted = false;
		SetIronsightTime();
	}
}

void CBaseCombatWeapon::SetIronsightTime( void )
{
	m_flIronsightedTime = gpGlobals->curtime;
}

The usage of prediction requires the inclusion of the prediction.h header on the client.

Toggle-command

ConCommand

You probably want a command to switch between normal and sighted mode. It doesn't really matter where you put it, as long as you include the headers for CBasePlayer and CBaseCombatWeapon and it's client-side.

#ifdef CLIENT_DLL
void CC_ToggleIronSights( void )
{
	CBasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
	if( pPlayer == NULL )
		return;

	CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon();
	if( pWeapon == NULL )
		return;

	pWeapon->ToggleIronsights();

	engine->ServerCmd( "toggle_ironsight" ); //forward to server
}

static ConCommand toggle_ironsight("toggle_ironsight", CC_ToggleIronSights);
#endif

player.cpp

Then in CBasePlayer::ClientCommand, add this before returning false:

else if( stricmp( cmd, "toggle_ironsight" ) == 0 )
{
	CBaseCombatWeapon *pWeapon = GetActiveWeapon();
	if( pWeapon != NULL )
		pWeapon->ToggleIronsights();

	return true;
}

Automatic ironsight-toggle

basecombatweapon_shared.cpp

Add

DisableIronsights();

to the following places:

  • bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) for switching/holstering weapons
  • bool CBaseCombatWeapon::DefaultReload( int iClipSize1, int iClipSize2, int iActivity ) for reloading weapons
  • void CBaseCombatWeapon::Drop( const Vector &vecVelocity ) for weapon dropping


Note.png注意:bool CBaseCombatWeapon::DefaultDeploy - Much better also write function of Disable Iron Sight in Deploy function. This will help with auto-switching when all the ammo has been used.


basecombatweapon_shared.cpp

Find: bool CWeaponShotgun::StartReload( void )

and some were inside Add

DisableIronsights();

Adjust the viewmodel

Ok, now for the last step we want to move the viewmodel according to the offsets.

There are still some little issues to fix, though. Viewmodel-bob is still active while iron-sighting and the viewmodel lags way too much when going out of ironsight and rotating the view.

baseviewmodel_shared.cpp

void CBaseViewModel::CalcIronsights( Vector &pos, QAngle &ang )
{
	CBaseCombatWeapon *pWeapon = GetOwningWeapon();

	if ( !pWeapon )
		return;

	//get delta time for interpolation
	float delta = ( gpGlobals->curtime - pWeapon->m_flIronsightedTime ) * 2.5f; //modify this value to adjust how fast the interpolation is
	float exp = ( pWeapon->IsIronsighted() ) ? 
		( delta > 1.0f ) ? 1.0f : delta : //normal blending
		( delta > 1.0f ) ? 0.0f : 1.0f - delta; //reverse interpolation

	if( exp <= 0.001f ) //fully not ironsighted; save performance
		return;

	Vector newPos = pos;
	QAngle newAng = ang;

	Vector vForward, vRight, vUp, vOffset;
	AngleVectors( newAng, &vForward, &vRight, &vUp );
	vOffset = pWeapon->GetIronsightPositionOffset();

	newPos += vForward * vOffset.x;
	newPos += vRight * vOffset.y;
	newPos += vUp * vOffset.z;
	newAng += pWeapon->GetIronsightAngleOffset();
	//fov is handled by CBaseCombatWeapon

	pos += ( newPos - pos ) * exp;
	ang += ( newAng - ang ) * exp;
}

Make use of this code in CBaseViewModel::CalcViewModelView:

void CBaseViewModel::CalcViewModelView( CBasePlayer *owner, const Vector& eyePosition, const QAngle& eyeAngles )
{
	// UNDONE: Calc this on the server?  Disabled for now as it seems unnecessary to have this info on the server
#if defined( CLIENT_DLL )
	QAngle vmangoriginal = eyeAngles;
	QAngle vmangles = eyeAngles;
	Vector vmorigin = eyePosition;

	CBaseCombatWeapon *pWeapon = m_hWeapon.Get();
	//Allow weapon lagging
	//only if not in ironsight-mode
	if( pWeapon == NULL || !pWeapon->IsIronsighted() )
	{
		if ( pWeapon != NULL )
		{
	#if defined( CLIENT_DLL )
			if ( !prediction->InPrediction() )
	#endif
			{
				// add weapon-specific bob 
				pWeapon->AddViewmodelBob( this, vmorigin, vmangles );
			}
		}

		// Add model-specific bob even if no weapon associated (for head bob for off hand models)
		AddViewModelBob( owner, vmorigin, vmangles );

		// Add lag
		CalcViewModelLag( vmorigin, vmangles, vmangoriginal );

#if defined( CLIENT_DLL )
		if ( !prediction->InPrediction() )
		{
			// Let the viewmodel shake at about 10% of the amplitude of the player's view
			vieweffects->ApplyShake( vmorigin, vmangles, 0.1 );	
		}
#endif
	}

	CalcIronsights( vmorigin, vmangles );

	SetLocalOrigin( vmorigin );
	SetLocalAngles( vmangles );

#endif
}

baseviewmodel_shared.h

Don't forget to declare the new function:

 	void		CalcIronsights( Vector &pos, QAngle &ang );

Single Player Fix (Weapon model doesn't stay on ironsight)

In most cases weapon model (view model) dosen't stay in definied position when ironsight is toggled.

Fix is simply but requies few fixes (weapon blend to ironsight and blend out).

Go to basecombatweapon_shared.cpp

When you added two functions EnableIronsight and DisableIronsight. Change them to:

void CBaseCombatWeapon::EnableIronsights( void )
{
/*
#ifdef CLIENT_DLL
	if( !prediction->IsFirstTimePredicted() )
		return;
#endif*/
	if( !HasIronsights() || m_bIsIronsighted )
		return;
 
	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
 
	if( !pOwner )
		return;
 
	if( pOwner->SetFOV( this, pOwner->GetDefaultFOV() + GetIronsightFOVOffset(), 0.4f ) ) //modify the last value to adjust how fast the fov is applied
	{
		m_bIsIronsighted = true;
		SetIronsightTime();
	}
}
 
void CBaseCombatWeapon::DisableIronsights( void )
{
/*
#ifdef CLIENT_DLL
	if( !prediction->IsFirstTimePredicted() )
		return;
#endif*/ 

// We are not using prediction in singleplayer


	if( !HasIronsights() || !m_bIsIronsighted )
		return;
 
	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
 
	if( !pOwner )
		return;
 
	if( pOwner->SetFOV( this, 0, 0.2f ) ) //modify the last value to adjust how fast the fov is applied
	{
		m_bIsIronsighted = false;
		SetIronsightTime();
	}
}


--Dexter127 03:43, 23 July 2012 (PDT)

Episode 1-engine fix

In the Episode 1-engine, SetFOV is server-side only. A simple fix would be to set the FOV on the server only, but here's a client-side implementation, partially derived from OB code.

m_hZoomOwner

Set m_hZoomOwner up for networking in player.cpp/.h by using a CNetworkHandle and adding it to the send-table.

In c_baseplayer.cpp/.h, add the m_hZoomOwner to the class-declaration, so it can be received. Set it to NULL in the constructor and add it to the recv- and the prediction-table.

Also add a SetFOV-function to C_BasePlayer and define it like this:

bool C_BasePlayer::SetFOV( C_BaseEntity *pRequester, int FOV, float zoomRate )
{
	//NOTENOTE: You MUST specify who is requesting the zoom change
	assert( pRequester != NULL );
	if ( pRequester == NULL )
		return false;

	if( ( m_hZoomOwner.Get() != NULL ) && ( m_hZoomOwner.Get() != pRequester ) )
		return false;
	else
	{
		//FIXME: Maybe do this is as an accessor instead
		if ( FOV == 0 )
		{
			m_hZoomOwner = NULL;
		}
		else
		{
			m_hZoomOwner = pRequester;
		}
	}

	m_iFOV = FOV;

	m_Local.m_flFOVRate	= zoomRate;

	return true;
}

ConVarRef

ConVarRef does not exist in the Episode 1-engine. You can either use the OB tier1-library (note: untested; might not work) or instead of ConVarRef( "sv_cheats" ), put extern ConVar sv_cheats; at the top of cbasecombatweapon_shared.cpp and use the member by pointer-operator (->) instead of the member-operator (.).

For the ConVars in the callbacks, simply use the ConVars directly or cast the IConVar to a ConVar.

Adjusting the bullet spread

Open the CPP of the weapon you want to be affected by ironsights. I used the SMG for this.

weapon_smg1.cpp

Search for the GetBulletSpread-function. It'll look something like this:

virtual const Vector& GetBulletSpread( void )
	{
			static const Vector cone = VECTOR_CONE_5DEGREES;
			return cone;
	}

You can, for example, replace it with the following:

virtual const Vector& GetBulletSpread( void )
	{
		if ( m_bIsIronsighted )
		{
			static const Vector cone = VECTOR_CONE_1DEGREES;
			return cone;
		}
		else
		{
			static const Vector cone = VECTOR_CONE_5DEGREES;
			return cone;
		}
	}

Here's an example picture with a 5 degree cone on the left, and a 1 degree cone on the right.

Bullet Spread Example

Add keybind

Last but not least, add the key to the Options/Keyboard-menu:

kb_act.lst

Probably somewhere in "#Valve_Combat_Title":

 "toggle_ironsight"		"#MOD_Toggle_Ironsight" 

And don't forget to add this #MOD_Toggle_Ironsight to your resource/MOD_english.txt (and the other lanuages).

Ironsight Sounds

basecombatweapon_shared.cpp

Add

pPlayer->EmitSound( "WWIPlayer.IronSightIn" );

to EnableIronsights and

pPlayer->EmitSound( "WWIPlayer.IronSightOut" );

to DisableIronsights.

Change the strings WWIPlayer.IronSightin and WWIPlayer.IronSightOut to your own sounds and precache them.


Prevent Ironsight while reloading

While reloading, you can still use Ironsight, even if you write DisableIronsights (); in bool CBaseCombatWeapon:: DefaultReload

Of course, it will turn off, but if you press the aim button again, Ironsight will start working and the reload animation will be played from the "aiming" view. It's not very pretty.

To prevent it, in:

basecombatweapon_shared.cpp

Find function: CBaseCombatWeapon::ToggleIronsights(void)


And completely replace it with:

void CBaseCombatWeapon::ToggleIronsights(void) //No possible use Iron Sight durin reloading
{
	if (m_bInReload == true)
	{
			DisableIronsights();
	}
	else
	{
		if (m_bIsIronsighted)
			DisableIronsights();
		else
			EnableIronsights();
	}
}