Battlefield Style Hitmarker

From Valve Developer Community
Jump to: navigation, search
Note:This has been tested on the 2013 and 2007 SDK's.

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 (C:\MOD\src\game\client). Add them to the Client project in the game solution.

HUD_HITMARKER.H

//========= Copyright © 1996-2005, Valve Corporation, 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( void );
 
private:
	bool m_bHitmarkerShow;

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

HUD_HITMARKER.CPP

//========= Copyright © 1996-2005, Valve Corporation, 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( void )
{
	return ( m_bHitmarkerShow && CHudElement::ShouldDraw() );
}
 
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudHitmarker::Paint( void )
{
        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 which gets called when a weapon fires and sucessfully hits an enemy.

X_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_X.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( void );

Implement the function you just declared as follows.

void CWeaponPistol::DrawHitmarker( void )
{
	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 add 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 NPC's.

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

	// 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		// Light Kill : Draw ONLY if we hit enemy
				if ( pPlayer->GetDefaultRelationshipDisposition(tr.m_pEnt->Classify()) != D_HT )
				{
					//DevMsg("Neitral npc ! \n");
				}
				else
				DrawHitmarker();
				#endif
			}
		}

So there we go, a very basic but fully functional Battlefield Style Hitmarker.

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

Enjoy,

Ferrety (ferrety6012@gmail.com)


Addition from Light Kill: (SP) Hitmarker will draw only if we hit ENEMY npc, it will ignore npc who change their behavior against player.

Example: We have custon npc who are friendly at the start, if you make him hate player by the script the hitmarker will not shown when you kill 'em. Works great on default HL2 npc's.