Wavelength Logo
tl.jpg (2351 bytes) blank.gif (837 bytes) tr.jpg (2446 bytes)
blank.gif (837 bytes)
HUD Graphics for Half-Life by 2
blank.gif (837 bytes)


 

This tutorial almost completely explains how to do HUD graphics. Once you understand this tutorial, you should be able to understand almost anything HUD-related in Half-Life. It also touches on messages (enough to do HUD graphics). I'd like to thank Fireball now for helping me figure out messages. They can be devils if you don't know how to work them. Also, this is a fairly long tutorial, so don't die or anything while reading it. =) As I said, it nearly completely describes HUD graphics. Meaning, there may be a thing or two I don't say, and I don't want to be held accountable for it, so I say nearly. But believe me, this tutorial should have enough to answer almost any question you have. I also don't explain exactly HOW to do anything. Just the code you would use to implement what you want to do (ex: I don't tell you HOW to make a sprite-based bar that raises and falls with a number). My goal with this file is to allow you to easily know how to make those things you want to. All I assume is that you know basic C++, for your own logic. You could practically (and I know some of you will) copy my code here, and use it in your own little Mods. But if you could include my name, 2, in a readme, it would be very nice and greatly appriciated. I guess I'll start now (I typed this after I finnished). Thanks.

File Name Old Code New Code Comments

Ok, this is my first tutorial, so bear with me. I'm going to explain how to do HUD stuff. It is nowhere near as hard as you people probably think, so I'll try to keep it nice and simple. So, I guess to start, I'll have to tell you how the HUD system works. Half-Life I believe is actually three programs running simultaniously. The first is the "engine." This is what draws the pictures and stuff on your computer (I'm not 100% sure about this one, but it sounds good, and explains a lot: Occam's razor proves it true). The next is pretty much the game. This would probably be the file you all are editing, the mp.dll. This is the only serverside program, so the more that is here the slower a multiplayer game runs. The final program is the "client" file. This is where the HUD is implemented. So, many of you are now probably asking "well how do the different programs communicate?" Well, they talk to eachother via these conveinient little READ and WRITE functions. The first step to adding a new HUD feature (determined by some variable from the server dll (mp.dll)) is to WRITE a message to the client's dll. So... here is my first section of code...

mp.dll

player.cpp (around line 156)

int gmsgSayText = 0;

int gmsgTextMsg = 0;

int gmsgSetFOV = 0;

int gmsgShowMenu = 0;

//There are a bunch of those (like 30 or so).

int gmsgYourMessage = 0;

//gmsgYourMessage would be replaced with whatever you want it to be.

//Just make sure you put this IN FRONT of the...

//LINK_ENTITY_TO_CLASS( player, CBasePlayer );

This new integer will be your message that you send to the client dll's. Now you must register it as a message...

mp.dll

player.cpp (around line 3206)

gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 );

gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake));

gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade));

gmsgAmmoX = REG_USER_MSG("AmmoX", 2);

//Again, there are a bunch of those.

gmsgYourMessage = REG_USER_MSG( "MessageName", MessageSize );

//gmsgYourMessage would be replaced with whatever you named it above

//MessageName would be replaced with whatever you want the name of the data flowing back and forth between files is going to be.

//Do no pick somthing already used.

//MessageSize will be covered soon (it'll make more sense this way).

Okay, now comes the part that is up to you. This is where you WRITE the message. I can't show you where to put it, because it depends on what it's for. Normally, though, it is found in the CBasePlayer class somewhere. I'll explain how to do it now.

MESSAGE_BEGIN( MSG_ONE, gmsgYourMessage, NULL, pev );

//Okay, the first parameter, MSG_ONE, I believe is what "carries" the message. There are a few other kinds, but for HUD graphics you use this.

//gmsgYourMessage again is from above.

//The next parameter is always NULL when doing HUD graphics.

//The final would be the pev of the player you are dealing with (if it's not in a function within CBasePlayer, you have to use a pointer to the pev, ex: m_pPlayer->pev).

WRITE_BYTE( 0 );

WRITE_SHORT( 0 );

WRITE_STRING( " " );

//These should be the only three forms of information you should have to write to the HUD.

//They are ranked in size- the smaller the better.

//A byte is an integer that holds up to 255 (and, reversely, -255 I think).

//A short can hold up to 65000.

//A string is either a char[ ] or char*.

MESSAGE_END( );

//This is necessary to "cap" off a message, to say "Hey! I'm done writing!"

Okay, now I'm going to explain the MessageSize thing from REG_USER_MSG. This is the "size" of the message, and is calculated by the bytes, shorts and strings you use. A byte is worth 1 and a short is worth 2. Now, if you are not sure how big it is going to be, (meaning you are probably using a char*), just put a -1. So for ours, because we had a String, it would look like this...

gmsgYourMessage = REG_USER_MSG( "MessageName", -1 );

If we didn't have that string in there, meaning we only had a byte and a short, it would look like this...

gmsgYourMessage = REG_USER_MSG( "MessageName", 3 );

//This is because 1 ( Byte ) + 2 ( Short ) = 3.

When you WRITE, the information within the parentheses would be what you want to send to the HUD (ex: a Players Health). Now, you have all the information you need set up on the server's side. Now lets set up the other side, the client side. Before I lose many of you, I'm just going to tell you that it's better to just ignore the differences in computers. Just think of it as a "talking" program, and a "listening" program. The mp.dll tells the client dll some stuff, and the client dll takes it and does whatever it wants with it. The first step to new HUD graphics is almost completely unrelated to that stuff up there. You must declare a class for you new graphics.

cl_dll.dll

hud.h ( Anywhere between lines 78 and 577 )

class YourClassName : public CHudBase

{

//YourClassName would be replaced by whatever you want.

public:

int Init( void );

int VidInit( void );

int Draw( float flTime );

//These are the three primary functions you need.

int MsgFunc_MessageName( const char *pszName, int iSize, void *pbuf );

//Then you have your message-grabbing function.

//"MessageName" is from up above.

int TheByte;

int TheShort;

char* TheString;

//These will be the three things we wrote, also to prove that you do not have to typecast with this message stuff ( woohoo! )

//Any additional functions or variables you want to add would be put here too.

private:

//Anyone actually know the point of privates? All it does is limit you...

HSPRITE SomeSprite;

wrect_t *SpriteArea;

};

Later in this file, you have to add the following line. This pretty much "adds" your class to the HUD, so it knows it is there...

cl_dll.h

hud.h ( Near line 562 )

CHudMenu m_Menu;

CHudAmmoSecondary m_AmmoSecondary;

CHudTextMessage m_TextMessage;

CHudStatusIcons m_StatusIcons;

YourClassName NewHudGraphic;

//YourClassName from above.

//NewHudGraphic would be whatever you want it to be.

There are two more lines you have to add, just to initialize your new object thingy. Both in HUD.cpp.

cl_dll.h

hud.cpp ( middle of the file, around line 100 )

m_SayText.Init();

m_Menu.Init();

NewHudGraphic.Init( );

And...

cl_dll.h

hud.cpp ( near end of file, around line 225 )

m_TextMessage.VidInit();

m_StatusIcons.VidInit();

NewHudGraphic.VidInit( );

Now, create your own new source file, and add it to the project (I'm assuming you're using Microsoft Visual C++, so do whatever sounds like this). Name it whatever you want. Here, we are just going to call it "HudDemo." First we have to define the functions we just made. They are pretty much all always the same, except for Draw. A word of caution: Sprites on the HUD are extremely confusing, and I recomend avoiding them. But I explain how to do them anyway, so if you absolutely have (or want) too, go ahead and read it. If I have already confused you, or you just don't want to know, skip it. Believe me, they're no fun.

cl_dll.dll

HudDemo.cpp

#include "hud.h"

#include "util.h"

#include "parsemsg.h"

#include <stdio.h>

#include <string.h>

//These are just the standard header files to include. The stdio.h and string.h allow you to use a few vary helpful functions, such as sprintf.

DECLARE_MESSAGE( NewHudGraphic, MessageName );

//NewHudGraphic is from above.

//MessageGrabber is from the function in our class, MsgFunc_MessageGrabber.

//Right now, I am only explaining how to do ONE message. If you want to try more than that, go for it.

int YourClassName :: Init( void )

{

HOOK_MESSAGE( MessageName );

//You grab the message we set up on the server side, and get ready to use it later (Remember: no quotes this time).

//It's from the "gmsgYourMessage = REG_USER_MSG( "MessageName", MessageSize );" line.

m_iFlags |= HUD_ACTIVE;

//Makes it the display work. Like putting a switch in the ON position...

//For an OFF position, the code would look like this...

//m_iFlags &= ~HUD_ACTIVE;

gHUD.AddHudElem( this );

//Add this to the HUD.

return 1;

//Always return 1.

}

int YourClassName :: VidInit( void )

{

//Load sprites and stuff here.

return 1;

//Always return 1.

}

int YourClassName :: MsgFunc_MessageName( const char *pszName, int iSize, void *pbuf )

{

//This is the part where we parse the message we sent from the MP.dll (just found out that valve calls it the ENTITY dll).

BEGIN_READ( pbuf, iSize );

//This is always the same, pretty much, find what we need to and begin reading.

//Now read the bytes, shorts and strings we sent from the entity dll, IN THE SAME ORDER!!!

TheByte = READ_BYTE( );

TheShort = READ_SHORT( );

//characters a little more complex... I'll just use a strcpy for now.

strcpy( TheString, READ_STRING( ) );

//if you had a maximum length of the string, you would set that integer as the third parameter of the strcpy.

return 1;

//Always return 1.

}

//The great thing about this function is that YOU never have to call it. =)

int YourClassName :: Draw( float flTime )

{

//This should be your HUGE function. This is what actually draws and positions everything.

if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_ALL ) )

return 1;

//If the HUD shouldn't be displayed, don't display this.

//Now we'll just draw the stuff like this...

// "Hello, how are you "Byte String Short" today?"

char *TempChar;

sprintf( Tempchar, "Hello, how are you %i %s %i today?", TheByte, TheString, TheShort );

//sprintf lets you put a bunch of data into a char.

//Now lets draw this to the screen. Often, it is useful to have your x and y positions predefined.

//For those of you used to graphs, picture the screen as quadrant I upside-down, meaning (0,0) is in the top left corner, and the bottom right (at 640 x 480 resolution) is (640, 480).

int xpos = ScreenWidth / 4; //One forth across the screen. Useful in the case of various screen resolutions.

int ypos = (ScreenHeight / 2) - (gHUD.m_iFontHeight / 2); //Very middle of the screen.

//Lets make a box around it, so it looks cool. We'll say it goes from one forth of the screen to one half.

FillRGBA( xpos, ypos-2, ScreenWidth / 4, gHUD.m_iFontHeight+4, 0, 0, 255, 128 );

//FillRGBA( X Position, Y Position, Width, Height, Red, Green, Blue, Alpha );

//X Position / YPosition : The coordinates of the top left corner.

//Width / Height : The total width and height of the box. Add this to X Position and Y Position respectively to get the other coordinates.

//Red / Green / Blue : The color. Figure this one out by yourself. =)

//Alpha : The transparency, where 255 is the maximum, and is SOLID, and 0 is the minimum and is COMPLETELY TRANSPARENT.

//Remember that the order we write the stuff here is the order it is layered on the screen. So, in this case, we would first want to draw the box so it is behind the text, instead of covering it up.

gHUD.DrawHudString( xpos, ypos, ScreenWidth/2, TempChar, 255, 140, 0 );

//DrawHudString( X Position, Y Position, Maximum Length, Text, Red, Green, Blue );

//X Position / Y Position : The coordinates of the top left corner.

//Maximum Length : The X Coordinate that it cuts the rest of the text, right of the position, off at.

//Text : What you want to display.

//Red / Green / Blue : The color. Figure this one out by yourself. =)

//All Text functions have an ADDITIVE property, meaning the closer they are to black (0,0,0), the more transparent they are.

 

//There are a few other text functions, each for their own purpose. Here they are (only different properties are listed)...

//DrawHudStringReverse( X Position, Y Position, Minimum Length, Text, Red, Green, Blue );

//X Position / Y Position : The coordinates of the top right corner.

//Minimum Length : The X Coordinate that it cuts the rest of the text, left of the position, off at.

 

//DrawHudNumberString( X Position, Y Position, Minimum Length, Number, Red, Green, Blue );

//Exactly like DrawHudStringReverse, except...

//Number : The integer you wish to display.

 

//DrawHudNumber( X Position, Y Position, Flags, Number, Red, Green, Blue );

//Havn't used this one much, so sorry if I describe it incorrectly.

//These are the numbers used for the Health, Shields and Ammo. That's right, the BIG numbers.

//X Position / Y Position : The coordinates for the top left corner, I THINK.

//Flags : Flags for the display. Acceptable flags are...

//DHN_DRAWZERO : When the value is 0, it will draw the 0 (otherwise it won't).

//DHN_3DIGITS : Draw 3 digits, at most, instead of a Maximum X coordinate.

//DHN_2DIGITS : Draw 2 digits, at most, instead of a Maximum X coordinate.

 

//Some other functions that may come in handy...

//UnpackRGB( Red, Green, Blue, Hexadecimal );

//Red / Green / Blue : Variables you have, where to put the generated values.

//Hexadecimal : The hexadecimal value assigned for a color earlier (those #defines, like RGB_YELLOWISH).

 

//ScaleColors( Red, Green, Blue, Alpha );

//Since all the text functions have an additive property, you can't do transparency real easily. This will calculate the new RGB values to your desired transparency.

//Red / Green / Blue : RGB Variables you already have set, that will be changed.

//Alpha : The transparency you want.

 

//That's enough text functions. =)

return 1;

}

Okay, now that's all you really need to know to do text stuff in the HUD. A lot can be accomplished with that code, ie New Menu's. But if you are DYING to know, here is how to do sprites in the HUD. Let's put a warning...

WARNING: Sprite Code Stuff. Skip this unless you want a headache.

Sprites are pretty complex, just because, well, there's a lot of code for them. For now, I'm just going to tell you how to put a sprite on the screen, and a few functions to go along with it. I don't do much sprite stuff, but I'm pretty sure most of this stuff is correct.

We'll use the same messages as before, only with a little new code.

cl_dll.dll

HudDemo.cpp

int YourClassName :: VidInit( void )

{

//Now for some new code... We made SomeSprite and SpriteArea earlier, we're just now using them.

int HUD_OurNewSprite = gHUD.GetSpriteIndex( "new_sprite" );

//We have found our sprite's data (not really, because it doesn't exist yet), now lets make the sprite...

SomeSprite = gHUD.GetSprite(HUD_OurNewSprite);

SpriteArea = &gHUD.GetSpriteRect(HUD_OurNewSprite);

return 1;

}

Now, as I mentioned in the comment, we need to make Sprite Data for it to get. So open up the pak0.pak file, and in the sprites folder, find hud.txt and open it.

pak0.pak / sprites

hud.txt

item_battery 320 320hud2 48 52 20 20

item_healthkit 320 320hud2 68 52 20 20

item_longjump 320 320hud2 88 52 20 20

//For now, lets just make the sprite a skull and crossbones thing from when a player dies.

new_sprite 320 320hud1 192 240 32 16

//Sprite_Name Resolution SpriteFile XPos YPos Width Height

//Sprite_Name : whatever you define it as in GetSpriteIndex

//Resolution : if the resoltion is below 640, it calls the 320 resolution, otherwise it does the 640.

//SpriteFile : the sprite the actual thing-to-be-displayed can be found in.

//XPos / YPos : The coordinates of the top left corner of the thing-to-be-displayed

//Width / Height : How far over and down to find the bottom right corner of the thing-to-be-displayed.

Later in the file, you will need to put one for the 640 resolution...

pak0.pak / sprites

hud.txt

item_battery 640 640hud2 176 0 44 44

item_healthkit 640 640hud2 176 48 44 44

item_longjump 640 640hud2 176 96 44 44

new_sprite 640 640hud1 192 224 32 16

Now that we have all that set up, we can do the REAL coding for the sprite. =) All of what we did got the sprite ready to manipulate, and now we are going to manipulate it. Now, in the Draw function, you need to do a few things.

cl_dll.dll

HudDemo.cpp

int YourClassName :: Draw( float flTime )

{

if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_ALL ) )

return 1;

SPR_Set( SomeSprite, 255, 140, 0 );

//Set the sprite up. This registers the RGB values of the sprite.

//Now we need to draw the sprite. There are a bunch of ways to do this. For what we are doing, SPR_DrawAdditive would be the best.

SPR_DrawAdditive( 0, ScreenWidth / 4, ScreenHeight / 4, SpriteArea );

//SPR_DrawAdditive( Frame, X Coordinate, Y Coordinate, Rectangle );

//Draw a sprite with an additive property. Meaning, the closer you get to (0, 0, 0), black, the more transparent it gets.

//Frame : what frame to draw. Here is your chance for animated HUD graphics. =)

//X Coordinate / Y Coordinate : The coordinates of the top left corner of the sprite, on the screen.

//Rectangle : The area to get the sprite from. We set this up in VidInit.

 

//SPR_Draw( Frame, X Coordinate, Y Coordinate, Rectangle );

//You never use this. I have no idea what it does, and according to valve, there is no Rectangle Parameter.

//My guess is that it's just like SPR_DrawAdditive, only without the Additive property.

 

//SPR_DrawHoles( Frame, X Coordinate, Y Coordinate, Rectangle );

//Another one you never use. Supposedly, it just makes the Color Index #255, white (255, 255, 255), completely transparent.

//I have no idea when you would ever want to use this. =)

 

//Now a bunch of other functions you may want to use...

//SPR_Frames( Sprite );

//Returns the number of frames in Sprite.

 

//SPR_Height( Sprite, Frame );

//Returns the height of Sprite on frame Frame. Returns 0 if invalid.

 

//SPR_Width( Sprite, Frame );

//Returns the width of Sprite on frame Frame. Returns 0 if invalid.

 

//InHighRes( void );

//Returns 1 if the resolution is 640x480 or higher. Otherwise, 0.

 

//SPR_EnableScissor( X Coordinate, Y Coordinate, Width, Height );

//X Coordinate / Y Coordinate : The coordinates of the top left corner.

//Width / Height : How far over in pixels you have to move to get to the bottom right corner.

//I have no idea how to use this for sure.

 

//SPR_DisableScissor( void );

//Disables a scissor. Once again, you never use it.

 

char *TempChar;

sprintf( Tempchar, "Hello, how are you %i %s %i today?", TheByte, TheString, TheShort );

int xpos = ScreenWidth / 4;

int ypos = (ScreenHeight / 2) - (gHUD.m_iFontHeight / 2);

FillRGBA( xpos, ypos-2, ScreenWidth / 4, gHUD.m_iFontHeight+4, 0, 0, 255, 128 );

return 1;

}

And that is about it. Wow. =) After looking through it, I noticed somthing that may be confusing. When you draw a sprite, you use the rectangle. Not the actual sprite. I have no idea why, and this is VERY confusing. In my oppinion, the ideal condition would be one class that holds all the sprite information (including the rectangle). But it's not, so you're gonna have to do that yourself. Hopefully that is enough information for you.

 

blank.gif (837 bytes)
bl.jpg (2471 bytes) blank.gif (837 bytes) br.jpg (2132 bytes)