|
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.
Interaction
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
windowDef | Standard window |
animationDef | Non-visible window. Not really used in Doom 3, so it may not work |
editDef | A window that the user can type text in (the 'Server Name' box) |
choiceDef | A 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 |
sliderDef | A 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 |
bindDef | A window that allows the user to bind a key to a command |
listDef | A window that displays a list of items, needs code to work right |
markerDef | Not used in Doom 3, probably doesn't work |
fieldDef | Not used in Doom 3, probably doesn't work |
renderDef | A 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 |
onAction | Runs 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 |
onActionRelease | Runs the event when the action is finished. For most windows, this means 'left mouse button up', but the editDef and choiceDef behave oddly |
onMouseEnter | Runs the event when the mouse enters the window rectangle. Note this event is NOT RELIABLE because of issues with z-ordering and fast mice |
onMouseExit | Runs 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 |
onActivate | Runs 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 |
onDeactivate | Runs the event when the guis is deactivated (opposite of onActivate) |
onEsc | Runs this event when the user presses the escape button on his keyboard |
onEvent | Runs 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 |
onTrigger | Runs this event when the entity it is attached to is triggered. Should be put in the desktop window |
onEnter | Runs this event when the user presses enter in a listDef or editDef (also called when the user double-clicks a listdef) |
onEnterRelease | Runs 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 |
endGame | Ends 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 |
resetCinematics | Resets 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 |
evalRegs | Re-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)
Variable | Type | Default | |
---|
rect | rect (vec4) | 0, 0, 0, 0 | x, y, width, height dimensions of the window. x, y relative to parent |
visible | bool | true | Show or hide the window |
noevents | bool | false | Disable all events for the window |
forecolor | color (vec4) | 1, 1, 1, 1 | Text color |
hovercolor | color (vec4) | 1, 1, 1, 1 | Text color when the cursor is over it |
backcolor | color (vec4) | 0, 0, 0, 0 | Solid background color |
bordercolor | color (vec4) | 0, 0, 0, 0 | Border color |
matcolor | color (vec4) | 1, 1, 1, 1 | Color of the background material |
scale | vec2 | | Unused |
translate | vec2 | | Unused |
rotate | float | 0 | Rotate everything in the window some degrees |
textscale | float | 1 | Uniformly scale the size of the text |
text | string | | Text to display in the window |
background | string | | Background material to use |
varbackground | string | | Unused |
runscript | string | | Unused |
cvar | string | | See: editDef |
choices | string | | See: choiceDef |
choiceVar | string | | See: choiceDef |
bind | string | | See: bindDef |
modelRotate | vec4 | | See: renderDef |
modelOrigin | vec4 | | See: renderDef |
lightOrigin | vec4 | | See: renderDef |
lightColor | vec4 | | See: renderDef |
viewOffset | vec4 | | See: 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
Variable | Type | Default | |
---|
showtime | bool | false | Displays the window time (debug) |
showcoords | bool | false | Displays the window coordinates (debug) |
forceaspectwidth | float | 640 | Force a specific draw surface width (use on desktop window) |
forceaspectheight | float | 480 | Force a specific draw surface height (use on desktop window) |
matscalex | float | 1 | Scales the background material in the x direction |
matscaley | float | 1 | Scales the background material in the y direction |
bordersize | float | 0 | Sets the size of the border, note you must set this to something other than 0 for borderColor to do anything |
nowrap | bool | false | Don't wrap text that can't fit in the rectangle |
shadow | bool | false | Use a black drop-shadow when drawing the text |
textalign | int | 0 | Specify the text alginment (0=left; 1=center; 2=right) |
textalignx | float | 0 | Offset the x value of the text relative to the window |
textaligny | float | 0 | Offset the y value of the text relative to the window |
shear | vec2 | 0, 0 | "shear" the rectangle, which turns it into a parallelogram. Range is 0 to 1 |
wantenter | bool | false | Sends onAction to the window when the user presses enter |
naturalmatscale | bool | false | Uses the real size of the background material |
noclip | bool | false | Don't clip draw operations that lie outside the window rectangle |
nocursor | bool | false | Hide the cursor (use on desktop window) |
menugui | bool | false | This gui is full screen (use on desktop window) |
modal | bool | false | This window eats events if active, used for pop-up boxes |
invertrect | bool | false | This window is upside-down |
name | string | <name> | Set the window name, this overrides the name specified before the open curly brace |
play | string | | Prints a warning message telling you not to use it |
comment | string | | Free form comment area |
font | string | | Sets the font. Choices are "fonts/micro" "fonts/bank" or "fonts/an" |
Window variables (editDef)
Variable | Type | Default | |
---|
maxchars | int | 128 | Maximum number of characters that can be typed |
numeric | bool | false | This control only accepts numbers |
wrap | bool | false | Text larger than the width of the control is wrapped, and a scroll bar (slider) is created if the text goes beyond the height |
readonly | bool | false | Text cannot be edited |
forceScroll | bool | false | The control is forced to scroll to the bottom |
source | string | | File to read the initial text from |
password | bool | false | The control displays asterisks (***) rather than the text |
cvar | string | | The cvar to attach to. It displays the value of the cvar, and changes the cvar when the user changes the text |
liveUpdate | bool | true | If 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 |
cvarGroup | string | | Cvar 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)
Variable | Type | Default | |
---|
choicetype | int | 0 | 0=The cvar contains a 0 based index, 1=The cvar contains a value string |
currentchoice | int | 0 | The currently selected choice |
choices | string | | Semicolon seperated list of choices (Yes;No) |
values | string | <choices> | Semicolon seperated list of values (should have the same number of items as 'choices') |
gui | string | | GUI parm to attach to, will contain the currentchoice as a numeric |
cvar | string | | See: editDef |
liveUpdate | bool | true | See: editDef |
cvarGroup | string | | See: editDef |
Window variables (sliderDef)
Variable | Type | Default | |
---|
low | float | 0 | The minimum value |
high | float | 100 | The maximum value |
stepsize step | float | 1 | The amount to change when the arrow keys are used |
vertical | bool | false | The slider goes up and down |
scrollbar | bool | false | This slider is used as a scroll bar |
thumbshader | string | | Material to use for the "thumb" (the part that moves) |
cvar | string | | See: editDef |
liveUpdate | bool | true | See: editDef |
cvarGroup | string | | See: editDef |
Window variables (bindDef)
Variable | Type | Default | |
---|
bind | string | | Command to bind to |
Window variables (listDef)
Variable | Type | Default | |
---|
horizontal | bool | false | This list goes left-to-right (untested) |
listname | string | | The name of the list. The code uses this to populate the list with items |
tabstops | string | | Comma seperated list of pixel offsets for a multi-column list |
tabaligns | string | | Comma seperated list of 'textalign's for each column |
multipleSel | bool | false | Multiple items can be selected (requires code support) |
Window variables (renderDef)
Variable | Type | Default | |
---|
model | string | | Model (file, not def) to render |
anim | string | | Animation to play |
animClass | string | | entityDef to grab the animation from |
lightOrigin | vec4 | -128,0,0,1 | Position of the light |
lightColor | color (vec4) | 1,1,1,1 | Color of the light |
modelOrigin | vec4 | 0,0,0,0 | Position of the model |
modelRotate | vec4 | 0,0,0,0 | x,y,z rotation |
viewOffset | vec4 | -128,0,0,1 | Position of the camera |
needsRender | bool | true | Dirty flag, set it to true if something changes so the scene is rendered again |
|