A massive demonic invasion has overwhelmed the Union Aerospace Corporation's Mars Research Facility, leaving only chaos and horror in its wake. As one of only a few survivors, you must fight your way to hell and back against a horde of evil monsters.

Post tutorial Report RSS DOOM 3 GUI scripting tutorials

This is a series of tutorials on how to construct GUIs for Doom 3.

Posted by on - Advanced UI/HUD

Tutorials originally created by Zeh on Doom3World. Mirrored here for archival purposes. There is a sequel to these tutorials that go over how to create a custom hud here.

Note all example files for these tutorials have been mirrored here.

If you wish to discuss Doom 3 modding I recommend joining the Id Tech 4 discord server here.

Welcome to the first in a series of tutorials covering GUI scripting for the DOOM 3 engine. Actually, this is just an introduction to the GUI concept in DOOM, and it will not cover scripting syntax just yet. This introductory tutorial aims to make people from other backgrounds comfortable before they start editing GUI scripts for the DOOM 3 engine, or at least showing in what's used for and how. It's a little slow and contains lots of ramblings, but it's important to give a background to the whole concept before picking up the pace in a more technical fashion.


Complete GUI Scripting - 0: A GUI script primer

FPS engines are getting more complex everyday. A few years ago, a FPS engine was all about 3D renderization: it was all about how the player would see the world. This has changed, however, as engines started getting more polished and started offering more features instead of just pushing polygon counts higher.

One of these features is a more powerful approach to user interfaces. All games have GUIs - Graphical User Interfaces - such as menus and buttons, but it wasn't until recently that mod authors acquired the ability to change the way the normal GUI work or look like for a particular engine without having the original (engine) source code.

The Unreal and the Quake 3: Arena engines were the first "big name" engines to give mod authors the ability to change the game appearance completely, and more importantly, in a more friendly fashion. For example, mod authors could add a stylized "Select Class" window for their class-based mod, or add a new voting window, or showing a new map when a key was pressed

For id Software, while it was possible to modify the appearance of on-screen menus and dialogs before, the Team Arena menu format was the first step towards a very practical and powerful GUI scripting system; this has evolved a lot and is now available in the DOOM 3 engine as the GUI scripting system.

I think the GUI interaction in the game is really powerful as an interactive paradigm. It doesn't require additional controls; you're interacting with something people are familiar with. Allowing you to interact with complex displays is powerful, much more than adding three more keys to do something.


John Carmack, Nov 3rd, 2003

GUIs script are, in fact, very powerful tools for creating interfaces in the engine. You're not only able to create general-use user window interfaces and change the game windows' aspect, but also create in-game interfaces. For example, these are all examples of GUI scripts in the DOOM 3 engine (click to see a bigger version):

tut0 mainmenu

The main menu. This includes all submenus and options available.

tut0 hud

The player HUD (short for "Heads Up Display"). It's, of course, everything that overlays the game action, showing the player health, ammo, etc.

tut0 pda

The user PDA, one of the most complex GUI scripts in the game.

tut0 weapon

A weapon overlay (zoomed) - this actually shows the amount of ammo you have on the weapon itself, instead of depending on the HUD only. This is a non interactive display, but it is still a GUI.

tut0 elevator

A lift panel, used to call the lift. Notice the arrow cursor on the middle of it - this is an in-game GUI that can be controlled by the player, and is rendered on game architecture.

tut0 health

Pretty much like the lift button, this is a health station. When clicked, it gives the player more health -- one of the many examples of interaction with the player.

tut0 mpmenu

Another complex GUI script, the multiplayer menu provides a more friendly access to multiplayer features.

tut0 mpscoreboard

And of course, the multiplayer scoreboard is a mid-game window that shows information about the match.

All these examples are pure GUI scripts, plain and simple. GUIs are nothing more than a script (text) file, which loads external images (materials) and provides simple script functionality for user interactions. While it is mainly a client technology, it is able to communicate with the game, calling functions, getting variables and overall interfacing with the game engine. The GUI that this script generates can later be displayed (as a new window), used on-screen (as a HUD), or used on an in-game model, to work as a panel or as a computer screen (in this case, the user is able to interact with the in-game "screen" by using a mouse cursor in place of the gun crosshair).

To establish a parallel to more a common paradigm, think of these GUI scripts as HTML files. It has its own code and variables, but all visual elements are defined inside of it in a textual fashion. The interactivity code (triggered at certain times, or when the user activates a button, for example) is also placed on this file. Visual elements aren't really created in a run-length style, however - meaning you can place then wherever you want on-screen, in any absolute XY position, and not care about text flow.

Because of this, having a background in visual programming - be it HTML, Visual Basic, Actionscript or Lingo - will make GUI scripting really easy to learn. Having a background in Team Arena, Return to Castle Wolfenstein or Enemy Territory menu scripting will make it even easier, if at all needed.

An addendum from a guy who works with Flash 100% of the time on his daily job: in an interview I can't find right now, someone at id Software mentioned creating GUIs for DOOM 3 would be a lot like creating Flash movies. Well, that is correct, but only if you are used to create code-driven interfaces, since the GUI system on DOOM 3 doesn't make use of timelines (and this is, in some ways, a good thing). You won't have complex animations built by moving and tweening stuff around: you *will* have to *write* code for that. Keep in mind that it's the best way to do it, anyways, and I almost never use timeline animations on Flash itself, no matter how complex my animations are (and sites I build have animations on simply everything). So this is not really a limitation, and if you work with Flash using the timeline, don't expect to find something similar when building GUIs for DOOM 3.

With that said, the next tutorial presents the GUI script syntax and shows how to create a newscript from scratch.


Welcome to the second of a (hopefully) long list of tutorials on GUI scripting for DOOM 3. On this second tutorial (which is in fact labeled #1), we'll finally get our hands a bit dirty by creating a new GUI script. In the end, we will also preview it in-game.


Complete GUI scripting - 1: Your first GUI script


Like I said on the previous introduction tutorial, a GUI script is much like an HTML file. You have some kind of text that defines elements that you'll have on screen, and image files that make it become alive.

Also much like HTML content, GUI scripts are defined by one main tag, filled with mini-tags that create each visual element. While in HTML this root tag would be something like <HTML>, in GUI scripting we use a windowDef.

A windowDef is the heart, the feet and the brain of every GUI script. There's much more about windowDefs that will be covered later, but for now, let's just say that we need one windowDef so we can handle all of our visual elements. So let's go and create a windowDef. Here's the magic code from out of nowhere:

windowDef Desktop {
}

This is how a windowDef works: you start with the windowDef declaration, a name you give to it, an opening bracket, and later a closing bracket. Everything between the brackets is content that belongs to this windowDef.

One thing that's important to notice is that it has been given the "Desktop" name. All GUIs have their root element called "Desktop".

We haven't, however, added any attribute to this windowDef. While it exists, it won't be shown (or do anything at all) for a number of reasons. The first thing to do is to give it a rect, which defines the boundaries of this windowDef element.

windowDef Desktop {
  rect         0, 0, 640, 480
}

To use it, you just type the attribute name, followed by the value. The rect attribute takes four values - x, y, width, and height, and thus all values are presented in the same line, separated by commas.

One thing to notice is that the 640x480 size is not really expressed in pixels. In DOOM 3 scripting, it's as if the screen is always 640x480, so this basically means that this windowDef will take 100% of the screen, no matter in which resolution the game is being ran.

With that put, we can add more windowDefs to our screen, or change other attributes for this Desktop windowDef. Although this would hardly be used in a real GUI, let's add a bit more sauce to this Desktop.

windowDef Desktop {
  rect         0, 0, 640, 480
  bordersize   3
  bordercolor  1, 1, 1, 1
  backcolor    0.5, 0.5, 0.5, 1
  visible      1

}

Okey. What we did here with the bordersize attribute is to say this windowDef will have a border displayed, and its color is defined on the bordercolor attribute. While the bordersize attribute is pretty much straightforward - it draws a border to the inside of the windowDef, with its stroke strength being the number of pixels we used in the parameter - the bordercolor needs some clarification.

This is one of the many attributes that take colors as a parameter - and color four GUI scripts mean four things: R, G, B, and alpha. Each of the RGB values represent one of three RGB values that compose a color - and in this case, 0 is black (no light) and 1 is full light. Any values between 0 and 1 are allowed, and are calculated to reach the final color. Since the first three values here are 1, it means full red, green, and blue intensity, thus white. So this "1, 1, 1" value is the same as 255, 255, 255 in RGB values or #ffffff in hexadecimal colors.

The last parameter is alpha, and it uses a similar approach as the one used on the RGB values: 0 means total transparency, and 1 means total opacity. All values between 0 and 1 are allowed, so 0.5 would be 50% of transparency. In the end, this "1, 1, 1, 1" means total white.

Of course, the same applies to the backcolor parameter that follows. This is the color used in the background, and the "0.5, 0.5, 0.5, 1" value is easy: 50% of white, with total opacity: gray.

Following all these attributes is the visible attribute, which, as a boolean value, can be set to either 1 or 0. In this case it's set to 1, meaning it is visible. This is merely a formality, but works to illustrates the range of different attributes we could have on a single windowDef.

After setting our main stage, it's time to start populating it, so we'll add a few more windowDefs. Like I said above, this Desktop windowDef is the main container for our GUI, so subsequent windowDef elements will be its child. As we did with the attributes, we just nest these new windowDefs into it. Like this,

windowDef Desktop {
  rect         0, 0, 640, 480
  bordersize   3
  bordercolor  1, 1, 1, 1
  backcolor    0.5, 0.5, 0.5, 1
  visible      1
  windowDef myMessage {
  }
}

You'll notice I gave it a new name - since we're now getting into what we want, it's advisable to create names that'll help you in the process of coding your script. These names will never be seen by the player, of course, but they'll be used on scripting.

With our new child windowDef working, it's time to set some of its attributes.

windowDef Desktop {
  rect         0, 0, 640, 480
  bordersize   3
  bordercolor  1, 1, 1, 1
  backcolor    0.5, 0.5, 0.5, 1
  visible      1
  windowDef myMessage {
    rect         10, 10, 614, 40
    bordersize   1
    bordercolor  1, 0, 0, 1
    backcolor    0, 0, 0, 0.5
    text         "HELLO WORLD"
    textscale    0.5
    font         "fonts/english"
    textalign    1
    forecolor    0,0,1,1
  }
}

Okey, a bunch of new attributes; although pretty self-explanatory, there's some things that need to be explained here.

First, notice the first rect attribute. I wanted it to be centered on screen, a little far from the corners, so I used a x/y position of 10, 10... and a width of 614, and not 620 (which would be the normal thing to do - 640 - 10 (from the left) - 10 (from the right). Why?

The thing is, nested windowDefs have their x/y position relative to its parent, so our myMessage windowDef position is relative to the Desktop position. Normally, this wouldn't be a problem, since Desktop is at 10,10, but the DOOM 3 GUI system takes borders into account. Since Desktop has a bordersize of 3, the 0,0 position of it is not on 0,0 of the screen, but rather on 3,3. The myMessage position of 10,10 then translates to 13,13 - and then the width has to be 614 so it would be centered (640 - 13 - 13).

I then used a different bordersize on this windowDef (1) for a thinner border, a bordercolor of "1,0,0,1" (that is, full red) and a backcolor of 0,0,0,0.5 (a black with 50% opacity).

One new attribute now is the text one - the everlasting message "HELLO WORLD". This is actually what it looks like: this defines which message will be shown on this windowDef.

Then we set the text scale by setting the textscale attribute, 1 being the original size of the "text material". In this case, 0.5 means that we're using it at half the size, which is pretty big for our purposes.

The font attribute says which font file we'll be using for this text. In this case, we're using one of the built-in fonts, but new fonts can be created and used here (I also expect to cover this on a later tutorial).

The textalign attribute is then used to say how this text is aligned to the windowDef bounding box - 0 meaning left, 1 meaning centered, and 2 meaning right. In this case we're using the value 1, which of course means that the text is centered in the windowDef and ultimately centered on screen.

Finally, the forecolor is a color attribute that says which color will be used in the foreground of this windowDef - meaning, in this case, the text itself. With the value of "0,0,1,1", it means we have a full blue as the text color.

But of course, GUI scripts won't be always made of crappy boxes and colors; this was merely an example. It's time for us to get into the real thing, and start adding real content to our GUI script. And that means adding images as materials. I'll be using a quick image I made, grab it from the source files (on the end of this tutorial) if you want to test it too.

windowDef Desktop {
  rect         0, 0, 640, 480
  bordersize   3
  bordercolor  1, 1, 1, 1
  backcolor    0.5, 0.5, 0.5, 1
  visible      1
  windowDef myMessage {
    rect         10, 10, 614, 40
    bordersize   1
    bordercolor  1, 0, 0, 1
    backcolor    0, 0, 0, 0.5
    text         "HELLO WORLD"
    textscale    0.5
    font         "fonts/english"
    textalign    1
    forecolor    0,0,1,1
  }
  windowDef myPic {
    rect         249, 164, 187, 165
    background   "tut1_ranger"
    matcolor     1,1,1,1
    visible 1
  }
}

Now we're making progress. The myPic windowDef is much more like the stuff you'd normally find on real GUIs: just an image and some attributes.

First there's the mandatory rect attribute. In this case, it resembles a box and sits on the middle of our Desktop screen.

Then, the background attribute. This is simply a filename - the name of a material we wish to use. There a few catches, however.

First, this is relative to the base DOOM 3 dir - so if you are running the main game (no mod), this would translate to "<doom 3 dir>/base/<filename>". Since I have used no directory name on this filename, it means this material must be sitting at the /base dir itself.

Second, the 'background' file is a material in TGA format, sans extension. So in the end it means that it's looking for "/base/tut1_ranger.tga".

And third, the material is scaled to fit the entire background of your windowDef. Unless you use scaling options (to create tiles), no matter which size the windowDef's rect is, it will always have the entire background material shown.

Then comes the matcolor attribute. Despite the name, this is not a color value. It still is in the R,G,B,A format, but the first values differ. They are not absolute values of colors to be used: they are percentages of each channel, meaning how much of each channel will be rendered.

Normally, this value would be "1,1,1,1" for a normal image displaying with 100% opacity. You can subtract from channel values, however - if a material has a matcolor value of "0,1,1,1", for example, its red channel will be left out and the resulting image would be a mix between green and blue. This is actually a very powerful feature; using one single grayscale image, you are able to manipulate its color channels during runtime to use different color settings for the image, as if the image was being colorized - you could use the same grayscale image for both red and blue huds on a red vs blue team setup, for example.

That pretty much sums a basic GUI script. Now, it's time to (finally) test it using the DOOM 3 engine itself. Save the above script as a file - I'm using the tut1.gui name - and place it on the game's /base dir.

Run DOOM 3, pull down the console (CTRL+ALT+~ or ~) and type:

testgui tut1.gui

Ta-da! Your test GUI should load and display on the whole screen. Of course, use whichever name you used when creating your .gui file. In any case, check the result of the one I created below.

tut1 final

Yeah, it's an ugly screen, but it's a real GUI script, and serves its purpose.

Well, that pretty much sums it all. There are still lots to be covered, of course, but we'll get there.

Download source and example files


Hi, and welcome to the third tutorial in the Complete GUI Scripting series, courtesy of doom3world.org publishing and partners. In this tutorial you'll learn how to set up and use the built-in GUI editor, to create GUI scripts using a more visual approach. And while the core of script editing will always be done on the script file itself, this GUI editor will be an invaluable tool in designing and previewing them.


Complete GUI Scripting - 2: Using the built-in GUI editor


Something that's cool with the DOOM 3 engine is that it has several built-in editors, allowing you to edit the game content right out of the box. While this can be pretty wacky - people would have to run the game, *then* the editor to open some content for edition - it means that you can preview whatever you are doing in real time.

This is pretty well know for map editing - the map editor is integrated with the game - but what many people don't know is that many other editors are integrated into the game too - and this include, among many others, a GUI editor.

To launch the GUI editor, the normal thing to do is to fire up the game, and type

editguis

DOOM 3 will 'close' and the GUI editor will launch.

There is, however, one catch. Like other editors, it launches a new windows-like application, and if you are using some low resolution on the game, you'll be stuck in a unusable resolution, and your mouse cursor will probably be gone for some reason.

That's why it's advisable to create a new shortcut for the game, or use a new DOOM 3 script to launch it. To do this, first, duplicate the "Doom 3" shortcut on your system and give it a new name, like "Doom 3 GUI Editor". Right-click it, edit its properties, and change its target from:

<whatever>\Doom3.exe

To:

<whatever>\Doom3.exe +exec run_guieditor.cfg

This allows you to run the game and execute a DOOM 3 script file immediately upon loading. You have to create a this newscript file, though: open up notepad, and type this into it:

seta r_fullscreen "0"
seta r_gamma "1"
seta r_mode "8"
vid_restart
editguis

And save this as run_guieditor.cfg inside your DOOM 3's "/base" folder.

What this script does is:

1. Turn into windowed mode;
2. Reset gamma (so you'll have the original system gamma);
3. Set a high resolution (1600x1200);
4. Restart video
5. Open up the editor.

A note about step 3: even though r_mode is supposed to be 1600x1200, it looks that in this case it just forces the editor to be on a safe resolution. I use 1152x864 on windows myself, and using this shortcut/script combination, it always opens the GUI editor on my own resolution..

With that said, we're ready to open the editor. Double-click the shortcut you're just created, and the editor is launched.

tut2 01

This is the main window on the GUI editor. The first thing you'll notice is the editor name - "Quake IV GUI Editor". This is because this editor was developed by Raven Software, which is producing Quake IV. It was probably put on the DOOM 3 engine to give mod authors an easier start on designing GUIs. At least that's my theory...

Anyways, it's quite a straightforward program. The first thing you'll want to do is to go to the "View > Options..." menu, make sure the "Ignore Desktop Select" option is off on the "General" tab, and go to the "Grid" tab and set your options as follows:

tut2 02

I'm suggesting this to provide an easier editing interface; if you don't turn the grid on, there's no way to know what's inside the 640x480 editing area and it's easy to get lost. Feel free to skip this step, but be warned.

With that done, go to the menu and select "File > New" (or press CTRL+N) to create a new GUI script. Your editing area is filled with an empty GUI script.

tut2 03

On the right side of the screen (or wherever you moved it) you can see the "Navigator" window, which indicates which elements are visible and how they relate to each other. It works a lot like the way you expect a layer list to work on Photoshop, for example. Look at the list and, sure enough, our brand new GUI has only a Desktop windowDef, and it is visible. Clicking on it...

tut2 04

...selects it. This means two things: there's a selection halo visible on the editing area, and the windowDef attributes are visible on the "properties" window.

The selection halo is useful if you want to move the windowDef around, or resize it: just click and drag on a corner/side handler to resize it (as you'd do with any modern graphic editing package), or click and drag the windowDef itself to move. You can also use the keyboard keys: the arrow keys move the windowDef around one pixel at a time, and SHIFT+arrow keys resize it.

On the properties list, you'll see all the attributes we used on our previous lesson to create a new windowDef. In this case of a simple Desktop windowDef, only the rect is available, and that's all it needs anyway.

Since this is our main Desktop windowDef, we won't be messing around with it too much. It's time to add another windowDef to spice things up.

tut2 05

Go to the "Item > New > windowDef" menu and add a new windowDef. Yes, I know, there a lot of other items on the menu that can be added, but hold on, we'll get to that on later lessons...

Well, after adding our new windowDef, it sits there on the GUI. Double-click it and a nifty new window will open, with several properties to be edited. Type in a name for it (I'm using "Title") then go to the "Text" tab, since we'll be doing a text element like we did in the previous lesson. Here's what I've filled it with...

tut2 06

It's pretty easy and you can see how all properties available here translate to the attributes we manually created on the previous lesson. The good thing here is that you don't have to know all property names by memory or look into a GUI reference (which doesn't exist yet anyways): you can just play with the settings and see the result.

After filling the data, press OK to see it applied.

tut2 07

There it is, quite easy. Be sure to resize the windowDef to see all text, of course, as well as position it somewhere you want. Pretty cool, uh? Really easy, and the real-time preview is a real godsend.

We'll add another windowDef: this time, an image, just like we did in the previous tutorial. Select "Item > New > windowDef" again, double click the windowDef that was created, give it a name (I'm using "pentaImage", since I'm using a pentagram doodle) and then go to the "Image" tab. use this data:

tut2 08

What I'm doing here is giving it a backcolor - since I want the drawing to be fully visible - and assigning a material to it. Again, the "material" here refers to the path and filename used for the material. I'm using "tut2_penta" as a name, so this means "<doom3>/base/tut2penta.tga" (the file is attached to the tutorial source files, you can find the link to download it in the end of this tutorial).

There are other options available, but I'll leave them as is for now. Feel free to try them out, though.

Pressing OK will redraw the screen with our new windowDef options.

tut2 09

Sure enough, the image is applied to it. You can resize and move the windowDef, and see how it works visually.

You can will also notice the new properties - background, matcolor, and so on - were applied to the windowDef and are shown on the property window.

Other thing.. it's important to remember that, even though I made one text windowDef and one image windowDef, it doesn't mean you have to *chose* between one of these functionalities. You can have as many properties as you wish on it, and you could combine a background image with text on it, for example. The windowDefs I created are simple examples, but that doesn't mean they *have* to be like that.

Okey, now comes the cool part. While previewing it in the editor is close enough to seeing it in the game, it still isn't... perfect. It's static, and if you're playing with animations or scripting (we aren't yet), you won't be able to see the results.

But now, remember you are still inside of the DOOM 3 engine, and you are, in fact, able to test it right away without having to run it again and using the "testgui" command on the console. Select the "Tools > Viewer" menu (or press CTRL+T)...

tut2 10

...and a new window pops up, which is the GUI viewer rendered by the engine itself...

tut2 11

...and it works! While we have built an static GUI that doesn't depend that much on the in-game player for testing, the GUI viewer is a powerful tool and will help us a lot in the future, when testing animations and scripts.

And, of course, what the GUI editor has created is the GUI script code itself. Save the GUI into a file - I'm using the "tut2.gui" name myself - and have a look at it. This is the code it generates,

windowDef Desktop
{
   rect   0,0,640,480
   windowDef Title
   {
      rect   15,10,200,50
      visible   1
      forecolor   1,1,1,1
      text   "Hello."
      textscale   1
      font   "fonts/english"
   }
   windowDef pentaImage
   {
      rect   190,124,260,261
      visible   1
      background   "tut2_penta"
      matcolor   1,1,1,1
      backcolor   0.50196081,0.25098041,0,1
   }
}

Pure, simple, GUI script code.

This sums it all. To wrap up this lesson, I'd like to say something important. Why didn't I just show how to use the editor in the first place, in the previous lesson, instead of showing how to write scripts from scratch?

Well, to answer that, once again, I'll have to compare GUI scripting to HTML... that is: you can do cool sites on, say, DreamWeaver, but unless you have a wide knowledge of the way HTML code works, you'll never be able to produce quality pages. You know, the same applies to GUI scripts. A WYSIWYG tool is very cool to create GUI files fast and previewing them, but you WILL have to dwell with the code and the way it works deep inside if you want to create GUI scripts that are more than a simple box with text and an image.

Also, sometimes, changing the code itself will be faster and easier than opening a .GUI file on the GUI editor, so keep in mind...: the code is your friend.

On most of the next tutorials, while we'll use the official GUI editor, we'll also get a lot more into technical details, so be sure to play around with this powerful GUI editor in the mean time.

Download source and example files


Welcome to the fourth (#3 as any programmer would know) in the complete GUI scripting tutorials. This lesson won't cover much low-level scripting; it will, rather, show you how to prepare your external assets (materials) and use them on your GUI script. I'll use Adobe Photoshop CS in this lesson, but every technique used in this program can be easily reproduced on other graphic editing packages, including older Photoshop versions. We will transform a PSD file into a GUI script, and this GUI script will be used in later lessons; see this as a workflow tutorial rather then a scripting one.

Notice: this is version 2 of this lesson. I've rewritten most of it and redone all screens and files from scratch after some corrections/advices from patd (on reply, below).


Complete GUI Scripting - 3: Preparing and importing assets


When designing interfaces, no matter which media is the target, it's common for designers to just work on Photoshop (or a similar program) to create the basic layout, then import (or recreate) it in the final production tool. Creating Flash content works like that, creating After Effects content works like that, and GUI scripting couldn't be different; usually, the first step to creating an interface for the DOOM 3 engine would be to create it on your graphics editing package of choice than exporting images to be used by your GUI script.

While creating the general design of an interface is something much bigger and that is beyond the scope of these tutorials, there's a lot that can be said about exporting materials correctly from Photoshop then using them on the DOOM 3 engine. This tutorial will start from a finished PSD file, then we'll export all materials thinking about how they should work, then reassemble them on the GUI editor.

For this lesson, there's a little background that I have to add, to explain the sample PSD we'll be working with. Please bare with me while I go a bit off topic now...

...before the alien/demon invasion, the UAC officials on the UAC base on Mars were very worried about the staff morale. After all, being slayed by HellKnights and turning into zombies can put anyone down. To help bringing staff morale up, the UAC officials created a weekly cake celebration - basically, the kitchen staff would create a cake using brand-new UAC technology and put it on the kitchen for everyone to eat and feel happy.

However, the situation on Mars kitchens aren't exactly top-notch, and after complaints about people feeling ill after eating the weekly cake, the UAC decided to put a panel next to the cake indicating the current cake quality. Everybody could get on the cake status panel and look at the current cake quality before trying to eat it. Also, if they think the status is wrong, they can simply click a button and change the current cake quality, so anyone approaching the cake status panel later will also notice the current cake quality.

On the next few lessons, we'll be building the cake status panel. Here's a preview of how it will look like after we're finished.

tut3 26

With that said, this is the *complete* cake status PSD, opened in Photoshop (again, all files are available for download on a link at the end of this tutorial, so download to try yourself). This is just a quick design I created for this lesson, but it illustrates well what GUIs usually need.

tut3 01

All layers are shown on this screenshot, and some content will be created on the GUI editor itself. Also, notice I created the PSD file using a 640x480 size - this will help when I have to resize and position windowDefs on the GUI editor, so I just need to see their original values and positions.

Before we start exporting everything to materials, one word of advice: while GUI scripts works a lot like Photoshop - windowDefs work like layers, being stacked and all that - creating one windowDef/material for each PSD layer is not a good idea. If you use shared assets (like materials that are common and used on other GUIs too), this is fine, but other than that, it's a good idea to stack as many layers as possible on one single material file. This is what I'll do here, so even though this is a semi-complex PSD file with folders and a couple dozen layers, it'll be a pretty simple GUI script in the end.

Okey, let's start. The first thing to do here is to turn everything you don't want off, while seeing what you want to export as one material. In our case, we will first export the background; so we hide everything (including the background) and let the panelBack layer (and its grouped layers) visible only.

tut3 02

Now, select all (CTRL+A), make sure you have a visible layer selected (worst Photoshop usability bug ever), and select the menu "Edit > Copy Merged" (SHIFT+CTRL+C) to create a flatten copy of everything on the clipboard.

Now, select the "File > New" menu (CTRL+N). You'll notice it uses the values of the visible clipboard width and height in the new image properties, so just click OK or press enter to create a new image.

With that image created, select "Edit > Paste" (CTRL+V), delete the default "background" layer on the layers panel, and you'll have a new material ready to be exported.

tut3 03

Before saving, however, there's are two catches.

The first catch is related to the image size. As you'll notice on the "Image > Canvas Size" menu, our image has a 600x440 size. We will transform this on an image to be used by the DOOM 3 engine; and 3D video cards only use images with dimensions that are a number which is a power of two - that means using dimensions like 64, 128, 256, 512 and so on, so we have to conform to those rules.

Even though we *could* create images with custom sizes - in fact, I did that, in the first version of the article, but the result was pretty bad - it would mean that the engine would resize the image to a given size (256x256, or something similar) before using it, then resize it again when displaying it.

With that said, we have three choices now. We can:

1. Grab our 600x440 image and resize it to a valid size on Photoshop, like 512x256, then export it at that size and simply use it with the original 600x440 size on the GUI script; or
2. Add some transparent areas to our image to make it big enough to fit into a valid image size, like 1024x512, then export it and use it at this same size on the GUI script; or
3. Build our image from several small images (corners, top, sides, center), each one a separate image with a valid size.

Memory wise, the first choice is the better one; we would use the whole area for our image. It would result in resizing, though, and in the end the image would never be crisp, even at the original 640x480 size.

The second choice is a good one because you would have the image be as crisp as possible, but you would be wasting texture space.

The third choice is overall the best one; however, it would be harder to implement since you'll have to develop a whole user interface system that makes sense. This is the good choice to make when building a complex window-like system, or several different GUI scripts that would share the same assets and have different sizes, but for our example which is a lone GUI script, it would be overkill.

With that said, I'll stick to the second option for now. Keep in mind, however, that in the future, you will have to decide which is the best option, and it will vary according to several different factors.

Okey. So we will add some little unused space to our image; call the "Image > Canvas Size" menu, and resize it to the next big sizes which are valid numbers. In this case, 1024x512. Also, be sure to click on the top left anchor button, so our image will stay at the top left corner of the document. Like so:

tut3 04

It will resize the canvas without touching the image.

tut3 05

Now, the second catch. Since this material will have a transparency, we have to make sure an alpha channel gets exported. Now, if we were exporting this image to a PNG file, we could simply export/save it now. But since this is a TGA file, there's something we need to do: create an alpha channel manually.

Press CTRL and click on the name of the layer on the layers panel. This will create a selection that matches the current layer's alpha mask, so you'll see the so-called walking ants on top of the layer's opacity edges.

tut3 06

Now, select the "Select > Save Selection" menu, and use whatever name you want as the new channel.

tut3 07

This creates a new alpha channel on your document file. You can find it in the "Channels" window in Photoshop; it's a just grayscale mask of your opacity, where whites means total opacity and black means total transparency.

We're finally ready to save, wew. I hope that wasn't hard.

To do this, select the "File > Save As" menu (for other Photoshop versions, do "File > Save as a copy"). On the screen that opens, be sure to select "As a copy", select the "Targa" (TGA) format, and make sure that "Alpha Channels" is also selected.

tut3 08

Also, this time, instead of saving everything into DOOM 3's /base dir, we'll get a bit more organized. Create a new "cakestatus" directory on the <doom3>/base directory, and save this new tga file there, with the name "panel.tga".

When the bit options window pops up, be sure to select "32 bits" as the exporting option, or else the alpha channel won't be exported.

Great, this first material is exported and ready to use. Before we can jump into the GUI editor, however, we have other materials to export. So close this new file (and don't save it - you don't have to) and we're back to the original PSD file.

Hide all layers, and turn on the "_noisegore" folder and all layers inside of it so we can export the blood and broken glass overlay materials.

tut3 09

With that done, the process is pretty much the same as the previous one, and I'm using the "blood.tga" name. Once again...

1. Select everything (CTRL+A)
2. Copy merged (SHIFT+CTRL+C when a visible layer is selected)
3. Create a new image document (CTRL+N)
4. Accept the default properties (ENTER)
5. Paste the flatten material (CTRL+V)
6. Delete the default "background" layer
7. Resize the canvas ("Image > Canvas Size") using an 1024x512 size and at the top left corner
8. CTRL+Click on the only layer on the layers list to select its transparency
9. Use the menu "Select > Save Selection", use any name
10. Save as a copy, format TGA, with Alpha Channels, with 32 bit/pixel
11. Close the image document, don't save

Nice! We're *almost* ready to get to edit the GUI (of course, we could have both editors opened at the same time, exporting and importing, but I'm doing these things one step at a time).

We still have a few images to export: our buttons, and the status icons. So get back to the original PSD file, hide everything, and show the "_buttons" folder only.

tut3 10

We'll export them in a different way now. Also, even though some of these buttons assets belong to the same layer (the background), we'll export three different images. Also, this time we don't need an alpha channel, so it'll be a bit easier. We will do the "valid sizes" thingy a bit different, though.

First, use the marquee tool and drag a box around the first button to select it. Don't worry about the box size; when you copy, the transparent areas are subtracted. Just make sure the entire first button is selected, and no areas from the other buttons are.

tut3 11

Now, do the same you did with the other materials, but stop on step 6...

1. Select everything (CTRL+A)
2. Copy merged (SHIFT+CTRL+C when a visible layer is selected)
3. Create a new image document (CTRL+N)
4. Accept the default properties (ENTER)
5. Paste the flatten material (CTRL+V)
6. Delete the default "background" layer

Now, we would usually resize the canvas to a valid size - probably 128x64 in this case - but since we're dealing with a very simple box button and crispness is not a real issue, we will resize the image, as I mentioned in the first "type" of solution we had. This will provide a good example of the "other" kind of export.

So call the "Image > Image Size" menu. This works like the "Canvas Size" window, but it will resize the image inside the document properly. When the window opens, turn off the "Constrain Proportions" option - we want the image to be resizeable with no respect to ratio. Also, use width and height values that are valid power of two numbers - 128 and 64, respectively.

tut3 12

Press OK and the image will get a bit bigger.

Now, to save. Since we're not using any transparency - it's a box button after all - we don't have to worry about alpha channels. So we just do the "File > Save as" or "File > Save as a copy" directly. In the window that pops up, make sure you don't have the "Alpha channels" check on (it's off if you don't have any additional channel anyway). Save it on the same "cakestatus" directory, this time with the "button_bad.tga" name. On the window that pops up, select 24 bits per pixel - again, we're not using alpha channels, so we only need RGB values.

tut3 13

Close this new document, don't save, and get back to the original PSD file.

Now, I don't want to sound repetitive, so I'll just be brief and say to you to do the same thing with the other buttons. Save them as "button_soso.tga" and "button_ok.tga", on the same "cakestatus" directory.

With that done, we're almost there. There are a few other layers to export: the status symbols. Like the first materials we exported, these ones also have transparency settings, so we'll need to do all the channel process again.

So get to the original PSD file, hide everything, open up the "_status" folder, and show one of the layers. Now, repeat the exporting process (notice step 7 is different)...

tut3 14

1. Select everything (CTRL+A)
2. Copy merged (SHIFT+CTRL+C when a visible layer is selected)
3. Create a new image document (CTRL+N)
4. Accept the default properties (ENTER)
5. Paste the flatten material (CTRL+V)
6. Delete the default "background" layer
7. Resize the canvas ("Image > Canvas Size") using an 128x128 size and at the center/middle
8. CTRL+Click on the only layer on the layers list to select its transparency
9. Use the menu "Select > Save Selection", use any name
10. Save as a copy, format TGA, with Alpha Channels, with 32 bit color
11. Close the image document, don't save

Do this for all three "_status" layers, saving them to the "cakestatus" folder. I saved them as "symbol_ok.tga", "symbol_soso.tga", and "symbol_bad.tga".

One note about step 7: I used the 128x128 size, which is the nearest bigger valid size for images that size. Also, instead of using the top left corner, I centered it; this makes it easier to position this type of windowDef in the GUI script later.. or else we would have to manually center the symbols later in relation to each other. This way, we just place them at the same position.

And now, after we have exported everything, we're *finally* ready to create the GUI itself. Close Photoshop (you don't need to save the original PSD file since we didn't modify it) and we're ready to move.

Open up the GUI editor we've setup on the previous lesson, and create a new blank GUI document. Before anything, save it as "cakestatus.gui" on the same "<doom3>/base/cakestatus" directory we created earlier and placed our materials.

Let's start from the bottom levels: create a new windowDef (menu "Item > New > windowDef"), and double-click it to edit its properties. On the "General" tab, give it the "panel" name. Move to the "Image" tab, check the "Material" option, and type "cakestatus/panel" as the material name.

Something important to remember: even though the .gui file is at the same directory as the material itself, all file access is still relative to DOOM 3's current game base directory - in this case, "<doom3>/base". So "cakestatus/panel" will refer to "<doom3>/base/cakestatus/panel.tga".

Click OK and see the result. The new windowDef is created, and sure enough it has our panel as a background. The size is wrong, though; what have to do is to manually set the windowDef size to match the original size - since the PSD was created as a 1024x512 screen, this is rather easy...

tut3 15

...just go to the "transformer" window and use the original image sizes as values (1024x512). Also, "center" it on screen - 20x20 in our case, simple math considering 640-600=40 (20 to each size), and 480-440=40 (20 to top/bottom).

Do it again now for our blood/broken glass overlay. Create a new windowDef, double-click to open its properties, and give it the "blood" name, and use "cakestatus/blood" as a material to it on (of course, you could set all these properties on the "Properties" window itself, if you already knew the property names). Resize it to 1024x512 and position it at 0x0, and our cake status panel is starting to take shape.

tut3 16

Now we will finally add text. Simply create new windowDefs, double-clicking them, adding text, setting the size accordingly, and moving them to the right position and sizes. This is kind of a boring task and must be pretty basic for you now, so I won't get into it much; here's the result though.

tut3 17

Nice, but something looks weird. What's it?

The thing is, our new text is showing on top of the blood overlay. It has to be below it so it can also be tainted with blood and by the brightness created by the broken glass... and look natural, of course.

Look at the "Navigator" window, showing the windowDef items; since button_bad was the last one we created, its the last on the list, on the bottom. This is because the navigator window use the same system as the GUI code itself: new items are positioned on top of the other items, so items on the topmost position are shown on the end of the list.

This is easy to fix, though. On the navigator window (or on the editing area itself), right-click on the "blood" windowDef, and on the menu that pops up, select "Arrange > Bring to Front" or simply CTRL+SHIFT+], something Adobe Illustrator users will recognize. Sure enough, after doing this, our text windowDefs are now below the blood overlay, so the text looks like it has been painted with blood.

tut3 18

Now we'll create the buttons. Create a new windowDef, edit it and name it "button_bad", set its material to "cakestatus/button_bad", use "BAD" as its text, and press OK. Set its size (82x37) and set its position (you can simply move it).

tut3 19

Two things to notice on this screenshot: first, I already moved the "blood" windowDef to the 'top' again, so remember to do this on this and on the following steps. Second, Remember we resized our button material to 128x64? We're now back at using the original size (82x37), and it looks good.

Also, we're finally using a windowDef with a background image AND a text.

Now do the same with the "button_soso" and "button_ok" materials: create a new windowDef, name them, set those images as material, set its text, and position and resize them. You may also just copy the "button_bad" windowDef and then paste it again (to duplicate), changing its name, material, and position. This is easier, of course, so I'd recommend that.

After doing this, remember to send the "blood" windowDef to the bottom of the list again, so it'll display on top of everything.

Also, just like in Photoshop, you may also wish to make the "blood" windowDef invisible (clicking on the little eye icon on the navigator window) so you can work with the other windowDefs without having to worry with the overlay getting in the way. Turning it on or off doesn't change the "visible" property of the windowDef, so this is a editing-time only feature. On the screenshot below, I've turned it off so I can see what I'm doing better.

tut3 20

Time to move on. Now, we'll create the status panels. Create a new windowDef, name it "status_ok", and position it roughly on the middle of the screen, as below. Position or size is not that important, since this windowDef's boundaries won't be shown, but be sure to include the area that will be used by the cake status messages (the whole middle area).

tut3 21

I'll tell something in advance: we're doing this all because, in the next lesson, we will be able to turn the status windowDefs on and off, so it's better for us to have everything organized and correctly nested. We will, then, nest the "symbol_ok" windowDef inside of the "status_ok" windowDef.

To do this, either on the editing window or in the navigator window, select both "status_ok" and "symbol_ok" windowDefs. One *important thing* to remember: it doesn't take the windowDef order into account, but rather the order you selected them. The first windowDef you selected will always be the parent, and the rest, the children. So select the "status_ok" windowDef first.

Next, right-click, and select "Arrange > Make Child" in the menu that pops up.

tut3 23

Doing this, the symbol windowDef is into the status windowDef, and the navigator list update showing the children hidden (you have to open the parent windowDef by clicking on arrow to see the rest of the children). This is very similar to Photoshop's "Group with previous" (CTRL+G) action, and it works in the same way in the meaning that child windowDefs are now part of the parent windowDef and are masked by this parent.

Now, to create more text.

Create a new windowDef, and name it "text_ok". This will contain a description of the current status. I've used the "english" font, with scale 0.3, and the text "Cake is proper for eating. Observation is advised up to 4 hours after consumption. Adverse symptoms should be reported at once to the cake status updater team.". Also, give it a good position next to the "status_ok" picture.

tut3 22

Great. Now it's time to make it also a child of the "status_ok" windowDef. Select both "status_ok" and "text_ok" windowDefs, in this order, and again, do right-click and select "Arrange > Make Child". The "text_ok" windowDef will be nested into status_ok.

After this, we're almost ready to do the next step, so let's test this thing. First, select the "blood" windowDef again, move it to the bottom of the list either by using the "Arrange" menu or the keyboard shortcuts. This will be the end layout of the windowDef list...

tut3 25

Now, save the GUI file (save always and save often), and press CTRL+T or use the "Tools > Viewer" menu to test it on the viewer.

tut3 26

Is this looking good or what?

Well, we're ready to move on. What we have to do is to create the remaining status windowDefs, for the "soso" and the "bad" status. Now, do you remember - on the previous lesson - when I said that editing the code is sometimes better than doing anything on the GUI editor?

Well, this is one of those moments. While we could simply recreate and set all properties again for the remaining two windowDefs, this would be too repetitive. It's easier - even to maintain the same positions we used - to just open the GUI source code and do a little copy&paste job.

Go to the GUI editor, save the GUI script file, and close it. Now, open this same file - <doom3>/base/cakestatus/cakestatus.gui - on any text editor, notepad if you wish. Look for the "status_ok" windowDef, and sure enough, there it is.

   windowDef status_ok
   {
      rect   64,159,512,147
      visible   1
      windowDef symbol_ok
      {
         rect   16,6,128,128
         visible   1
         background   "cakestatus/symbol_ok"
         matcolor   1,1,1,1
      }
      windowDef text_ok
      {
         rect   153,6,345,134
         visible   1
         forecolor   1,1,1,1
         text   "Cake is proper for eating. Observation is advised up to 4 hours after consumption. Adverse symptoms should be reported at once to the cake status updater team."
         textscale   0.30000001
      }
   }

Now, simply copy this text and paste it twice, making sure you're not mixing any lines. Leave the original untouched, and modify the other two - renaming all "_ok" names (the three windowDef names and the material name) to "_soso" in the first copy, and to "_bad" in the second copy. Anyhow, this is the final result...

   windowDef status_ok
   {
      rect   64,159,512,147
      visible   1
      windowDef symbol_ok
      {
         rect   16,6,128,128
         visible   1
         background   "cakestatus/symbol_ok"
         matcolor   1,1,1,1
      }
      windowDef text_ok
      {
         rect   153,6,345,134
         visible   1
         forecolor   1,1,1,1
         text   "Cake is proper for eating. Observation is advised up to 4 hours after consumption. Adverse symptoms should be reported at once to the cake status updater team."
         textscale   0.30000001
      }
   }
   windowDef status_soso
   {
      rect   64,159,512,147
      visible   1
      windowDef symbol_soso
      {
         rect   16,6,128,128
         visible   1
         background   "cakestatus/symbol_soso"
         matcolor   1,1,1,1
      }
      windowDef text_soso
      {
         rect   153,6,345,134
         visible   1
         forecolor   1,1,1,1
         text   "Cake is not very poisonous. Use in case of profound starvation and report to the medical crew immediately after consumption."
         textscale   0.30000001
      }
   }
   windowDef status_bad
   {
      rect   64,159,512,147
      visible   1
      windowDef symbol_bad
      {
         rect   16,6,128,128
         visible   1
         background   "cakestatus/symbol_bad"
         matcolor   1,1,1,1
      }
      windowDef text_bad
      {
         rect   153,6,345,134
         visible   1
         forecolor   1,1,1,1
         text   "Eating this cake means certain and immediate death. Please seal the area and start decontamination process AH2-B."
         textscale   0.30000001
      }
   }

Notice I've also changed the text messages to a new proper description.

After changing the GUI script, save it, close the text editor, and open it again on the GUI editor. If you have messed up and made an invalid GUI script, the GUI editor will pretty much blow up when opening the file, probably killing you and everybody in a 1-kilometer range. If that happens, open the source code again and check if every windowDef is correctly opened and closed, and brackets are placed correctly.

If it doesn't blow up, this will be the final result, with status windowDefs displayed on top of each other.

tut3 27

Well, that was long - but it's done! Our real-world GUI is created, and ready to be scripted. The next lesson will cover some basic coding on this same GUI script. See you there.

Download source and example files


Hello, and welcome to the lesson #4 in our series of tutorials. In the previous (big) tutorial, we created a simple GUI panel, with a few windowDefs showing the current cake status on the UAC kitchen and some buttons that would later change the status displayed in the panel. Now, we'll make those buttons work, so the hungry UAC employees can happily press the panel buttons and actually change the currently reported cake state. And yes, I know how bad that reads.


Complete GUI Scripting - 4: Making simple interactions


GUI scripts aren't only about making animations or static screens. They're about giving the player the power to interact with them, changing something in the GUI itself or in the level - opening a door, calling an elevator, or turning on a light, for example. This is achieved through the basic user interactions the GUI system allows - by capturing mouse events and running commands when they are executed. In this lesson we'll add mouse events to the player buttons.

This lesson could be done in two ways: on the code itself, or in the GUI editor. Doing it in the code itself is usually faster - you would have immediate access to all windowDefs and all code - but for easy previewing of our results, we'll do this lesson in the GUI editor.

Open up the GUI script we created in the previous lesson - cakestatus.gui - and save it as cakestatus2.gui, on the same <doom3>/base/cakestatus directory. Since this is a new lesson, we won't be messing with the previous lesson's source.

tut4 01

First of all, you'll remember all our status windowDefs - status_ok, status_soso and status_bad - are all visible, on top of each other. We don't want that; they should be hidden, so the 'default' status will be none. So, on the navigator window, double-click on the status_ok windowDef, and in the properties that pop up, on the "General" tab, turn off the "visible" check box. By doing so, you will make this windowDef invisible and it automatically turns off the 'eye' icon of this windowDef, making it also invisible for editing. But remember; both these settings are independent, so you can have an invisible windowDef visible when editing or vice-versa.

You will also notice that all windowDefs inside of status_ok - symbol_ok and text_ok are also hidden. This is because we've nested them, so if the parent is not visible, children aren't visible.

Do the same for the status_soso and status_bad windowDefs, so the 'current cake status' area will be blank.

tut4 02

We will now add a few events that will make those windowDefs visible again.

Here's the thing: apart from being visual elements with some properties such as bordersize, background and text, windowDefs also respond to certain events. Much like you'd expect from a button in Visual Basic, a movieclip in Flash or an image in HTML, you can attach pieces of code that are executed when the mouse rolls over a windowDef, rolls out of it, or click on it, just to point a few common interaction events.

In code, this is easily achieved by adding events manually just like you would add properties or child windowDefs. For example, this windowDef...

windowDef myButton {
    rect        10, 10, 100, 100
    background  "gui/button_background"
    matcolor    1, 1, 1, 1
}

...could have a new event added, like this...

windowDef myButton {
    rect        10, 10, 100, 100
    background  "gui/button_background"
    matcolor    1, 1, 1, 1
    eventName {
        // code to be executed
    }
}

...and the code inside the "eventName" block would be executed when a certain condition was met. These event names are built-in events, such as "onMouseEnter" for when the player moves the mouse over the windowDef area, "onMouseExit" for when the player moves the mouse out of the windowDef area, "onAction" for when the user activates the windowDef by clicking on it with the mouse, and many others.

In our case, since we will have a normal button, we'll use these simple mouse events. In our example above, it would look like this...

windowDef myButton {
    rect        10, 10, 100, 100
    background  "gui/button_background"
    matcolor    1, 1, 1, 1
    onMouseEnter {
        // code to be executed
    }
}

...so our code would be executed when the mouse rolls over the button windowDef area.

While this is easy to do in the source code - you just have to create a new line and type in your event blocks - we are using the GUI editor and we don't have direct access to a windowDef source. Thankfully, the GUI editor allows you to add as much code to a windowDef as you wish.

We'll first make simple button interactions. Go to the GUI editor, select the "button_bad" windowDef, and either select the menu "Item > Scripts", or right-click on the windowDef and select "Scripts", or simply press CTRL+ENTER. The code window pops up.

tut4 03

Of course, since we have no code, this is all blank. Now, let's add the "shell" to what we want to do. This code...

onMouseEnter
{
}

onMouseExit
{
}

onAction
{
}

...contains the blocks of events we need to make buttons come alive. But this alone does nothing, so we have to add some code to it.

We then come down to our first GUI scripting code command: set. With set, you can set a windowDef's attributes directly, via code (there are a few exceptions we'll talk about later). It takes as parameters the name of the property we want to change, and the new value we want it to be.

onMouseEnter
{
   set "button_bad::matcolor" "0.5 0.5 0.5 1";
}

onMouseExit
{
   set "button_bad::matcolor" "1 1 1 1";
}

onAction
{
}

So it's easy to read what this code will do: when the mouse moves over button_bad, button_bad will change its matcolor property to "0.5 0.5 0.5 1", which is a dark red. When the mouse moves out of the button, the matcolor will get back to its default state, so the button becomes all red again.

After that is done, close the "Item Scripts" window, and test the GUI script by pressing CTRL+T.

Cool, it works, right? WRONG, it doesn't. Why is that?

The thing is, we had an image overlay on top of our GUI - the "blood" material. The mouse events end up getting captured by the topmost windowDef element, so the blood windowDef blinds all windowDefs below it - that is, the entire GUI (this is like buttons or movieclips work in Flash, too).

But the blood windowDef won't have any mouse event we need to capture, so this is easy to fix: just double-click the "blood" name in the navigator window (to edit this windowDef's properties), and on the "General" tab, turn on the "No events" option. This will make sure this windowDef will be just a visual element, and mouse events will fall through to windowDefs below.

With that done, press CTRL+T to try again.

tut4 04

And it finally works.

Now, select "button_bad" again and press CTRL+ENTER to edit its scripts, and then copy them all and apply them to the "button_soso" and "button_ok" windowDefs. Remember to edit the code so each code will correctly refer to the correct windowDef element itself, as in:

onMouseEnter
{
   set "button_soso::matcolor" "0.5 0.5 0.5 1";
}

onMouseExit
{
   set "button_soso::matcolor" "1 1 1 1";
}

onAction
{
}

For button_soso, and

onMouseEnter
{
   set "button_ok::matcolor" "0.5 0.5 0.5 1";
}

onMouseExit
{
   set "button_ok::matcolor" "1 1 1 1";
}

onAction
{
}

For button_ok.

So, our buttons are finally working like buttons. Great. But they don't do anything just yet; we want them to turn on the status messages.

Doing it is easy. As we simply changed the matcolor property of the button windowDef on an event, we can change the visible property of another windowDef on another event. That means making the "status_bad" windowDef visible when the "button_bad" is clicked. Simple enough, just select button_bad and press CTRL+ENTER to edit its code, and use:

onMouseEnter
{
   set "button_bad::matcolor" "0.5 0.5 0.5 1";
}

onMouseExit
{
   set "button_bad::matcolor" "1 1 1 1";
}

onAction
{
   set "status_bad::visible" "1";
}

Go ahead and try it.

tut4 05

Pretty cool, right? And so easy. You'll also notice the cursor even changed to a hand, to show that some action will be executed when clicked.

This is pretty much enough to turn on our status windowDefs, but there's one catch. If you click on two buttons, two status windowDefs will be visible. That's something we don't want; so we need to be sure to turn off all other status windowDefs before making one of them visible. So we just add some safe code to our onAction event.

onMouseEnter
{
   set "button_bad::matcolor" "0.5 0.5 0.5 1";
}

onMouseExit
{
   set "button_bad::matcolor" "1 1 1 1";
}

onAction
{
   set "status_bad::visible" "1";
   set "status_soso::visible" "0";
   set "status_ok::visible" "0";
}

That is: when clicked, turn on status_bad, but turn off status_soso and status_ok.

Now, copy this onAction event block script to the other buttons. Of course, remember you have to correctly set what's visible and invisible, as in

onAction
{
   set "status_bad::visible" "0";
   set "status_soso::visible" "1";
   set "status_ok::visible" "0";
}

For button_soso, and

onAction
{
   set "status_bad::visible" "0";
   set "status_soso::visible" "0";
   set "status_ok::visible" "1";
}

For button_ok.

With that said, test away; the GUI is working! You press a button and it turns on the status, you press another one and the status changes. Great. Never again UAC employees will have to face the risk of imminent death because of a rotten cake. No, they will have an status panel; so people in the edge of death can at least update the status panel before they pass away, warning other employees of the danger that cake represents. Yes, this doesn't make sense.

However... while this panel is cool, it lacks something. It lacks touch, it lacks feedback. And the best way to add this to an interface is to add sounds to it.

In GUI scripting, this is easily achieved using the localSound command. It only takes one argument - the sound you want to play. To make things easier, we'll use some built-in sounds, with commands copied directly from id Software's GUI sources, so it will sound a lot like a panel from the singleplayer game. We will add a sound for when the mouse moves over the button and when it is clicked. So, updating button_bad...

onMouseEnter
{
   localSound "guisounds_ping2";
   set "button_bad::matcolor" "0.5 0.5 0.5 1";
}

onMouseExit
{
   set "button_bad::matcolor" "1 1 1 1";
}

onAction
{
   localSound "guisounds_click3";
   set "status_bad::visible" "1";
   set "status_soso::visible" "0";
   set "status_ok::visible" "0";
}

...simple enough, notice "guisounds_ping2" will play on the onMouseEnter event, and the "guisounds_click3" sound will play when you click the button. Add this code to the other buttons, and then test away - since the built-in viewer also plays sounds - and marvel at the power of DOOM 3 GUI scripting (cheesy, I know).

tut4 06

This wraps up our lesson; you're ready to add scripted events to your GUI scripts. While we have only discussed a few event blocks and a couple of script commands, understanding what this tiny example of the script system does is enough to unleash the power of the GUI scripting system.

I'd like to add a closing note: until now, I've covered some key aspects of the GUI scripting system, and I've done my best to go as slow as possible, to help first-timers with this whole concept. With that said, this was the last of the beginner lessons - all that someone needs to learn in order to have the first grasp of the scripting system was covered in those tutorials - and I'll now move into intermediate and advanced topics. That means two things: first, that since I don't want to sound boring, I'll start condensing steps a little more. Second, the tutorials will be shorter because, instead of going step-by-step in the whole process of creating something, it will just cover a few new aspects of the scripting system. Hopefully, I'll also work on a GUI script reference to make things easier -- you know, it's all someone should need.

The next tutorial (finally adding this GUI to a map and watching it in action) will be just a small continuation of this one. Since this has actually been covered in other tutorials, I will do this just to finish the cake trilogy.

See you there.

Download source and example files


Welcome to lesson five-and-a-half in the complete GUI scripting series. In this tutorial, we'll apply the GUI we created on previous lessons to a map. This is a really short and a really simple tutorial, but added to the previous two lessons, it's aimed to make a complete GUI script tutorial from start to finish.



This tutorial presumes you have some minimum knowledge of the map editor in DOOM 3. If you don't, please take a moment to see the many tutorials available on this topic, specially BNA!'s Your first box map tutorial and the DOOM 3 level editing FAQ on how to properly set up the editor.

Complete GUI Scripting - 5: Applying a GUI script to a map



One of the most powerful features of GUI scripting in the DOOM 3 engine is the ability to apply your GUI script to an in-game object, giving the player the ability to interact with it in the game world itself, without opening outside dialog windows.

Adding GUI scripts to in-game objects is pretty easy. For this tutorial, I've created a simple 'kitchen' map to place our cake status reporter panel. This is a rather crude copy & paste job from the original DOOM 3 singleplayer maps, so please bare since I don't have that much of an experience in the DOOM 3 map editor.

Well, first of all, you'll have to download the source files for this tutorial, since the map we use is inside of it. Open the zip, put all *.map files on the <doom3>/base/maps folder, and put all the rest in the <doom3>/base/guis/cakestatus folder.

Something to take notice is that, since I've changed the location of our "cakestatus" script from /base/cakestatus to /base/guis/cakestatus, all material references had to be changed too. So I changed the GUI script and cakestatus3.gui is different from the cakestatus2.gui file we used on the previous lesson. This is, however, just a hack job to get us going. On real, serious mod works, it'd better to have your GUIs on /guis and your assets somewhere else, all correctly distributed.

Back to the topic. Run the map editor and open up our map - kitchen.map.

tut5 01

This is pretty much your default kitchen. It has a refrigerator, and that's where the UAC personnel puts the dangerous UAC cake, so we need to put the cake status reporter panel next to it. First, we have to add a new model that will display our panel.

Press ESC to deselect everything, right-click on the editor area, and select "New Model...". On the window that pops up, find a model to place on the map - not all models will do, though, so we need models that are prepared to have a GUI entity. Navigate to the "base > models > mapobjects > guiobjects" folder - anyone from there will do. I used "flatmonitor > flatmonitor.lwo", which is a large, flat panel you can put on the wall.

tut5 02

After pressing OK, the object is ready to be moved. Move it by dragging the model or rotate it by using the "Selection > Rotate" menu until you find a position that it fits well.

That done, we're ready to get the panel working. With the model selected, be sure to have the "entity" tab opened in the 'property' window. There are many options, but we won't be dealing with them.

tut5 03

On the entity window, press the "Gui..." button to select a GUI that will be placed on the panel. On the window that pops up, navigate to the "base > guis > cakestatus" folder, and sure enough, select the "cakestatus3" GUI script.

tut5 04

Easy enough, it's already working on our preview window, if you have the render window enabled (if you don't, press F3 to enable it, then F4 or F7 to put on the lights).

tut5 05


Select the BSP > BSP menu to compile it, make sure it has no leaks, then press F2 to open it in-game. As soon as the game runs, if it kicks you to the console (it does for me), type "map kitchen" to run the map.

I think the cake is bad...

tut5 06

...or maybe I'll just say it's good so somebody will eat it and die.

tut5 07


Let's have a final look in our kitchen. It's a pretty big panel, nobody will miss it. And hopefully, the world will be a better place... thank to UAC cake status reporting technology.

tut5 08

Well, that's pretty much it - this lesson is just about applying a GUI script to a model anyways. There's much more that could be said about interaction between GUIs and levels, but it's out of the scope of this lesson. I'd like to thanks Njal for this tutorial that helped me write mine, and point it out for people that are looking for a bit more serious level control with GUIs.

Download source and example files

Post comment Comments
FanProgrammer
FanProgrammer

Thanks

Reply Good karma Bad karma+1 vote
LithTechGuru Author
LithTechGuru

You're most welcome

Reply Good karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: