UI Coding Tutorial #1 When I first
downloaded the Quake3 source, I found the folder within the source
directory called UI. I figured it was just menu stuff, nothing
special and moved on to the server side stuff. But when I was asked
to learn how the menu code works, I sure thought of it differently.
You can do a lot of stuff with the UI code, but in this
tutorial I will be detailing how the UI works.
Open up
ui_menu.c in the UI folder. At the top you will see this:
#include "ui_local.h"
#define
ID_SINGLEPLAYER 10 #define ID_MULTIPLAYER 11 #define
ID_SETUP 12 #define ID_DEMOS 13 #define ID_CINEMATICS 14
#define ID_MODS 15 #define ID_EXIT 16
#define
MAIN_BANNER_MODEL "models/mapobjects/banner/banner5.md3" #define
MAIN_MENU_VERTICAL_SPACING 34
Here is all the initial
definitions for the main menu buttons. You can probably figure out
what’s what. Near the bottom, you will see model definitions, that
is for the “Quake3” model that’s at the top of the main menu. All
the text-based buttons (even the ones that pulse) are code-based,
without pictures. I’ll show you how to put your own picture-buttons
later on.
Now for the next bit:
typedef struct { menuframework_s menu;
menutext_s singleplayer; menutext_s multiplayer;
menutext_s setup; menutext_s demos; menutext_s
cinematics; menutext_s mods; menutext_s exit;
qhandle_t bannerModel; } mainmenu_t;
static
mainmenu_t s_main;
Here are additional definitions,
basically what each button is. On the left you can see menutext_s’s,
and a qhandle_t. These just tell Quake3 what type of button you’re
using. Also, you can just comment out the bannermodel reference here
and the button will not show up at all.
/* ================= MainMenu_ExitAction
================= */
static void MainMenu_ExitAction( qboolean result ) { if(
!result ) { return; } UI_PopMenu(); UI_CreditMenu();
}
This defines the ExitAction (when you press
quit). UI_PopMenu tells Quake3 to kill any menus that are running,
then the creditmenu is the menu where you see the credits, and that
is in ui_credits.c.
/*
================= Main_MenuEvent =================
*/ void Main_MenuEvent
(void* ptr, int event) { if( event != QM_ACTIVATED ) {
return; }
switch( ((menucommon_s*)ptr)->id ) {
case ID_SINGLEPLAYER: UI_SPLevelMenu(); break;
case ID_MULTIPLAYER: UI_ArenaServersMenu(); break;
case ID_SETUP: UI_SetupMenu(); break;
case
ID_DEMOS: UI_DemosMenu(); break;
case ID_CINEMATICS:
UI_CinematicsMenu(); break;
case ID_MODS:
UI_ModsMenu(); break;
case ID_EXIT:
UI_ConfirmMenu( "EXIT GAME?", NULL, MainMenu_ExitAction );
break; } }
This tells Quake3 what to do
if a button gets clicked on. Here different buttons will trigger
other menus.
/*
=============== Main_MenuDraw ===============
*/ static void
Main_MenuDraw( void ) { refdef_t refdef; refEntity_t ent;
vec3_t origin; vec3_t angles; float adjust; float x,
y, w, h; vec4_t color = {0.5, 0, 0, 1};
// setup the refdef
memset( &refdef, 0, sizeof( refdef )
);
refdef.rdflags = RDF_NOWORLDMODEL;
AxisClear(
refdef.viewaxis );
x = 0; y = 0;
Here you
can change the w and h variables (width and height) to whatever you
want. (Try setting w = 640 and h = 480) =)
w = 500; h = 60; UI_AdjustFrom640( &x,
&y, &w, &h ); refdef.x = x; refdef.y = y;
refdef.width = w; refdef.height = h;
adjust = 0;
// JDC: Kenneth asked me to stop
this 1.0 * sin( (float)uis.realtime / 1000 ); refdef.fov_x = 60 + adjust;
refdef.fov_y = 19.6875 + adjust;
refdef.time =
uis.realtime;
origin[0] = 300; origin[1] = 0;
origin[2] = -32;
trap_R_ClearScene();
// add the model
memset( &ent, 0, sizeof(ent) );
adjust = 5.0 * sin( (float)uis.realtime / 5000 );
VectorSet( angles, 0, 180 + adjust, 0 ); AnglesToAxis(
angles, ent.axis ); ent.hModel = s_main.bannerModel;
VectorCopy( origin, ent.origin ); VectorCopy( origin,
ent.lightingOrigin ); ent.renderfx = RF_LIGHTING_ORIGIN |
RF_NOSHADOW; VectorCopy( ent.origin, ent.oldorigin );
trap_R_AddRefEntityToScene( &ent );
trap_R_RenderScene( &refdef );
// standard menu drawing
Menu_Draw( &s_main.menu );
if
(uis.demoversion) { UI_DrawProportionalString( 320, 372, "DEMO
FOR MATURE AUDIENCES DEMO", UI_CENTER|UI_SMALLFONT, color );
UI_DrawString( 320, 400, "Quake III Arena(c) 1999-2000, Id
Software, Inc. All Rights Reserved", UI_CENTER|UI_SMALLFONT, color
); } else { UI_DrawString( 320, 450, "Quake III Arena(c)
1999-2000, Id Software, Inc. All Rights Reserved",
UI_CENTER|UI_SMALLFONT, color ); } }
Most of
this just talks about how the model will be drawn, but the bottom
part adds the Id Software stuff, you might want to take out if it
doesn’t match your new UI. For the next part I’m only going to go
over one button, because the rest are about the same.
void UI_MainMenu( void ) { int y;
int style = UI_CENTER | UI_DROPSHADOW;
trap_Cvar_Set(
"sv_killserver", "1" );
if( !uis.demoversion &&
!ui_cdkeychecked.integer ) { char key[17];
trap_GetCDKey( key, sizeof(key) ); if( strcmp( key,
"123456789" ) == 0 ) { UI_CDKeyMenu(); return; } }
memset( &s_main, 0 ,sizeof(mainmenu_t) );
MainMenu_Cache();
s_main.menu.draw = Main_MenuDraw;
s_main.menu.fullscreen = qtrue; s_main.menu.wrapAround =
qtrue; s_main.menu.showlogo = qtrue;
y = 134;
s_main.singleplayer.generic.type = MTYPE_PTEXT;
s_main.singleplayer.generic.flags =
QMF_CENTER_JUSTIFY|QMF_PULSEIFFOCUS;
s_main.singleplayer.generic.x = 320;
s_main.singleplayer.generic.y = y;
s_main.singleplayer.generic.id = ID_SINGLEPLAYER;
s_main.singleplayer.generic.callback = Main_MenuEvent;
s_main.singleplayer.string = "SINGLE PLAYER";
s_main.singleplayer.color = color_red;
s_main.singleplayer.style = style;
y +=
MAIN_MENU_VERTICAL_SPACING;
Ok, if you notice at the
top, they define “y” and “style”. These just basically save the
programmers time. The “y” is used to find the spacing for the menu
buttons. Below the first button, you’ll see reference to the
MAIN_MENU_VERTICAL_SPACING, that was defined before. It’s using a
math formula to figure out where to stick the button on the y axis.
(the x is the same for all of them) Now the style is defined, and so
the programmers just have to type “style” in for the
s_main.singleplayer.style (example) because the contents of “style”
is the same for all the buttons.
y += MAIN_MENU_VERTICAL_SPACING;
s_main.setup.generic.type = MTYPE_PTEXT;
s_main.setup.generic.flags =
QMF_CENTER_JUSTIFY|QMF_PULSEIFFOCUS; s_main.setup.generic.x =
320; s_main.setup.generic.y = y; s_main.setup.generic.id =
ID_SETUP; s_main.setup.generic.callback = Main_MenuEvent;
s_main.setup.string = "SETUP"; s_main.setup.color =
color_red; s_main.setup.style = style;
Here is
the main definitions for the SETUP button. You will see the math
thingy at the top, and on the second line it tells Quake3 what
generic type the button is. Then the flags, and the only real
important thing there is that the button will “pulse” when the mouse
is over it. The next is the x coordinate, after that the y, then it
tells Quake3 to find the ID_SETUP when the button is clicked.
(remember that from before?) Then it tells Quake3 which menu the
action will be taking place in, then what text is actually going to
be displayed, (change it to configuration for fun) what color is
being used, and then the style part. That’s basically how the
Quake3 Main Menu works, and later I will do more tutorials about how
to actually change things, etc.
-IoN_PuLse
Latest Features UI Coding
Tutorial #1 - IoN_PuLse - 22/04 The Q3Center
Map Contest Verdict - PaRaDoX - 30/03 Q3 Console by
Keen - Bigdog - 11/03 Quaker's
Day - Marine71 - 09/03 Q3 Mods – End
Of February - IoN_PuLse -
05/03
|