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