Welcome, Guest! Login | Register

New Menuing System Tutorial [Print this Article]
Posted by: omega
Date posted: Apr 14 2003
User Rating: 5 out of 5.0
Number of views: 6335
Number of comments: 1
Description: by Phillip Peterson


EDITOR NOTES:
* This tutorial exists on the old site; all I did was convert it, added formatting and colors. If you discover any mistakes I may have missed, please let me know =)


Description
In this tutorial, we are going to go through the steps necessary to create a menuing system for Half-Life similar to the one for Quake 2.


In The Client DLL

Open up hud.h and somewhere in there add this code:
 CODE  


typedef enum
{
    MENU_ALIGN_LEFT,
    MENU_ALIGN_CENTER
 
} menu_align_t;


// What we just created was a new type to store the alignment of certain line in the menu


typedef struct
{
    char Text[128];
// text of the menu item
    menu_align_t Align;
// the items alignment
    int Selectable;
// can it be selected
    int ReturnNumber;
// if so, what number is it
   
} menu_struct_t;

// this is the basic structure for the menu, our menu�s will be arrays for menu_struct_t�s

class CNewMenu
{
public:
    int Init( void );
    void InitHUDData( void );
    int VidInit( void );
    void Reset( void );
    int Draw( float flTime );
    int MsgFunc_MenuOpen( const char *pszName, int iSize, void *pbuf );
   
    void SelectNextOption ( void );
// moves the cursor down
    void SelectPreviousOption ( void );
// moves the cursor up
    void SelectItem ( void );
// slects the menu item
    int m_iCurrentSelection;
   
// index to the current item selected
    int m_iTotalSlots;
   
// total number of entries in the menu, include everything
    BOOL m_bMenuDisplayed;
// true if the menu is displayed
    BOOL m_bCanCancelMenu;
// true if the menu can be closed
    menu_struct_t *m_CurrentMenu;
// pointer to the current menu
    int sRed, sGreen, sBlue;
   
// colors for the item that are selected
    int Red, Green, Blue;
   
// colors for the rest of the items in the menu
};


Now the explanation of the class CNewMenu.

These following functions are basic HUD functions:

Init - as you can probably guess is the point where we get everything started
InitHUDData - in this function we�ll set all our member variables up, stuff like that
VidInit - isn�t really needed, but it�s here for us incase you want to add a sprite or something
Reset - reset the data
Draw - we�ll draw the menu here, hence its name
MsgFunc_MenuOpen - is the function called when we send the message to open a new menu


These next functions are ones we are going to create, we�ll need these later:

SelectNextOption - this function will advance the menu selection one
SelectPreviousOption - this function will select the previous item
SelectItem - this one will be called when the player selects an item


The Member Variables of CNewMenu

m_iCurrentSelection - this is the index in our menu array to the item that is currently selected or highlighted
m_iTotalSlots - this is the number of items in the menu array
m_bMenuDisplayed - boolean, true if there is a menu
m_bCanCancelMenu - boolean, true if the menu can be exited ( like for a voting system, probably wouldn�t be used for a team selection menu )
*m_CurrentMenu - pointer to the menu being displayed
sRed, sGreen, sBlue - will hold the RGB color for the selected item
Red, Green, Blue - will hold the RGB color for the rest of the items


OK, now our class is declared, so go down a bit in hud.h and find the definition of CHud, and add this code:
 CODE  

CHudStatusIcons m_StatusIcons;
// add the code after m_StatusIcons is declared
CNewMenu m_NewMenu;

After that is declared, go through hud.cpp and the others add make sure our Init() is called and VidInit() is called.

Now we write some real code

Create a new file called newmenu.cpp and add it to your project. (Assuming you�re using MSVC++)

Now in newmenu.cpp do the includes:
 CODE  

#include "hud.h"
#include "util.h"
#include "parsemsg.h"

#include "string.h"
#include "stdio.h"


These are the basic header files we need. Now, we need to declare our message using DECLARE_MESSAGE so the server�s dll can communicate with the client dll.
 CODE  

DECLARE_MESSAGE ( m_NewMenu, MenuOpen );

The m_NewMenu is the name of the member variable in CHud. That sounded really confusing, sorry, it�s late and I�m tired.

Now we define our Init() function.
 CODE  

int CNewMenu::Init ( void )
{
    gHUD.AddHudElem ( this );
   
    HOOK_MESSAGE ( MenuOpen );
   
    InitHUDData ();
   
    return 1;
}


By calling gHUD.AddHudElem ( this ) we add our new menu to the list of all the other HUD elements, so we can receive our new message, and get updated, etc.

HOOK_MESSAGE ( MenuOpen ) just lets HL know that our class wants the MenuOpen message.

Finally, we init our variables by calling InitHUDData() and then we return 1 to show everything is OK.
 CODE  

void CNewMenu::InitHUDData ( void )
{
    UnpackRGB ( sRed, sGreen, sBlue, RGB_REDISH );
    UnpackRGB ( Red, Green, Blue, RGB_YELLOWISH );
   
    m_CurrentMenu = NULL;
    m_bMenuDisplayed = FALSE;
    m_bCanCancelMenu = FALSE;
    m_iTotalSlots = 0;
    m_iCurrentSelection = 0;
   
}


Here, we are initializing all the variables. The thing of most interest to you would be the setting of the RGB values for the highlighted and non-highlighted items in the menu. We�re making them variables so it will be easier for you to modify the colors if you don�t want yellow and red.
 CODE  

void CNewMenu::Reset ( void )
{
   
}

int CNewMenu::VidInit ( void )
{
    return 1;
}


Not much going on here, we�re just putting them here just incase you want them later.

The Draw function
 CODE  

int CNewMenu::Draw ( float flTime )
{
    int x_pos = ScreenWidth / 4;
    int y_pos = (ScreenHeight - (12 * m_iTotalSlots))/2;
   
    int char_widths = gHUD.m_scrinfo.charWidths[' '];
   
    int x_rel;
   
    for ( int i = 0; i < m_iTotalSlots; i++ )
    {
  x_rel = x_pos;
  if ( m_CurrentMenu[i].Align == MENU_ALIGN_CENTER )
  {
    for ( int cs = 0; m_CurrentMenu[i].Text[cs] != '\0'; cs++ ) { }
   
    x_rel = (ScreenWidth - (15*cs))/2;
  }
 
  if ( i == m_iCurrentSelection )
    gHUD.DrawHudString ( x_rel, y_pos, ScreenWidth, m_CurrentMenu[i].Text, sRed, sGreen, sBlue );
  else
    gHUD.DrawHudString ( x_rel, y_pos, ScreenWidth, m_CurrentMenu[i].Text, Red, Green, Blue );
 
  y_pos += 12;
    }
   
    return 1;
}


This is pretty simple. All that is going on is that we�re getting the X and Y coords for the menu so we can draw it centered, and then x_rel is used for center aligned items. Other than that, we�re just cycling through the array and drawing the Text.

The Message Function

 CODE  

int CNewMenu::MsgFunc_MenuOpen ( const char *pszName, int iSize, void *pbuf )
{
    BEGIN_READ ( pbuf, iSize );
   
    int menu = READ_BYTE ();
    m_bCanCancelMenu = READ_BYTE ();
   
   
    m_CurrentMenu = TestMenu;
    m_iTotalSlots = 10;
    m_iCurrentSelection = 0;
   
    if ( !m_CurrentMenu[m_iCurrentSelection].Selectable )
  SelectNextOption ();

   
// switch ( menu )
    // {
    // case 1:
    // break;
   
    // default:
    // break;
    // }

   
    m_iFlags |= HUD_ACTIVE;
    m_bMenuDisplayed = TRUE;
   
   
    return 1;
}


In this menu, we�re getting the menu to be displayed (which is sent as a byte, you can set up a switch statement to set up the CurrentMenu, TotalSlots, etc ) and whether or not it can be canceled ( another byte ). In the server dll, the protocall would be something like this:
 CODE  

WRITE_BYTE ( menu_number );
WRITE_BYTE ( TRUE or FALSE );


After that, we set up the menu so its active so it can be drawn.

The Next/Previous/Selection Functions
 CODE  

void CNewMenu::SelectItem ( void )
{
    if ( (m_CurrentMenu) && (m_bMenuDisplayed) && ( m_CurrentMenu[m_iCurrentSelection].Selectable) )
    {
  char cmd[32];
 
  sprintf ( cmd, "menuselect %d\n", m_CurrentMenu[m_iCurrentSelection].ReturnNumber );
 
  ClientCmd ( cmd );
 
  m_CurrentMenu = NULL;
  m_iTotalSlots = 0;
  m_iCurrentSelection = 0;
 
  m_bMenuDisplayed = FALSE;
  m_bCanCancelMenu = FALSE;
 
  m_iFlags &= ~HUD_ACTIVE;
  gHUD.m_iKeyBits &= ~IN_ATTACK;
    }
}


Before we send the "menuselect" command, we do some basic checking to make sure there is a menu. Etc. Then we find out which one is currentley highlight and return menuselect and its ReturnNumber. Then we clear the keybits of IN_ATTACK so if you have successive menus, the select command wouldn�t be carried onto the next one, and finally, we make the menu inactive.
 CODE  

void CNewMenu::SelectNextOption ( void )
{
    if ( m_iCurrentSelection == (m_iTotalSlots - 1) )
  m_iCurrentSelection = 0;
   
    else
  m_iCurrentSelection++;
   
    while ( m_CurrentMenu[m_iCurrentSelection].Selectable != TRUE )
    {
  if ( m_iCurrentSelection == (m_iTotalSlots - 1) )
    m_iCurrentSelection = 0;
 
  else
    m_iCurrentSelection++;
    }
}

void CNewMenu::SelectPreviousOption ( void )
{
    if ( m_iCurrentSelection == 0 )
  m_iCurrentSelection = (m_iTotalSlots - 1);
   
    else
  m_iCurrentSelection--;
   
    while ( m_CurrentMenu[m_iCurrentSelection].Selectable != TRUE )
    {
  if ( m_iCurrentSelection == 0 )
    m_iCurrentSelection = (m_iTotalSlots - 1);
 
  else
    m_iCurrentSelection--;
    }
}



Both these functions just cycle though the array and find a new selectable item, and change the currentselection to its index. Nothing fancy, you can probably figure out whats going on here just by looking at the code.

The Selection/Highlighting of our menu

Now that we have finished or newmenu.cpp, open up ammo.cpp and find CHudAmmo::Think (). At the very top of the function, add this code:
 CODE  

if (gHUD.m_iKeyBits & IN_ATTACK)
{
    if ( gHUD.m_NewMenu.m_bMenuDisplayed )
    {
  gHUD.m_NewMenu.SelectItem ();
  return;
    }
}


Now, whenever the menu is displayed, and the player presses their fire button, it�ll select a menu item, cool huh?

Now go find void CHudAmmo::UserCmd_Close () and add this code at the very top.
 CODE  

if ( gHUD.m_NewMenu.m_bCanCancelMenu )
{
    if ( gHUD.m_NewMenu.m_bMenuDisplayed )
    {
  gHUD.m_NewMenu.m_CurrentMenu = NULL;
  gHUD.m_NewMenu.m_iCurrentSelection = 0;
  gHUD.m_NewMenu.m_bMenuDisplayed = FALSE;
  gHUD.m_NewMenu.m_iTotalSlots = FALSE;
  gHUD.m_NewMenu.m_iFlags &= ~HUD_ACTIVE;
  gHUD.m_NewMenu.m_bCanCancelMenu = FALSE;
  return;
    }
}


This checks to see if the menu is displayed, and if so, can it be canceled; if it can, them we get rid of the menu. If you want to, you can modify the menu so you can "Hide" it and bring it back later with TAB or some other command.

Now go find void CHudAmmo::UserCmd_NextWeapon(void) and add this code at the top:
 CODE  

if ( gHUD.m_NewMenu.m_bMenuDisplayed )
{
    gHUD.m_NewMenu.SelectNextOption ();
    return;
}


This Code will advance the menu item, blah blah blah.

And again, in void CHudAmmo::UserCmd_PrevWeapon(void) add this at the top:
 CODE  

if ( gHUD.m_NewMenu.m_bMenuDisplayed )
{
    gHUD.m_NewMenu.SelectPreviousOption ();
    return;
}


This makes the menu go back one.

The Test Menu and Defining a New Menu

At the top of newmenu.cpp is where you should create your new menu�s, here is the test menu, this is basically how all others should be created.
 CODE  

menu_struct_t TestMenu[] =
{
    { "T e s t M e n u", MENU_ALIGN_CENTER, FALSE, -1 },
    { "", MENU_ALIGN_LEFT, FALSE, -1 },
    { "Please Choose a Team", MENU_ALIGN_CENTER, FALSE, -1 },
    { "Red Team", MENU_ALIGN_LEFT, TRUE, 1 },
    { "Blue Team", MENU_ALIGN_LEFT, TRUE, 2 },
    { "Green Team", MENU_ALIGN_LEFT, TRUE, 3 },
    { "Yellow Team", MENU_ALIGN_LEFT, TRUE, 4 },
    { "", MENU_ALIGN_LEFT, FALSE, -1 },
    { "Use \'[\' and \']\'", MENU_ALIGN_LEFT, FALSE, -1 },
    { "To Move The Selection", MENU_ALIGN_LEFT, FALSE, -1 }
};


The FALSE�s and TRUE�s are the bools for whether or not that specific item is selectable, the FALSE�s are accompanied by -1�s, it doesn�t matter for those, but for the TRUE�s make sure you get the right number.

In the server dll, register the new message like this:

At the top of player.cpp, around all the other gmsg�s add this:
 CODE  

int gmsgMenuOpen = 0;

Then down by all the REG_USER_MSG�s, add this
 CODE  

gmsgMenuOpen = REG_USER_MSG ( �MenuOpen�, -1 );

And if you want to call the menu, this is how you would do it:
 CODE  

MESSAGE_BEGIN ( MSG_ONE, gmsgMenuOpen, NULL, pev );
// the pev is the player the menu is going to
WRITE_BYTE ( menu );
// the number of the menu
WRITE_BYTE ( canbecanceled );
// true/false
MESSAGE_END ();


That�s it! We�re done! Feel free to email with questions or comments about anything, or if you would like the full source.

Rate This Article
This article is currently rated: 5 out of 5.0 (1 Votes)

You have to register to rate this article.
User Comments Showing comments 1-1

Posted By: MIFUNE on May 11 2005 at 18:02:41
Any pics of the menu working? ¬¬


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: MIFUNE | Dec 31 2017
 
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (6 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!