One of the most frequent questions asked on the forum is how to create a Heads Up Display (HUD). This tutorial is based on the original HUD tutorial written by Ewen, but has been updated to work with Genesis 1.1. This tutorial will teach you how to change the sample program (GTEST.EXE) to enable an overlaid HUD. This simply draws some 2d bitmaps over the frame after all the 3D work has been done, so that it appears to sit on top of the screen.
This tute was written by Ewen.
Modified to work with Genesis 1.1 by David Wulff.
ok, here we go...remember to give or take a few when it comes to line numbers...
First download the files from Ewen's original tutorial as we will be using the same bitmap file for the hud graphics.
Second: you must make changes to Client.C and GENVS.C.
In GENVS.C you must add a line near the beginning of the file to include HUD.H
Line 35: #include "HUD.h"
Next add a pointer to a HUD object (and a rectangle object we'll use to position the HUD), about line 72:
// Misc objects
static Console_Console *Console;
Host_Host *Host;
geSound_System *SoundSys;
HUD *theHUD;
geRect gR = {0,0,0,0};
Now we need some changes to the WinMain function, to set the clipping rectangle of the HUD and to create the HUD object, this is around about line 415 or so:
#if 1
// <>
geEngine_SetGamma(Engine, UserGamma);
#endif
QueryPerformanceFrequency(&Freq);
QueryPerformanceCounter(&OldTick);
// Hud
gR.Left = 0; // left of the screen
gR.Top = 480 - 1 - 24; // 25 pixels from the bottom of the screen is where we draw
gR.Right = 640 - 1; // the width of the screen
gR.Bottom = 480 - 1; // the bottom of the screen
theHUD = CreateClientHUD(Engine, &gR); // create the HUD object
//SetCapture(GameMgr_GethWnd(GMgr));
ShowCursor(FALSE);
#ifdef CLIP_CURSOR
{
(NB: Remeber to change the 640 and 480 references for your current screen size)
Finally, we need to destroy the HUD on shutdown by modifying the ShowdownAll function:
//=====================================================================================
// ShutdownAll
//=====================================================================================
void ShutdownAll(void)
{
ReleaseCapture();
ShowCursor(TRUE);
....
// Free each object (sub objects are freed by their parents...)
if (MenusCreated)
{
Text_Destroy();
GMenu_Destroy();
MenusCreated = GE_FALSE;
}
// Hud
DestroyClientHUD(theHUD);
if (Host)
{
Host_Destroy(Host);
Host = NULL;
}
....etc.
In Client.C you must add a line near the beginning of the file to include HUD.H
Line 28:. #include "HUD.h"
After the includes, you must add a line to make a reference to theHUD object in GENVS.C:
Line 35: extern HUD *theHUD;
Next, you must add one line of code to the Client_RenderFrame function:
//=====================================================================================
// Client_RenderFrame
//=====================================================================================
geBoolean Client_RenderFrame(Client_Client *Client, float Time)
{
assert(Client_IsValid(Client) == GE_TRUE);
if (Client->NetState != NetState_WorldActive) // If no world currently set, then return...
return GE_TRUE;
if (!RenderWorld(Client, Client->GMgr, Time))
GenVS_Error("Client_RenderFrame: RenderWorld failed.\n");
if (Client->MultiPlayer)
{
if (!PrintClientScores(Client))
GenVS_Error("Client_RenderFrame: PrintClientScores failed.\n");
}
PrintCrossHair(Client);
UpdateStatusBar(Client);
ScreenBorderHack(Client);
// Hud
if (!DrawHUD(theHUD, Client)) // this is where we draw the HUD
return GE_FALSE;
return GE_TRUE;
}
Here is the listing for the HUD header file.
/****************************************************************************/
/* FILE: HUD.h */
/* Ewen Vowels */
/* 29 August 1998 */
/* Copyright (c) 1998, Ewen Vowels; All rights reserved. */
/* NB: This code is free to use / modify, just let Ewen know if you are */
/* using it or attribute him in your program. */
/**/
/* Minor modifications made by David Wulff */
/****************************************************************************/
#ifndef _EVHUD_H
#define _EVHUD_H
#include <Windows.h>
#include "Genesis.h"
#include "Ram.h"
#include "Client.H"
#include "Host.H"
#include "Text.h"
#include "Host.h"
#include "Server.h"
#include "GPlayer.h"
#include "GenVSI.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct HUD
{
geEngine *theEngine;
geRect *theHUDLocation; // technically not needed
geBitmap *HUDTextures;
} HUD;
HUD *CreateClientHUD(geEngine *Engine, geRect *HUDLoc);
geBoolean DestroyClientHUD(HUD * hud);
geBoolean DrawHUD(HUD * hud, Client_Client *client);
#ifdef __cplusplus
}
#endif
#endif
/****************************************************************************/
/* FILE: HUD.c */
/* Ewen Vowels */
/* 29 August 1998 */
/* Copyright (c) 1998, Ewen Vowels; All rights reserved. */
/* NB: This code is free to use / modify, just let Ewen know if you are */
/* using it or attribute him in your program. */
/**/
/* Minor modifications made by David Wulff */
/****************************************************************************/
#include <Windows.h>
#include <assert.h>
#include "Genesis.h"
#include "HUD.h"
#include "Ram.h"
#include "Client.H"
#include "Host.H"
#include "Text.h"
#define evItemWidth 20
#define evItemHeight 20
HUD* CreateClientHUD(geEngine *Engine, geRect *HUDLoc)
{
HUD *h = malloc(sizeof(HUD)); // create the memory for the HUD
assert(h);
h->theEngine = Engine; // get a copy of the engine object
h->theHUDLocation = HUDLoc; //store the location
h->HUDTextures = geBitmap_CreateFromFileName(NULL, "bttlxe\\gfx\\hud\\hud_01.bmp"); //load the texture
geEngine_AddBitmap(h->theEngine, h->HUDTextures); //add it to the engine
return h;
}
geBoolean DestroyClientHUD(HUD * hud)
{
if (hud->HUDTextures)
{
assert(hud->theEngine);
geEngine_RemoveBitmap(hud->theEngine, hud->HUDTextures);
}
assert(hud);
free(hud); // free the memory
return GE_TRUE;
}
geBoolean DrawHUD(HUD * hud, Client_Client *client)
{
Client_ClientInfo *clientInfo;
geRect textClip;
int32 remainder, health;
uint32 Ammo;
geBoolean HasItem;
// currently there are too many magic numbers in here, and there could be some serious optimisations,
// but this is more of a proof of concept demo.
assert(hud->theEngine);
clientInfo = client->ClientInfo;
if (!clientInfo->Active)
return GE_TRUE;
// draw the health box
textClip.Top = evItemHeight;// health icon is on the second row of the HUD ITEMS texture
textClip.Bottom = 2 * evItemHeight;
textClip.Left = 0;
textClip.Right = evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 4*evItemWidth - 15, hud->theHUDLocation->Top))
return GE_FALSE;
textClip.Top = 0; // numbers are the top row of the HUD ITEMS texture, so wew clip to show only the 24 pixels
textClip.Bottom = evItemHeight;
health = clientInfo->Health;
remainder = health / 100;
if (remainder > 0) // only display the first digit if health >= 100
{
textClip.Left = remainder * evItemWidth;
textClip.Right = remainder * evItemWidth + evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + evItemWidth, hud->theHUDLocation->Top))
return GE_FALSE;
}
health = health % 100;
remainder = health / 10;
textClip.Left = remainder * evItemWidth;
textClip.Right = remainder * evItemWidth + evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 2*evItemWidth - 5, hud->theHUDLocation->Top))
return GE_FALSE;
health = health % 10;
remainder = health;
textClip.Left = remainder * evItemWidth;
textClip.Right = remainder * evItemWidth + evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 3*evItemWidth - 10, hud->theHUDLocation->Top))
return GE_FALSE;
// from Client.c
Ammo = client->Inventory[client->CurrentWeapon];
HasItem = (Ammo & (1<<15));
Ammo &= 0xff;
if (client->CurrentWeapon == 0) // if it is the blaster, display an infinity symbol
{
textClip.Top = evItemHeight;
textClip.Bottom = 2 * evItemHeight;
textClip.Left = evItemWidth;
textClip.Right = 2 * evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 8*evItemWidth - 15, hud->theHUDLocation->Top))
return GE_FALSE;
}
else if (HasItem) // if the user has an item, show the AMMO
{
textClip.Top = 0; // numbers are the top row of the HUD ITEMS texture
textClip.Bottom = evItemHeight;
remainder = Ammo / 100;
if (remainder > 0) // only draw the first digit if necessary
{
textClip.Left = remainder * evItemWidth;
textClip.Right = remainder * evItemWidth + evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 6*evItemWidth, hud->theHUDLocation->Top))
return GE_FALSE;
}
Ammo = Ammo % 100;
remainder = Ammo / 10;
if (remainder > 0) // only draw the second digit if necessary
{
textClip.Left = remainder * evItemWidth;
textClip.Right = remainder * evItemWidth + evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 7*evItemWidth - 5, hud->theHUDLocation->Top))
return GE_FALSE;
}
Ammo = Ammo % 10;
remainder = Ammo;
textClip.Left = remainder * evItemWidth;
textClip.Right = remainder * evItemWidth + evItemWidth;
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 8*evItemWidth - 10, hud->theHUDLocation->Top))
return GE_FALSE;
}
else
{
//if (!Console_XYPrintf(Host->Console, 30, 30, " Ammo: No Weapon"))
// return GE_FALSE;
}
textClip.Top = 2 * evItemHeight ;// weapons are on the third row of the HUD ITEMS texture
textClip.Bottom = 3 * evItemHeight ;
switch(client->CurrentWeapon)
{
case 0: // BLASTER
{
textClip.Left = 0;
textClip.Right = evItemWidth;
break;
}
case 1: // GRENADES
{
textClip.Left = 2*evItemWidth;
textClip.Right = 3*evItemWidth;
break;
}
case 2: // ROCKETS
{
textClip.Left = 3*evItemWidth;
textClip.Right = 4*evItemWidth;
//if (!Console_XYPrintf(Host->Console, 30, 31, " Rockets"))
// return GE_FALSE;
break;
}
case 3: // SHREDDER
{
textClip.Left = evItemWidth;
textClip.Right = 2*evItemWidth;
break;
}
}
if (!geEngine_DrawBitmap(hud->theEngine, hud->HUDTextures, &textClip, hud->theHUDLocation->Left + 9*evItemWidth - 15, hud->theHUDLocation->Top))
return GE_FALSE;
return GE_TRUE;
}
That's it. It would be fairly easy to add the showing of weapons, and can easily be modified to suit your customised GTest.
You will need to download the zip file from the original tutorial, which contains the old source files and bitmaps. Ignore everything in the zip file appart from the bitmap file. Place this in your bmp directory and change the path at the top of the above hud.c to point to this file.
Any problems with the original code should be pointed to Ewen. Any problems with the modified code can be sent to me.