Making DOOM 3 Mods : GUIs

After working with the Doom 3 GUIs a while you will both love and hate them. GUI of course stands for Graphical User Interface. The GUI system is used for every single menu in the game, as well as the HUD, and random control panels you see placed around the levels. The system in Doom 3 is pretty simple in that there aren't very many commands, but the ones that are there are very powerful.

The whole system is based around nested windows and events (like javascript). There is one top level window called 'Desktop', then one or more windows that are 'children' of the desktop, then zero or more windows that are children of the children, etc...

Here's an example of a very simple GUI that could be used as a display on a computer screen or something (since it's not really interactive):

windowDef Desktop {
    rect      0, 0, 640, 480
    backcolor 0, 0, 0, 1

    windowDef Text1 {
        rect        35, 50, 285, 200 
        backcolor   0.5, 0.6, 0.6, 0.3
        bordercolor 0.5, 0.6, 0.6, 1
        bordersize  1
        text        "This is some Text!"
        font        "fonts/bank"
        textscale   0.5
        textalign   1
        shadow      1
        forecolor   0.7, 0.7, 0.4, 1
        visible     1

        windowDef Text2 {
            rect        5, 80, 275, 20 
            backcolor   0.0, 0.0, 0.0, 0.3
            bordercolor 1, 1, 1, 1
            bordersize  1
            text        "This is more Text!"
            font        "fonts/bank"
            textscale   0.5
            textalign   1
            textaligny  -13
            forecolor   0.4, 0.7, 0.7, 1
            visible     1

    windowDef Text3 {
        rect        350, 400, 285, 200 
        text        "Amazing"
        textalign   1

To see what this looks like, paste the above code in to a file called "mygui.gui" in the "guis" subdirectory of your mod, then type, "testGUI guis/mygui.gui" in the console. If you want to change something and see the results, you don't have to exit the game. You can just type, "reloadGuis" in the console, and the change should show up immediately. For more advanced guis, you may have to hit escape and use "testGui" again after using "reloadGuis".

Events and Scripts

Now let's go ahead and make it a bit more interesting. Change the definition for Text3 to this:

windowDef Text3 {
    rect        350, 400, 285, 200
    text        "Amazing"
    textalign   1

    onTime 0 {
        transition rect "350, 400, 285, 200" "350, 300, 285, 200" "2000" ;
        transition forecolor "1 1 1 1" "1 0 0 1" "1000" ;
    onTime 1000 {
        transition forecolor "1 0 0 1" "0 0 1 1" "1000" ;
    onTime 2000 {
        transition rect "350, 300, 285, 200" "350, 400, 285, 200" "2000" ;
        transition forecolor "0 0 1 1" "1 1 0 1" "1000" ;
    onTime 3000 {
        transition forecolor "1 1 0 1" "1 1 1 1" "1000" ;
    onTime 4000 {
        resetTime 0 ;

Amazing indeed. This simple change makes the amazing text bounce up and down while changing colors.

This code basically says:

  • At time 0: Start a transition of the rectangle from "350, 400, 285, 200" to "350, 300, 285, 200" over 2 seconds, and start transitioning the foreground color from white to red over 1 second.
  • At time 1000 (1 second): Transition from red to blue over 1 second.
  • At time 2000 (2 seconds): Transition the rectangle back down over 2 seconds, and transition the color from blue to yellow.
  • At 3 seconds: Trasition from yellow back to white over 1 second.
  • At 4 seconds, start all over again.

The built in timer and the transition commands are very powerful tools. It should be noted that each window keeps it's own timer, so 'resetTime 0' only resets the time for the current window. If we wanted to reset the time for another window, we could say "resetTime Text1 0" and if we wanted to transition properties of a different window, we would say "transition Text1::foreground ..."

The other thing to note is a semicolon is required at the end of every line inside a script block. We will learn about script blocks in a second, but anything that starts with "on" is probably a script block.


Our gui is currently very fascinating to look at, but we can't do anything with it... yet.

There is a small issue witth the 'testGUI' command. You'll notice you don't have a cursor when you testGUI, but if you apply this gui to a surface in game, the cursor will appear. This is because world guis are handled a bit differently than menu guis. For testing purposes, we're going to turn our world gui into a menu gui so we can use the cursor with the 'testGUI' command. Set 'menugui 1' in the desktop window, and you will see your cursor when you testGUI (side note: to turn off the cursor for world guis, set 'nocursor 1').

Even though you can move your cursor around, you still can't click stuff. Why don't we go ahead fix that now. Add "notime 1" to Text3, and replace "onTime 4000" with this:

onTime 4000 {
    set notime 1 ;
onAction {
    set notime 0 ;
    resetTime 0 ;

Assuming all went well, the amazing text should do it's little dance every time you click it. Amazing.

If typing text really isn't your thing, there is a gui editor. Type 'editGuis' at the console to launch it. A word of warning: It tends to crash. a lot. And it doesn't handle very complex guis with a lot of transitions too well (it only shows the windows in their initial states). I use it to lay out a basic idea of what I want, then go in to the .gui file to fix it up and add scripts.

GUI reference

Window Types

windowDefStandard window
animationDefNon-visible window. Not really used in Doom 3, so it may not work
editDefA window that the user can type text in (the 'Server Name' box)
choiceDefA window that allows the user to toggle between a few different choices. The yes/no windows are choice defs, as is the game type window in the create server menu
sliderDefA window similar to a scroll bar. The volume control is a slider def, as are the scroll bars in multi-line edit windows and list windows
bindDefA window that allows the user to bind a key to a command
listDefA window that displays a list of items, needs code to work right
markerDefNot used in Doom 3, probably doesn't work
fieldDefNot used in Doom 3, probably doesn't work
renderDefA window that displays a rendered 3d scene

Event handlers

onTime <time>Runs the event at T+<time> milliseconds. The timeline is not static, and can be reset with the 'resetTime' command
onNamedEvent <event>Allows the code to trigger custom events. The 'overwrite save game' window is implemented with a named event
onActionRuns the event when the user does something with the window. For most windows, this means 'left mouse button down', but it is also fired when the text changes in an editDef or the choice changes in a choiceDef
onActionReleaseRuns the event when the action is finished. For most windows, this means 'left mouse button up', but the editDef and choiceDef behave oddly
onMouseEnterRuns the event when the mouse enters the window rectangle. Note this event is NOT RELIABLE because of issues with z-ordering and fast mice
onMouseExitRuns the event when the mouse exits the window rectangle. Note this event is also NOT RELIABLE. It is very possible to get a mouse enter, but no mouse exit message
onActivateRuns the event when the gui is first activated (for world guis, when the user first puts his cursor over the gui). This should be put in the desktop window
onDeactivateRuns the event when the guis is deactivated (opposite of onActivate)
onEscRuns this event when the user presses the escape button on his keyboard
onEventRuns this event every frame. The scripts in here should be very small because.. well.. they are run every frame. Internally this is called onFrame, but we had to leave it as onEvent so as to not break existing guis
onTriggerRuns this event when the entity it is attached to is triggered. Should be put in the desktop window
onEnterRuns this event when the user presses enter in a listDef or editDef (also called when the user double-clicks a listdef)
onEnterReleaseRuns this event when the user lets go of the enter key. listDef does not use this event

Script commands

set [window::]<variable> <value>Sets some variable to some value
setFocus <window>Sets the focus to some window
endGameEnds the game (sets g_nightmare to true and calls disconnect)
resetTime [window] [time]Resets the timeline for some window to some time. If you specify window, you have to specify time. If you don't specify anything, it resets the current window to time 0
showCursor <bool>Shows or hides the cursor
resetCinematicsResets the cinematics playing in the window to frame 0
transition [window::]<variable> <from> <to> <time> [ <accel> <decel> ]Linearly interpolates a variable from one value to another over time (in milliseconds). accel and decel are values < 1 that specify a fraction of the time spent accelerating or decelerating (defaults to 0). If accel + decel > 1 it normalizes them.
localSound <sound>Plays a sound
runScript <function>Runs the specified function in the game script
evalRegsRe-eveluates window registers (variables). Should not be needed anymore

Window registers
These can be changed at run time with the set command (or transition for vecs and floats)

rectrect (vec4)0, 0, 0, 0x, y, width, height dimensions of the window. x, y relative to parent
visiblebooltrueShow or hide the window
noeventsboolfalseDisable all events for the window
forecolorcolor (vec4)1, 1, 1, 1Text color
hovercolorcolor (vec4)1, 1, 1, 1Text color when the cursor is over it
backcolorcolor (vec4)0, 0, 0, 0Solid background color
bordercolorcolor (vec4)0, 0, 0, 0Border color
matcolorcolor (vec4)1, 1, 1, 1Color of the background material
rotatefloat0Rotate everything in the window some degrees
textscalefloat1Uniformly scale the size of the text
textstringText to display in the window
backgroundstringBackground material to use
cvarstringSee: editDef
choicesstringSee: choiceDef
choiceVarstringSee: choiceDef
bindstringSee: bindDef
modelRotatevec4See: renderDef
modelOriginvec4See: renderDef
lightOriginvec4See: renderDef
lightColorvec4See: renderDef
viewOffsetvec4See: renderDef

Additional user variables may be defined with the 'definevec4', 'definefloat', and 'float' commands. Note you cannot set the initial value for the variable (it will always be 0). There are guis in Doom 3 that specify an initial value, but it is ignored. Example:

windowDef Desktop {
    rect      0, 0, 640, 480
    backcolor 0, 0, 0, 1
    float     myfloat
    definefloat myotherfloat
    definevec4  myvec4

Window variables
These can be changed in the file, but cannot be changed at run time

showtimeboolfalseDisplays the window time (debug)
showcoordsboolfalseDisplays the window coordinates (debug)
forceaspectwidthfloat640Force a specific draw surface width (use on desktop window)
forceaspectheightfloat480Force a specific draw surface height (use on desktop window)
matscalexfloat1Scales the background material in the x direction
matscaleyfloat1Scales the background material in the y direction
bordersizefloat0Sets the size of the border, note you must set this to something other than 0 for borderColor to do anything
nowrapboolfalseDon't wrap text that can't fit in the rectangle
shadowboolfalseUse a black drop-shadow when drawing the text
textalignint0Specify the text alginment (0=left; 1=center; 2=right)
textalignxfloat0Offset the x value of the text relative to the window
textalignyfloat0Offset the y value of the text relative to the window
shearvec20, 0"shear" the rectangle, which turns it into a parallelogram. Range is 0 to 1
wantenterboolfalseSends onAction to the window when the user presses enter
naturalmatscaleboolfalseUses the real size of the background material
noclipboolfalseDon't clip draw operations that lie outside the window rectangle
nocursorboolfalseHide the cursor (use on desktop window)
menuguiboolfalseThis gui is full screen (use on desktop window)
modalboolfalseThis window eats events if active, used for pop-up boxes
invertrectboolfalseThis window is upside-down
namestring<name>Set the window name, this overrides the name specified before the open curly brace
playstringPrints a warning message telling you not to use it
commentstringFree form comment area
fontstringSets the font. Choices are "fonts/micro" "fonts/bank" or "fonts/an"

Window variables (editDef)

maxcharsint128Maximum number of characters that can be typed
numericboolfalseThis control only accepts numbers
wrapboolfalseText larger than the width of the control is wrapped, and a scroll bar (slider) is created if the text goes beyond the height
readonlyboolfalseText cannot be edited
forceScrollboolfalseThe control is forced to scroll to the bottom
sourcestringFile to read the initial text from
passwordboolfalseThe control displays asterisks (***) rather than the text
cvarstringThe cvar to attach to. It displays the value of the cvar, and changes the cvar when the user changes the text
liveUpdatebooltrueIf set, the cvar is changed as the text is changed, and the text changes as the cvar changes. Otherwise only changes when "cvar read" or "cvar write" is sent
cvarGroupstringCvar group this item belongs to. This makes it possible to update all the cvars of a group with a cvar read/write command. For example, "cvar read audio" would update all controls with the cvarGroup set to audio

Window variables (choiceDef)

choicetypeint00=The cvar contains a 0 based index, 1=The cvar contains a value string
currentchoiceint0The currently selected choice
choicesstringSemicolon seperated list of choices (Yes;No)
valuesstring<choices>Semicolon seperated list of values (should have the same number of items as 'choices')
guistringGUI parm to attach to, will contain the currentchoice as a numeric
cvarstringSee: editDef
liveUpdatebooltrueSee: editDef
cvarGroupstringSee: editDef

Window variables (sliderDef)

lowfloat0The minimum value
highfloat100The maximum value
float1The amount to change when the arrow keys are used
verticalboolfalseThe slider goes up and down
scrollbarboolfalseThis slider is used as a scroll bar
thumbshaderstringMaterial to use for the "thumb" (the part that moves)
cvarstringSee: editDef
liveUpdatebooltrueSee: editDef
cvarGroupstringSee: editDef

Window variables (bindDef)

bindstringCommand to bind to

Window variables (listDef)

horizontalboolfalseThis list goes left-to-right (untested)
listnamestringThe name of the list. The code uses this to populate the list with items
tabstopsstringComma seperated list of pixel offsets for a multi-column list
tabalignsstringComma seperated list of 'textalign's for each column
multipleSelboolfalseMultiple items can be selected (requires code support)

Window variables (renderDef)

modelstringModel (file, not def) to render
animstringAnimation to play
animClassstringentityDef to grab the animation from
lightOriginvec4-128,0,0,1Position of the light
lightColorcolor (vec4)1,1,1,1Color of the light
modelOriginvec40,0,0,0Position of the model
modelRotatevec40,0,0,0x,y,z rotation
viewOffsetvec4-128,0,0,1Position of the camera
needsRenderbooltrueDirty flag, set it to true if something changes so the scene is rendered again

Copyright © 2004 id software