HUD Countdown Tutorial. by Hopper. hopper@mind-body-power.net
All code in this tutorial was made by me. I didn't use anyone else's stuff. This stuff is common sense... I don't care if you cut and paste the code in here. If you do, please give me some credit. Please don't post this tut to other sites and take credit for it yourself.
This is my first tutorial, so let me know if there are any problems with it. It was made with the sdk v2.3 and Vis Studio.net. There may be some bugs because I havent tested it to death yet, but for the testing I have done it works quite well.
Now for the good stuff: Making a countdown timer that is started and stopped from the server.
Overview: This tut will walk you through the steps required to make a countdown timer on the client's hud. The basic idea is that we will make a message on the server side that will tell the client side to either start or stop the timer. When the client gets the start timer message, it will start the countdown. The timer will continue until 0, or until it gets a message from the server telling it to stop counting down.
Code:
Server side modifications:
1. First we need to make a new message. This message will be sent to the client when we want to start and stop the countdown.
First we make the message. Go into Player.cpp and to about line 190. There you will see the following code:
| | |
int gmsgSetFOV = 0; int gmsgShowMenu = 0; int gmsgGeigerRange = 0;
|
Right after that stuff, add the following code:
// Countdown message declaration
Next, you want to go down about 50 lines to where all of the messages are registered with the engine. Look for the following code:
| | |
gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); gmsgAmmoX = REG_USER_MSG("AmmoX", 2);
|
Under that stuff, add the following:
| | |
// register Countdown message with the engine gmsgCountdown = REG_USER_MSG("Countdown",6);
|
This code is going to register our message and assign it a unique integer value that the engine will use to identify it in the client dll. The 6 is how many bytes the message will sending. I'll talk about how you could change it later.
Ok, that's it for Player.cpp On to the next step...
2. This part is the real important part... I'm basically going to show you the syntax for sending this message, but I want to leave this general enough that you can customize it for your own mod. With that in mind, I'm not going to show you where to put the message or anything... that's for you to figure out. I will however give you some ideas.
First, put this code above the function you are going to use this code in:
| | |
extern int gmsgCountdown; // takes care of "undeclared identifier compiler // errors and name mangling issues
|
then, put this where you want to call the countdown
| | |
MESSAGE_BEGIN( MSG_ALL, gmsgCountdown ); WRITE_SHORT( START_COUNTING ); WRITE_SHORT( flDelay ); WRITE_SHORT( 0 ); MESSAGE_END();
MESSAGE_BEGIN( MSG_ALL, gmsgCountdown ); WRITE_SHORT( STOP_COUNTING ); WRITE_SHORT( 0 ); WRITE_SHORT( 0 ); MESSAGE_END();
|
Ok, there you go... You just stick that code in the spot that you want to send the message. You have to #define the START_COUNTING and the STOP_COUNTING values to the same values that we will use on the client side. The best way to do it is #define them in a header file that is included on both the client and server side, so that if you need to change them or add new values, they will automatically update on both sides. Also, the flDelay variable should be set to the length of time the countdown should run.
Now, about where to stick these: If your countdown is related to the gamerules, and you're using it for a capture the flag type system where after you capture the flag you want a time delay where the capturing team has to defend the flag before they get a point... you should stick some logic into the void CHalfLifeTeamplay :: Think ( void ) function that controls when to start and when to stop the timer. On a more basic level, you could also call it straight from your Touch() function on your flag... but that isnt always what you want to do if your system is more complex than just one flag.
Alright, now we're done working in the server side stuff... on to the client.
Client Side Modifications:
1. The first thing we have to do is set up the message function. This is kind of complicated so just follow directions closely.
First, go into Hud.cpp.
Find the following code:
| | |
int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) { return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); }
|
After it, add the following code:
| | |
int __MsgFunc_Countdown(const char *pszName, int iSize, void *pbuf) { return gHUD.MsgFunc_Countdown( pszName, iSize, pbuf ); }
|
2. Next, go a bit further down and find the CHud::Init() function. It has all of the HOOK_MESSAGE functions in it. Find the following code:
| | |
HOOK_MESSAGE( SetFOV ); HOOK_MESSAGE( Concuss );
|
After that stuff, add the following:
| | |
HOOK_MESSAGE( Countdown );
|
3. Next, you have to go into the hud.h file and find the CHud class declaration at the bottom. In there, find the following code:
| | |
void _cdecl MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ); int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf );
|
After that stuff, add the following:
| | |
int _cdecl MsgFunc_Countdown( const char *pszName, int iSize, void *pbuf );
|
4. Now, before we set up the message function, I want to add some variables to the Chud class so that we can use them to control the drawing of the countdown. So, go into hud.h and find the following code ( in the CHud class declaration ):
| | |
HSPRITE m_hsprCursor; float m_flTime; // the current client time float m_fOldTime; // the time at which the HUD was last redrawn double m_flTimeDelta; // the difference between flTime and fOldTime
|
After that stuff, add the following code:
| | |
float m_flCountdownStartTime; // stores the time our countdown started float m_flTimeLeft; // time to put on HUD int m_iCountdownDuration; // duration of the countdown bool m_bCountdown; // countdown active?
|
Although you don't need to, it is good programming practice to initialize these values... So, you could go into the hud.cpp file and file the constructor for that class and initialize the values. Make the int's and floats equal to 0 and the set the bool to false.
5. Now that we have that done, lets make our message function. Go into hud_msg.cpp and go to the very bottom of the file. Add the following code:
| | |
#define START_COUNTING 1 //should agree with server side values #define STOP_COUNTING 0
int CHud :: MsgFunc_Countdown( const char *pszName, int iSize, void *pbuf ) { BEGIN_READ( pbuf, iSize );
int iAction = READ_SHORT(); int iDuration = READ_SHORT();
if( iAction == 1 ) //start the countdown { m_bCountdown = true; m_flCountdownStartTime = m_flTime; m_iCountdownDuration = iDuration; } else //end the countdown { m_bCountdown = false; m_flCountdownStartTime = 0; m_iCountdownDuration = 0; }
return 1; }
|
Basically what we're doing here is setting the parameters for the countdown depending on whether we are starting it or stopping it. The READ_SHORT() macros simply get the first and second short's that we sent in the message. If you wanted to send additional info in that third short, just add another READ_SHORT() statement.
6. Now that we have that done we can go take care of actually putting the countdown on the screen.
First, go to the hud_redraw.cpp. Find the CHud::Think() function. This is where we will figure out what time to write to the hud. Find the following code:
| | |
// think about default fov if ( m_iFOV == 0 ) { // only let players adjust up in fov, and only if they are not overriden by something else m_iFOV = max( default_fov->value, 90 ); }
|
After that stuff, add the following:
| | |
//Figure out how much time is left (if we are counting) if( m_bCountdown ) { m_flTimeLeft = m_flCountdownStartTime + m_iCountdownDuration - m_flTime; if( m_flTimeLeft < 0) // if we get to less than 0, we want to stop m_bCountdown = false; }
|
In the above code, the m_flTime is the current time client side. m_flTimeLeft is going to be the amount of time left until the timer stops.
7. Ok, finally you need to go into the CHud::Redraw() function. Its right below the Think() function. Find the following code:
| | |
// are we in demo mode? do we need to draw the logo in the top corner? if (m_iLogo) { int x, y, i;
if (m_hsprLogo == 0) m_hsprLogo = LoadSprite("sprites/%d_logo.spr");
SPR_Set(m_hsprLogo, 250, 250, 250 ); x = SPR_Width(m_hsprLogo, 0); x = ScreenWidth - x; y = SPR_Height(m_hsprLogo, 0)/2;
// Draw the logo at 20 fps int iFrame = (int)(flTime * 20) % MAX_LOGO_FRAMES; i = grgLogoFrame[iFrame] - 1;
SPR_DrawAdditive(i, x, y, NULL); }
|
Right below that stuff, add the following code:
| | |
//Draw the Countdown if( m_bCountdown ) { int rVal = 100; int gVal = 100; int bVal = 100; if( m_flTimeLeft < 10 ) rVal = 255; //red text when less than 10 sec left
DrawHudNumber( 20, 20, 200, m_flTimeLeft, rVal, gVal, bVal );
}
|
As it is right now, the timer will be drawn to the upper left corner of the screen, it will be lightish and partially transparent till it hits 10 seconds to go... then it will turn to a reddish color.
FINAL THOUGHTS:
This is all fine and dandy if you want a timer, but if you want the timer to be counting down to an event, you have to set up the event to run on a timed schedule also. The implementation is similar to the way we figure out how much time is left in the countdown. On the server side, you can access the current time and set a start time and all that. In fact, you can just modify the timed round code to work for you. Its all in the Think() function in the various gamerules files. Be creative.
The message we are sending is only 4 bytes long (2 shorts). However, we set up the message to send 6 bytes. This is in case you want to send some additional info across. Since this kind of message doesn't get sent too often, there wont be an issue with bandwidth.
Check the DrawHudNumber function to see what the input parameters are for. You can customize it a lot if you want.
Common problems: If you are getting undeclared identifier errors, check to make sure you are using all the same names for the functions. ( myFunc, is not myfunc ) Also, make sure you have the extern's that I put in there.
Well, that is it... That should work. This is my first tut so if I missed anything, let me know. If you get any errors that are crazy and insane let me know, I might have left something out of the tut.
-Hopper
btw, check out the mod im workin on... we're hammering out the details till HL2 comes out. http://www.mind-body-power.net |