HUD Elements

From Valve Developer Community
Jump to: navigation, search
English (en)
Edit
Underlinked - Logo.png
This article needs more links to other articles to help integrate it into the encyclopedia. Please help improve this article by adding links that are relevant to the context within the existing text.
January 2024

In games, it is often important to broadcast information to the player through the use of a HUD (head-up display). This often consists the player's health, the amount of ammunition they are carrying, or a message about an objective. Many different elements normally comprise this HUD. This article will explain how to create those elements and use them to display information to the user.

HUD elements usually use the VGUI2 library (except in Counter-Strike: Global Offensive Counter-Strike: Global Offensive, and some third-party games like Black Mesa Black Mesa) to render their state. This allows them to not only look and feel like the other VGUI elements within the game, but also allows them to use scripted, animated components, greatly increasing their visual quality. This requires them to have both code components declared on the client, and script files residing on the client as well, external to the code.

HUD element classes should descend from the CHudElement base class. This base class deals with updating, drawing, as well as hiding HUD elements based on certain game state (a HUD element can be set to disappear when the player dies and so on). This class also interprets the HudLayout.res file in the /scripts directory of the game to determine the positions and behaviors of all the elements. Most elements will descend from the vgui::Panel base class. This provides them with a rudimentary canvas on which to draw text, shapes or textures. For more information on the vgui::Panel class, see the VGUI2 documentation provided in this SDK.

HUD Messages

Elements use a simple system for capturing messages sent from the server. A HUD element declares a callback function and the corresponding message to link it to. When a message is received, the callback function generally reads some piece of information out of the message and displays or otherwise updates its visual state. An example of this is the Damage message sent by the server when a player takes damage. The message is encoded with the amount of damage, as well as the location. The Health HUD element receives the message and plays animations as well as alters its numerical read-out to reflect the new health of the player.

Message handlers are declared by the HUD element via the following macro:

DECLARE_HUD_MESSAGE( CMyHUDClass, MyHUDMessage );

The first parameter specifies the class declaring the message handler, the second is the message in question. The macro expands the message's name into a callback function which must be defined in the HUD element class. In this case, the callback function created would be:

void MsgFunc_MyHUDMessage( bf_read &msg );

This function's prototype and body must be filled out using the above declaration. The bf_read class is a data buffer with various query methods. It is used to transmit unformatted data between the server and client. The server uses the bf_write class to send this data.

The HUD element must also include the HOOK_HUD_MESSAGE macro definition, generally placed within the Init() function of the HUD element. Extending our above example, the definition would be:

HOOK_HUD_MESSAGE( CMyHUDClass, MyHUDMessage );

This macro registers the message and links it to the callback function we defined earlier. Failure to include this declaration will result in an assertion and failure when a user message is sent from the server.

Sending Messages From the Server

For messages to be properly received by the client, they must be declared and sent from the server. This is accomplished via the Register() function in the usermessages singleton.

void CUserMessages::Register( const char *name, int size )
This function creates a message definition and retains it for the life of the session. When a message is sent, the name specified here is used to identify the message as it is sent to the client.

name

  • Text identifier matching the identifier declared via the DECLARE_HUD_MESSAGE for a HUD element.

size

  • Size in bytes the message intends to send.
    Note.pngNote:A value of -1 implies the size is variable or unknown

All user messages should be declared in the RegisterUserMessages() global function. This function is called upon the instantiation of the usermessage singleton. User messages not declared will not be properly received by the client and will produce an error on attempting to do so.

If all the steps above are followed, the HUD element should now have a working infrastructure for sending and receiving messages to and from the server and client. To send a message, we use the UserMessageBegin(), MessageEnd(), and supporting macros listed below.

The UserMessageBegin() function is defined as:

void UserMessageBegin( IRecipientFilter& filter, const char *messagename )
This function constructs a user message of the given type (by name), and prepares it for accepting data from the user. The filter can be of any IRecipientFilter type (CSingleUserRecipientFilter, CBroadcastRecipientFilter, etc) as defined in ../dlls/recipientfilter.h.

filter

  • Filter used to send message to proper recipients.

messagename

  • Identifying name for this message (as registered previously).

The following macros provide functionality for writing data into the stream sent to the client. They must be received and processed in the order they were sent. The macros are written as follows:

...

WRITE_BYTE( m_uchMyByte );
WRITE_VEC3COORD( m_vecMyOrigin );
WRITE_BOOL( m_bMyState );

...

The following is a description of all available macros for writing data the message stream:

WRITE_BYTE One byte
WRITE_CHAR One character
WRITE_SHORT One short
WRITE_WORD One word
WRITE_LONG One long
WRITE_FLOAT One float
WRITE_ANGLE Unsigned 8-bit angle
WRITE_COORD Compressed coordinate value
WRITE_VEC3COORD Compressed coordinate value from Vector type
WRITE_VEC3NORMAL Compressed normal value from Vector type
WRITE_ANGLES Compressed angles value from Vector type
WRITE_STRING Character string
WRITE_ENTITY Entity index (short)
WRITE_BOOL One bit boolean value
WRITE_UBITLONG Unsigned bit long value
WRITE_SBITLONG Signed bit long value
WRITE_BITS Number of bit values, as specified via parameter

Following the WRITE_ macros, the message must be terminated and sent via the MessageEnd() function.

Receiving Messages on the Client

Once a message has been sent by the server, the client will receive it via the callback function hooked to that message. The receiving callback function is passed a bf_read class instance which contains the data passed from the server. The class contains utility functions to read formatted data out of the stream. Again, the data must be read in the order it was sent.

Now that data is sent and received between the server and client, the painting functions (as described in the VGUI2 document) may be used to draw any information it wishes based on that data.

To catch these messages on the client side, they must be hooked, adding a message hook requires that the instance of its class won't get destroyed.

 class CHudThingy : public CHudElement , public vgui::Panel
 {
 
 	DECLARE_CLASS_SIMPLE( CHudThingy, vgui::Panel ); // THIS IS IMPORTANT
 	....
 	void	MsgFunc_SayText(bf_read &msg) { /* Do something with the message */ };
 }
 
 DECLARE_HUD_MESSAGE( CHudThingy, SayText );
 // Inits the advanced chat
 void CHudAdvancedChat::Init( void )
 {
 	...
 	HOOK_HUD_MESSAGE( CHudThingy, SayText );
 }

Example Code

An example HUD element can be found in the following files, included with the sample application:

../cl_dll/sdk/sdk_hud_message.cpp

../dlls/sdk/sdk_env_message.cpp

../game_shared/sdk/sdk_usermessages.cpp

Showing User Messages In game(code- server plugin)

In this case this function can be added to the project, this is fully commented.

void YourPlugin::SayTextMsg(int PlayerIndexN, const char *Message)
{
	MRecipientFilter filter;
	if(PlayerIndexN == 0)
	{
		filter.AddAllPlayers(MaxClients); // We grab the maxclients at the ServerActivate Void
	}
	else
	{
		filter.AddRecipient(PlayerIndexN); // Adds the player
	}

	bf_write *pWrite=engine->UserMessageBegin(&filter, 3); // 3 for Say_Text

	if( !pWrite )
	{
		//TODO: Action to perform when something goes wrong
	}
	else
	{
		pWrite->WriteByte(PlayerIndexN); // Players index, to send a global message from the server make it 0
		pWrite->WriteString(Message); //the message itself
		pWrite->WriteByte(0); //0 to phrase for colour 1 to ignore it
		engine->MessageEnd(); //finish off						
	}
}

Usage:

void YourPlugin::ClientCommand(edict_t *pEntity)
{
	const char *pcmd = m_Engine->Cmd_Argv(0); 
	if ( !pEntity || pEntity->IsFree() )
		return; // Continue only if the entity exists
	if ( FStrEq( pcmd, "ClientMsg" ) )
		SayTextMsg( engine->IndexOfEdict(pEntity), "Hello World!"); // Only the person who typed the command sees this!
	else if ( FStrEq( pcmd, "SayServer" ) )
		SayTextMsg( 0, "Hello World!"); // Everyone in game sees this:)

...

The RecipientFilter would have to be modified, please look here for more information.