Navigation

 UI Coding Tutorial #1 Feature Type: Coding Tutorials     Added: 23/04  
  Author: IoN_PuLse 

Learn how the main menu of Quake3 works.

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

Comments »



  Page 1

  Comments ( 0 )


  Features Index

  Home

Copyright © 2001 MGON International AB - Privacy statement