Battlefield Style Hitmarker
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.
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.
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.
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)