Battlefield Style Hitmarker: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(Cleanup, code formatting (+ consistency), better article flow)
(Cleanup, better tag/template usage, added proper copyright attribution)
 
(2 intermediate revisions by 2 users not shown)
Line 2: Line 2:
We are going to re-create Battlefield-style hitmarkers for all weapons, complete with animation. This is going to be a very simple copy & paste tutorial, and there is room for contribution and tweaks.
We are going to re-create Battlefield-style hitmarkers for all weapons, complete with animation. This is going to be a very simple copy & paste tutorial, and there is room for contribution and tweaks.


{{Note|This has been tested on the 2013 and 2007 SDKs.}}
{{Note|This has been tested on the {{Source 2007|4}} and {{Source 2013|4}} SDKs.}}


== The Code ==
== The Code ==
Create the following files and place them inside your client (C:\MOD\src\game\client). Add them to the Client project in the game solution.
Create the following files and place them inside your client ({{path|<src code directory>/src/game/client/}}). Add them to the Client project in the game solution.


=== hud_hitmarker.h ===
=== hud_hitmarker.h ===
<source lang=cpp>
<syntaxhighlight lang=cpp>
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//========= Copyright Josh Marshall, All rights reserved. =============================//
//
//
// Purpose: Hit Marker
// Purpose: Hit Marker
Line 34: Line 34:
protected:
protected:
virtual void ApplySchemeSettings( vgui::IScheme *scheme );
virtual void ApplySchemeSettings( vgui::IScheme *scheme );
virtual void Paint( void );
virtual void Paint();
   
   
private:
private:
Line 41: Line 41:
CPanelAnimationVar( Color, m_HitmarkerColor, "HitMarkerColor", "255 255 255 255" );
CPanelAnimationVar( Color, m_HitmarkerColor, "HitMarkerColor", "255 255 255 255" );
};
};
</source>
</syntaxhighlight>


=== hud_hitmarker.cpp ===
=== hud_hitmarker.cpp ===
<source lang=cpp>
<syntaxhighlight lang=cpp>
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//========= Copyright Josh Marshall, All rights reserved. =============================//
//
//
// Purpose: Hit Marker
// Purpose: Hit Marker
Line 121: Line 121:
// Purpose:  
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CHudHitmarker::ShouldDraw( void )
bool CHudHitmarker::ShouldDraw()
{
{
return ( m_bHitmarkerShow && CHudElement::ShouldDraw() );
return ( m_bHitmarkerShow && CHudElement::ShouldDraw() );
Line 129: Line 129:
// Purpose:  
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHudHitmarker::Paint( void )
void CHudHitmarker::Paint()
{
{
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
Line 160: Line 160:
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HitMarkerShow" );
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HitMarkerShow" );
}
}
</source>
</syntaxhighlight>


== Implementation ==
== Implementation ==
Line 166: Line 166:


=== <base>_usermessages.cpp ===
=== <base>_usermessages.cpp ===
Depending on your mod base this will be '''hl2_usermessages.cpp''' or '''sdk_usermessages.cpp'''.
Depending on your mod base this will be {{Path|hl2_usermessages|cpp}} or {{Path|sdk_usermessages|cpp}}.


Add the following line to bottom of the file  
Add the following line to bottom of the file  
<source lang=cpp>
<syntaxhighlight lang=cpp>
usermessages->Register( "ShowHitmarker", 1 ); // Show hit marker
usermessages->Register( "ShowHitmarker", 1 ); // Show hit marker
</source>
</syntaxhighlight>


=== weapon_<name>.cpp ===
=== weapon_<name>.cpp ===
For this example I will be using the '''weapon_pistol.cpp''', you will have to add this to all weapons used in your mod. The reasoning for this in my case is not all weapons use the <code>MAX_TRACE_LENGTH</code>, and I need to set this accordingly per weapon, else you could add this in the <code>Baseclass::PrimaryAttack</code>.
For this example I will be using the {{Path|weapon_pistol|cpp}}, you will have to add this to all weapons used in your mod. The reasoning for this in my case is not all weapons use the {{Code|MAX_TRACE_LENGTH}}, and I need to set this accordingly per weapon, else you could add this in the {{Code|Baseclass::PrimaryAttack}}.


Declare the following underneath the rest of the function declarations near the top of the file.
Declare the following underneath the rest of the function declarations near the top of the file.


<source lang=cpp>
<syntaxhighlight lang=cpp>
void DrawHitmarker( void );
void DrawHitmarker();
</source>
</syntaxhighlight>


Implement the function you just declared as follows.
Implement the function you just declared as follows.


<source lang=cpp>
<syntaxhighlight lang=cpp>
void CWeaponPistol::DrawHitmarker( void )
void CWeaponPistol::DrawHitmarker()
{
{
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
Line 195: Line 195:
CSingleUserRecipientFilter filter( pPlayer );
CSingleUserRecipientFilter filter( pPlayer );
UserMessageBegin( filter, "ShowHitmarker" );
UserMessageBegin( filter, "ShowHitmarker" );
WRITE_BYTE( 1 );
WRITE_BYTE( 1 );
MessageEnd();
MessageEnd();
#endif
#endif
}
}
</source>
</syntaxhighlight>


Inside your weapon file find the <code>::PrimaryAttack</code> function and add under <code>CBasePlayer *pOwner = ToBasePlayer( GetOwner() );</code> add:
Inside your weapon file find the {{Code|::PrimaryAttack}} function and under
<source lang=cpp>
<syntaxhighlight lang=cpp>
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
</syntaxhighlight>
 
add:
<syntaxhighlight lang=cpp>
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
if ( !pPlayer )
if ( !pPlayer )
return;
return;
</source>
</syntaxhighlight>


Finally, add this snippet at the bottom of the <code>::PrimaryAttack</code> function:
Finally, add this snippet at the bottom of the {{Code|::PrimaryAttack}} function:
<source lang=cpp>
<syntaxhighlight lang=cpp>
// Set up the vectors and traceline
// Set up the vectors and traceline
trace_t tr;
trace_t tr;
Line 235: Line 240:
}
}
}
}
</source>
</syntaxhighlight>


== Animation ==
== Animation ==
Open up your '''hudanimations.txt''' file and add the following to the bottom of the file.
Open up your {{Path|hudanimations|txt}} file and add the following to the bottom of the file.


<pre>
{{CodeBlock
event HitMarkerShow
|<nowiki>event HitMarkerShow
{
{
Animate HudHitmarker Alpha 255 Linear 0.2 0.4
Animate HudHitmarker Alpha 255 Linear 0.2 0.4
Line 252: Line 257:
Animate HudHitmarker Alpha 0 Linear 0.2 0.4
Animate HudHitmarker Alpha 0 Linear 0.2 0.4
}
}
</pre>
</nowiki>}}


You can edit the values here to achieve the look and feel you like, but I feel these settings emulate Battlefield's style nicely.
You can edit the values here to achieve the look and feel you like, but I feel these settings emulate Battlefield's style nicely.
Line 258: Line 263:
== Finishing Up ==
== Finishing Up ==
=== Single Player Tweaks ===
=== Single Player Tweaks ===
For single player mods, you'll find you need to edit a line within the <code>::PrimaryAttack</code> code we added, rather than detecting players we need to detect NPCs.
For single player mods, you'll find you need to edit a line within the {{Code|::PrimaryAttack}} code we added, rather than detecting players we need to detect NPCs.


Simply change the following (or if you want both just add it):
Simply change the following (or if you want both just add it):
<source lang=cpp>
<syntaxhighlight lang=cpp>
// Check to see if we hit a player
// Check to see if we hit a player
if ( tr.m_pEnt )
if ( tr.m_pEnt )
Line 270: Line 275:
}
}
}
}
</source>
</syntaxhighlight>


to this:
to this:
<source lang=cpp>
<syntaxhighlight lang=cpp>
// Check to see if we hit an NPC
// Check to see if we hit an NPC
if ( tr.m_pEnt )
if ( tr.m_pEnt )
Line 280: Line 285:
{
{
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
// Light Kill: Draw ONLY if we hit an enemy NPC
// Note(Light Kill): Draw ONLY if we hit an enemy NPC
if ( pPlayer->GetDefaultRelationshipDisposition( tr.m_pEnt->Classify() ) != D_HT )
if ( pPlayer->GetDefaultRelationshipDisposition( tr.m_pEnt->Classify() ) != D_HT )
{
{
Line 292: Line 297:
}
}
}
}
</source>
</syntaxhighlight>


In single player the hitmarker will draw only if we hit an ENEMY NPC, it will ignore NPCs who change their behavior against the player.
In single player the hitmarker will draw only if we hit an '''ENEMY''' NPC, it will ignore NPCs who change their behavior against the player.


'''Example:''' We have custom NPC who are friendly at the start, if you make him hate the player by the script the hitmarker will not show when you kill 'em. Works great on default HL2 NPCs.
{{Example|We have custom NPC who are friendly at the start, if you make him hate the player by the script the hitmarker will not show when you kill 'em. Works great on default HL2 NPCs.}}


== Conclusion ==
== Conclusion ==
So there we go, a very basic but fully functional Battlefield-style hitmaker.
So there we go, a very basic but fully functional Battlefield-style hitmaker.


There is a lot of room for tweaking, for instance, this will currently show when you hit any players/NPCs regardless of their Team or AI Relationship. You could think about adding in functionality to differentiate between friendly and enemy players/NPCs and display different properties accordingly.
There is a lot of room for tweaking, for instance, this will currently show when you hit any players/NPCs regardless of their Team or AI Relationship. You could think about adding in functionality to differentiate between friendly and enemy players/NPCs and display different properties accordingly.
Line 307: Line 311:
Enjoy,
Enjoy,


Ferrety  
Josh "[[User:Joshy1002|Ferrety]]" Marshall ([mailto:ferrety6012@gmail.com ferrety6012@gmail.com])
(ferrety6012@gmail.com)
 
== See also ==
* [[Understanding VGUI2 Animation]]
 
[[Category:Programming]]
[[Category:Tutorials]]
[[Category:Free source code]]

Latest revision as of 15:44, 18 October 2025

The Goal

We are going to re-create Battlefield-style hitmarkers for all weapons, complete with animation. This is going to be a very simple copy & paste tutorial, and there is room for contribution and tweaks.

Note.pngNote:This has been tested on the Source 2007 Source 2007 and Source 2013 Source 2013 SDKs.

The Code

Create the following files and place them inside your client (🖿<src code directory>/src/game/client/). Add them to the Client project in the game solution.

hud_hitmarker.h

//========= Copyright Josh Marshall, All rights reserved. =============================//
//
// Purpose: Hit Marker
//
//=============================================================================//

#include "hudelement.h"
#include "hud_numericdisplay.h"
#include <vgui_controls/Panel.h>
 
class CHudHitmarker : public vgui::Panel, public CHudElement
{
	DECLARE_CLASS_SIMPLE( CHudHitmarker, vgui::Panel );
 
public:
	CHudHitmarker( const char *pElementName );
 
	void Init();
	void Reset();
	bool ShouldDraw();

	void MsgFunc_ShowHitmarker( bf_read &msg );
 
protected:
	virtual void ApplySchemeSettings( vgui::IScheme *scheme );
	virtual void Paint();
 
private:
	bool m_bHitmarkerShow;

	CPanelAnimationVar( Color, m_HitmarkerColor, "HitMarkerColor", "255 255 255 255" );
};

hud_hitmarker.cpp

//========= Copyright Josh Marshall, All rights reserved. =============================//
//
// Purpose: Hit Marker
//
//=============================================================================//

#include "cbase.h"
#include "hudelement.h"
#include "hud_macros.h"
#include "hud_hitmarker.h"
#include "iclientmode.h"
#include "c_baseplayer.h"

// VGUI panel includes
#include <vgui_controls/AnimationController.h>
#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>

using namespace vgui;
 
// memdbgon must be the last include file in a .cpp file!
#include "tier0/memdbgon.h"
 
DECLARE_HUDELEMENT( CHudHitmarker );
DECLARE_HUD_MESSAGE( CHudHitmarker, ShowHitmarker );

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CHudHitmarker::CHudHitmarker( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "HudHitmarker" )
{
	vgui::Panel *pParent = g_pClientMode->GetViewport();
	SetParent( pParent );
 
	// Hitmarker will not show when the player is dead
	SetHiddenBits( HIDEHUD_PLAYERDEAD );
 
	int screenWide, screenTall;
	GetHudSize( screenWide, screenTall );
	SetBounds( 0, 0, screenWide, screenTall );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudHitmarker::Init()
{
	HOOK_HUD_MESSAGE( CHudHitmarker, ShowHitmarker );

	SetAlpha( 0 );
	m_bHitmarkerShow = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudHitmarker::Reset()
{
	SetAlpha( 0 );
	m_bHitmarkerShow = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudHitmarker::ApplySchemeSettings( vgui::IScheme *scheme )
{
	BaseClass::ApplySchemeSettings( scheme );
 
	SetPaintBackgroundEnabled( false );
	SetPaintBorderEnabled( false );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CHudHitmarker::ShouldDraw()
{
	return ( m_bHitmarkerShow && CHudElement::ShouldDraw() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudHitmarker::Paint()
{
	C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
	if ( !pPlayer )
		return;

	if ( m_bHitmarkerShow )
	{
		int x, y;

		// Find our screen position to start from
		x = XRES( 320 );
		y = YRES( 240 );

		vgui::surface()->DrawSetColor( m_HitmarkerColor );
		vgui::surface()->DrawLine( x - 6, y - 5, x - 11, y - 10 );
		vgui::surface()->DrawLine( x + 5, y - 5, x + 10, y - 10 );
		vgui::surface()->DrawLine( x - 6, y + 5, x - 11, y + 10 );
		vgui::surface()->DrawLine( x + 5, y + 5, x + 10, y + 10 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudHitmarker::MsgFunc_ShowHitmarker( bf_read &msg )
{
	m_bHitmarkerShow = msg.ReadByte();

	g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HitMarkerShow" );
}

Implementation

Once compiled, we now have the hud element created. The next step is to call it in the code, for this we will add a usermessage that gets called when a weapon fires and successfully hits an enemy.

<base>_usermessages.cpp

Depending on your mod base this will be 🖿hl2_usermessages.cpp or 🖿sdk_usermessages.cpp.

Add the following line to bottom of the file

	usermessages->Register( "ShowHitmarker", 1 ); // Show hit marker

weapon_<name>.cpp

For this example I will be using the 🖿weapon_pistol.cpp, you will have to add this to all weapons used in your mod. The reasoning for this in my case is not all weapons use the MAX_TRACE_LENGTH, and I need to set this accordingly per weapon, else you could add this in the Baseclass::PrimaryAttack.

Declare the following underneath the rest of the function declarations near the top of the file.

	void	DrawHitmarker();

Implement the function you just declared as follows.

void CWeaponPistol::DrawHitmarker()
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if ( pPlayer == NULL )
		return;

#ifndef CLIENT_DLL
	CSingleUserRecipientFilter filter( pPlayer );
	UserMessageBegin( filter, "ShowHitmarker" );
		WRITE_BYTE( 1 );
	MessageEnd();
#endif
}

Inside your weapon file find the ::PrimaryAttack function and under

CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

add:

	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( !pPlayer )
		return;

Finally, add this snippet at the bottom of the ::PrimaryAttack function:

	// Set up the vectors and traceline
	trace_t tr;
	Vector vecStart, vecStop, vecDir;

	// Get the angles
	AngleVectors( pPlayer->EyeAngles(), &vecDir );

	// Get the vectors
	vecStart = pPlayer->Weapon_ShootPosition();
	vecStop = vecStart + vecDir * MAX_TRACE_LENGTH;

	// Do the TraceLine
	UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr );

	// Check to see if we hit a player
	if ( tr.m_pEnt )
	{
		if ( tr.m_pEnt->GetTeamNumber() != pPlayer->GetTeamNumber() )
		{
			if ( tr.m_pEnt->IsPlayer() )
			{
				DrawHitmarker();
			}
		}
	}

Animation

Open up your 🖿hudanimations.txt file and add the following to the bottom of the file.

event HitMarkerShow { Animate HudHitmarker Alpha 255 Linear 0.2 0.4 RunEvent HitMarkerHide 0.4 } event HitMarkerHide { Animate HudHitmarker Alpha 0 Linear 0.2 0.4 }

You can edit the values here to achieve the look and feel you like, but I feel these settings emulate Battlefield's style nicely.

Finishing Up

Single Player Tweaks

For single player mods, you'll find you need to edit a line within the ::PrimaryAttack code we added, rather than detecting players we need to detect NPCs.

Simply change the following (or if you want both just add it):

	// Check to see if we hit a player
	if ( tr.m_pEnt )
	{
		if ( tr.m_pEnt->IsPlayer() )
		{
			DrawHitmarker();
		}
	}

to this:

	// Check to see if we hit an NPC
	if ( tr.m_pEnt )
	{
		if ( tr.m_pEnt->IsNPC() )
		{
		#ifndef CLIENT_DLL
			// Note(Light Kill): Draw ONLY if we hit an enemy NPC
			if ( pPlayer->GetDefaultRelationshipDisposition( tr.m_pEnt->Classify() ) != D_HT )
			{
				//DevMsg( "Neutral NPC!\n" );
			}
			else
			{
				DrawHitmarker();
			}
		#endif
		}
	}

In single player the hitmarker will draw only if we hit an ENEMY NPC, it will ignore NPCs who change their behavior against the player.

PlacementTip.pngExample:We have custom NPC who are friendly at the start, if you make him hate the player by the script the hitmarker will not show when you kill 'em. Works great on default HL2 NPCs.

Conclusion

So there we go, a very basic but fully functional Battlefield-style hitmaker.

There is a lot of room for tweaking, for instance, this will currently show when you hit any players/NPCs regardless of their Team or AI Relationship. You could think about adding in functionality to differentiate between friendly and enemy players/NPCs and display different properties accordingly.


Enjoy,

Josh "Ferrety" Marshall (ferrety6012@gmail.com)

See also