Adding a Scope: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
(De-POV-ified (also please note contributors: tutorials should not be signed))
Line 1: Line 1:
{{tutpov}}
The code and ideas in this tutorial can be used to add a HUD-based sniper scope to your game, for use by any weapon that you see fit. As an example, it has been added alongside the existing zoom functionality of the HL2 crossbow. However, it is possible to tell it to display from any part of your code.
 
You can use the code and ideas in this tutorial to add a HUD-based sniper scope to your game, for use by any weapon that you see fit :) As an example, I've added it alongside the existing zoom functionality of the HL2 crossbow, but as you'll see, you can tell it to display from any part of your code.


==Prerequisites==
==Prerequisites==
The only prerequisite for this tutorial is that you have already created a sniper scope texture (e.g. a big black square/rectangle with a transparent circle in the middle of it) which has an appropriate .VMT file. Here's one I made earlier:
The only prerequisite for this tutorial is that you have already created a sniper scope texture (e.g. a big black square/rectangle with a transparent circle in the middle of it) which has an appropriate .VMT file. Here is an example texture:


[http://www.fine-art.pwp.blueyonder.co.uk/scope.vtf scope.vtf]
[http://www.fine-art.pwp.blueyonder.co.uk/scope.vtf scope.vtf]
Line 10: Line 8:
[http://www.fine-art.pwp.blueyonder.co.uk/scope.vmt scope.vmt]
[http://www.fine-art.pwp.blueyonder.co.uk/scope.vmt scope.vmt]


...you also need to setup this material to be loaded as a hud texture; do this by adding the following to ''scripts/hud_textures.txt''
This material must be setup to be loaded as a hud texture. Do this by adding the following to ''scripts/hud_textures.txt'':
 
<pre>
<pre>
"sprites/640_hud"
"sprites/640_hud"
Line 174: Line 173:


==Telling the HUD Element When to Show==
==Telling the HUD Element When to Show==
For this, I used a HUD (aka User) message which I called '''ShowScope'''. To enable this, add the following line to your user messages registration code (e.g. hl2_usermessages.cpp or similar), in the '''RegisterUserMessages()''' function:
For this, a HUD (aka User) message was used, which was called '''ShowScope'''. To enable this, add the following line to your user messages registration code (e.g. hl2_usermessages.cpp or similar), in the '''RegisterUserMessages()''' function:
 
<pre>
<pre>
usermessages->Register( "ShowScope", 1); // show the sniper scope
usermessages->Register( "ShowScope", 1); // show the sniper scope
</pre>
</pre>
Then all you need to do is fire off this message with either a true or false value (1 or 0) to show or hide the scope. For example, change the '''CWeaponCrossbow::ToggleZoom()''' function to the following:
Then all you need to do is fire off this message with either a true or false value (1 or 0) to show or hide the scope. For example, change the '''CWeaponCrossbow::ToggleZoom()''' function to the following:
<pre>
<pre>
void CWeaponCrossbow::ToggleZoom( void )
void CWeaponCrossbow::ToggleZoom( void )
Line 217: Line 219:


== Making a Weapon Zoom ==
== Making a Weapon Zoom ==
I got a request to add a section about actually adding zoom functionality to a weapon in the first place. Here's what you need to do - most of this code is lifted straight from CWeaponCrossbow. For the purpose of this tutorial, I'm adding zoom to the .357 Magnum as a secondary fire (like the crossbow). If you want to implement it in a weapon which already uses the IN_ATTACK2 key to perform a secondary attack, you'll need to define and use a separate key for this.
This section deails with actually adding zoom functionality to a weapon in the first place. Here's what you need to do - most of this code is lifted straight from CWeaponCrossbow. For the purpose of this tutorial, zoom is being added to the .357 Magnum as a secondary fire (like the crossbow). If you want to implement it in a weapon which already uses the IN_ATTACK2 key to perform a secondary attack, you'll need to define and use a separate key for this.
 
To a very basic weapon like the .357, you need to add one boolean field and 5 new functions to acheive the zoom function like the crossbow. Bear in mind that many weapons (like the pistol) already define one or more of these functions, and all you need to do is add to it.


To a very basic weapon like the .357, you need to add one boolean field and 5 new functions to acheive the zoom function like the crossbow. Bare in mind that many weapons (like the pistol) already define one or more of these functions, and all you need to do is add to it.
<pre>
<pre>
public:
public:
Line 234: Line 237:


You would then add the following definitions for those functions (again, just add the relevant code to functions if they already exist):
You would then add the following definitions for those functions (again, just add the relevant code to functions if they already exist):
<pre>
<pre>
/**
/**
Line 327: Line 331:
</pre>
</pre>


''Andy aka zoolicious''
=Alternative=
 
----


An alternative way to add a scope (in HL2) is to allow firing while zooming. Whilst it applies to all weapons, it is far easier to implement.
An alternative way to add a scope (in HL2) is to allow firing while zooming. Whilst it applies to all weapons, it is far easier to implement.


In HL2_Player.cpp comment out the following block of code (line 523):
In HL2_Player.cpp comment out the following block of code (line 523):
<pre>
<pre>
if ( m_nButtons & IN_ZOOM )
if ( m_nButtons & IN_ZOOM )
Line 341: Line 344:
}
}
</pre>
</pre>
''Jeremy aka Jeremy.006''
 
[[Category:Programming]]
[[Category:Programming]]
[[Category:Tutorials]]
[[Category:Tutorials]]

Revision as of 03:08, 10 April 2006

The code and ideas in this tutorial can be used to add a HUD-based sniper scope to your game, for use by any weapon that you see fit. As an example, it has been added alongside the existing zoom functionality of the HL2 crossbow. However, it is possible to tell it to display from any part of your code.

Prerequisites

The only prerequisite for this tutorial is that you have already created a sniper scope texture (e.g. a big black square/rectangle with a transparent circle in the middle of it) which has an appropriate .VMT file. Here is an example texture:

scope.vtf

scope.vmt

This material must be setup to be loaded as a hud texture. Do this by adding the following to scripts/hud_textures.txt:

"sprites/640_hud"
{
   TextureData
   {
      "scope"
      {
	  "file"	"scope"
	  "x"		"0"
	  "y"		"0"
	  "width"	"512"
	  "height"	"512"
      }

      ...
   }
}

The HUD Element

This element displays your scope texture on the HUD. Just add the following code to hud_scope.cpp, which you should create somewhere in your game's client project.

#include "cbase.h"
#include "hudelement.h"
#include "hud_macros.h"
#include "iclientmode.h"
#include "c_basehlplayer.h"

#include <vgui/IScheme.h>
#include <vgui_controls/Panel.h>

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


/**
 * Simple HUD element for displaying a sniper scope on screen
 */
class CHudScope : public vgui::Panel, public CHudElement
{
	DECLARE_CLASS_SIMPLE( CHudScope, vgui::Panel );

public:
	CHudScope( const char *pElementName );

	void Init();
	void MsgFunc_ShowScope( bf_read &msg );

protected:
	virtual void ApplySchemeSettings(vgui::IScheme *scheme);
	virtual void Paint( void );

private:
	bool			m_bShow;
    CHudTexture*	m_pScope;
};


DECLARE_HUDELEMENT( CHudScope );
DECLARE_HUD_MESSAGE( CHudScope, ShowScope );

using namespace vgui;


/**
 * Constructor - generic HUD element initialization stuff. Make sure our 2 member variables
 * are instantiated.
 */
CHudScope::CHudScope( const char *pElementName ) : CHudElement(pElementName), BaseClass(NULL, "HudScope")
{
	vgui::Panel *pParent = g_pClientMode->GetViewport();
	SetParent( pParent );

	m_bShow = false;
	m_pScope = 0;

	// Scope will not show when the player is dead
	SetHiddenBits( HIDEHUD_PLAYERDEAD );
}


/**
 * Hook up our hud message, and make sure we are not showing the scope
 */
void CHudScope::Init()
{
	HOOK_HUD_MESSAGE( CHudScope, ShowScope );

	m_bShow = false;
}


/**
 * Load  in the scope material here
 */
void CHudScope::ApplySchemeSettings( vgui::IScheme *scheme )
{
	BaseClass::ApplySchemeSettings(scheme);

	SetPaintBackgroundEnabled(false);
	SetPaintBorderEnabled(false);

	if (!m_pScope)
	{
		m_pScope = gHUD.GetIcon("scope");
	}
}


/**
 * Simple - if we want to show the scope, draw it. Otherwise don't.
 */
void CHudScope::Paint( void )
{
	if (m_bShow)
	{
		// This will draw the scope at the origin of this HUD element, and
		// stretch it to the width and height of the element. As long as the
		// HUD element is set up to cover the entire screen, so will the scope
		m_pScope->DrawSelf(0, 0, GetWide(), GetTall(), Color(255,255,255,255));

		// Hide the crosshair
		if (g_pLocalPlayer)
		{
			g_pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_CROSSHAIR;
		}
	}
	else if ((g_pLocalPlayer->m_Local.m_iHideHUD & HIDEHUD_CROSSHAIR) != 0)
	{
		g_pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_CROSSHAIR;
	}
}



/**
 * Callback for our message - set the show variable to whatever
 * boolean value is received in the message
 */
void CHudScope::MsgFunc_ShowScope(bf_read &msg)
{
	m_bShow = msg.ReadByte();
}

Then you need to declare your HUD element in scripts/HudLayout.res with the following lines:

HudScope
{
	"fieldName"	"HudScope"
	"xpos"	        "0"
	"ypos"	        "0"
	"wide"	        "640"
	"tall"          "480"
	"visible"       "1"
	"enabled"       "1"
	"PaintBackgroundType"	"0"
}

This will make the element cover the entire screen.

Telling the HUD Element When to Show

For this, a HUD (aka User) message was used, which was called ShowScope. To enable this, add the following line to your user messages registration code (e.g. hl2_usermessages.cpp or similar), in the RegisterUserMessages() function:

usermessages->Register( "ShowScope", 1); // show the sniper scope

Then all you need to do is fire off this message with either a true or false value (1 or 0) to show or hide the scope. For example, change the CWeaponCrossbow::ToggleZoom() function to the following:

void CWeaponCrossbow::ToggleZoom( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	
	if ( pPlayer == NULL )
		return;

	if ( m_bInZoom )
	{
		if ( pPlayer->SetFOV( this, 0, 0.2f ) )
		{
			m_bInZoom = false;

			// Send a message to hide the scope
			CSingleUserRecipientFilter filter(pPlayer);
			UserMessageBegin(filter, "ShowScope");
				WRITE_BYTE(0);
			MessageEnd();
		}
	}
	else
	{
		if ( pPlayer->SetFOV( this, 20, 0.1f ) )
		{
			m_bInZoom = true;

			// Send a message to Show the scope
			CSingleUserRecipientFilter filter(pPlayer);
			UserMessageBegin(filter, "ShowScope");
				WRITE_BYTE(1);
			MessageEnd();
		}
	}
}

Making a Weapon Zoom

This section deails with actually adding zoom functionality to a weapon in the first place. Here's what you need to do - most of this code is lifted straight from CWeaponCrossbow. For the purpose of this tutorial, zoom is being added to the .357 Magnum as a secondary fire (like the crossbow). If you want to implement it in a weapon which already uses the IN_ATTACK2 key to perform a secondary attack, you'll need to define and use a separate key for this.

To a very basic weapon like the .357, you need to add one boolean field and 5 new functions to acheive the zoom function like the crossbow. Bear in mind that many weapons (like the pistol) already define one or more of these functions, and all you need to do is add to it.

public:
    bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );  // Required so that you know to un-zoom when switching weapons
    void ItemPostFrame( void );                              // Called every frame during normal weapon idle
    void ItemBusyFrame( void );                              // Called when the weapon is 'busy' e.g. reloading

protected:
    void ToggleZoom( void );                                 // If the weapon is zoomed, un-zoom and vice versa
    void CheckZoomToggle( void );                            // Check if the secondary attack button has been pressed

    bool m_bInZoom;                                          // Set to true when you are zooming, false when not

You would then add the following definitions for those functions (again, just add the relevant code to functions if they already exist):

/**
 * Check for weapon being holstered so we can disable scope zoom
 */
bool CWeapon357::Holster(CBaseCombatWeapon *pSwitchingTo /* = NULL  */)
{
	if ( m_bInZoom )
	{
		ToggleZoom();
	}

	return BaseClass::Holster( pSwitchingTo );
}


/**
 * Check the status of the zoom key every frame to see if player is still zoomed in
 */
void CWeapon357::ItemPostFrame()
{
	// Allow zoom toggling
	CheckZoomToggle();

	BaseClass::ItemPostFrame();
}


/**
* Check the status of the zoom key every frame to see if player is still zoomed in
*/
void CWeapon357::ItemBusyFrame( void )
{
	// Allow zoom toggling even when we're reloading
	CheckZoomToggle();

	BaseClass::ItemBusyFrame();
}


/**
 * Check if the zoom key was pressed in the last input tick
 */
void CWeapon357::CheckZoomToggle( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if ( pPlayer && (pPlayer->m_afButtonPressed & IN_ATTACK2))
	{
		ToggleZoom();
	}
}


/**
 * If we're zooming, stop. If we're not, start.
 */
void CWeapon357::ToggleZoom( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if ( pPlayer == NULL )
		return;

	if ( m_bInZoom )
	{
                // Narrowing the Field Of View here is what gives us the zoomed effect
		if ( pPlayer->SetFOV( this, 0, 0.2f ) )
		{
			m_bInZoom = false;

			// Send a message to hide the scope
			CSingleUserRecipientFilter filter(pPlayer);
			UserMessageBegin(filter, "ShowScope");
			WRITE_BYTE(0);
			MessageEnd();
		}
	}
	else
	{
		if ( pPlayer->SetFOV( this, 20, 0.1f ) )
		{
			m_bInZoom = true;

			// Send a message to Show the scope
			CSingleUserRecipientFilter filter(pPlayer);
			UserMessageBegin(filter, "ShowScope");
			WRITE_BYTE(1);
			MessageEnd();
		}
	}
}

Alternative

An alternative way to add a scope (in HL2) is to allow firing while zooming. Whilst it applies to all weapons, it is far easier to implement.

In HL2_Player.cpp comment out the following block of code (line 523):

if ( m_nButtons & IN_ZOOM )
	{
		//FIXME: Held weapons like the grenade get sad when this happens
		m_nButtons &= ~(IN_ATTACK|IN_ATTACK2);
	}