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.
|