Welcome, Guest! Login | Register

UI Tutorial #1 [Print this Article]
Posted by: IoN_PuLse
Date posted: May 05 2003
User Rating: N/A
Number of views: 3610
Number of comments: 0
Description: An introduction to the Q3 UI code.


These tutorials are old and I haven't read through them in a while, they were written by me and I will try to update them and make a few new ones.

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"

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.


static void MainMenu_ExitAction( qboolean result ) {
    if( !result ) {

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.


void Main_MenuEvent (void* ptr, int event) {
    if( event != QM_ACTIVATED ) {

    switch( ((menucommon_s*)ptr)->id ) {


    case ID_SETUP:

    case ID_DEMOS:


    case ID_MODS:

    case ID_EXIT:
  UI_ConfirmMenu( "EXIT GAME?", NULL, MainMenu_ExitAction );

This tells Quake3 what to do if a button gets clicked on. Here different buttons will trigger other menus.


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;


    // 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 ) {

    memset( &s_main, 0 ,sizeof(mainmenu_t) );


    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;


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.


    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.

Rate This Article
This article has not yet been rated.

You have to register to rate this article.
User Comments

No User Comments

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:
Valid XHTML 1.0! Valid CSS!