zeh@Posted: Thu Aug 12, 2004 12:36 am :
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.

Image

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.

Image

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...

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


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

Code:
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...

Code:
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.

Image

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...

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.

Code:
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.

Image

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:

Code:
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

Code:
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:

Code:
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.

Image

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.

Code:
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

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


For button_soso, and

Code:
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...

Code:
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).

Image

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 (500kb)



MisterCrow@Posted: Thu Aug 12, 2004 8:42 am :
Sweet, awesome tutorial! I hope to get to these GUI tutorials real soon!

I have a quick question though, is there a way to browse GUI assets (textures) within the GUI Editor? I was really looking forward to being able to look at a whole bunch of GUI elements at once so I could pick-and-choose what I needed to make an interface.

As far as I can tell you have to know exactly what you want before you make it, hence the "Material" property not having any kind of preview or browse button.



zeh@Posted: Thu Aug 12, 2004 11:58 am :
MisterCrow wrote:
I have a quick question though, is there a way to browse GUI assets (textures) within the GUI Editor? I was really looking forward to being able to look at a whole bunch of GUI elements at once so I could pick-and-choose what I needed to make an interface.


Not inside the GUI editor, at least not that I know of - you have to either use an external browser, or look in the directories (if you're going to use tga files) or the materials listing (if you're going to use a shader) to know what to put on the material fields.



KingJackaL@Posted: Thu Aug 12, 2004 12:03 pm :
Excellent tutorials dude, I've enjoyed them. My UAC employees are all safe now... well, cept for that first lemming :lol:

I noted that this paragraph:

Quote:
We'll first make simple button interactions. Go to the GUI editor, select the "button_ok" 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.


...should reference 'button_bad' if it's follow with the proceeding script ;).

But yeah, can't wait for the scripting ones - if I don't figure it out before then. But yeah, I hadn't noticed the GUIeditor (or any of the others) or noticed the whole GUI system at all until I read your first tute. Must say it's improved my opinion of the engine massively - such an incredibly flexible system, it'll surely make for some excellent mods :D.



zeh@Posted: Thu Aug 12, 2004 12:07 pm :
Quote:
I noted that this paragraph:

Quote:
We'll first make simple button interactions. Go to the GUI editor, select the "button_ok" 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.


...should reference 'button_bad' if it's follow with the proceeding script ;).


Wow, you're right -- fixed. I don't know how I could let that slip. :)

Thanks.



goliathvt@Posted: Thu Aug 12, 2004 4:55 pm :
Excellent work, Zeh. Thanks!

G



Mastiff@Posted: Fri Aug 13, 2004 4:41 am :
You write excellent tutorials. I'm looking forward to the intermediate ones.



redneckprovence@Posted: Tue Aug 31, 2004 10:22 pm :
my GUI editor does not play any sound :(
but if I use the same GUI script in a map, then the sounds are played
anyone knows why?



b0ksah@Posted: Mon Sep 06, 2004 6:42 pm :
Bumpy



Gordon228@Posted: Fri Feb 04, 2005 7:37 pm :
This is good but i need to know how to edit or creat a pda door gui?



xwarpedx@Posted: Fri Feb 04, 2005 9:04 pm :
not to be rude but theres a button called "search" at the top of the forum pages. theres also lists of tutorials stickied on the boards already.



son_of_sam2@Posted: Fri Sep 23, 2005 12:42 am :
ok i have done all that you have sid and it works , but what im looking for is how to cap an activate to one press , non repeater i guess you could say this is what i have, mainly because i could not get the retset time to work with different variables so i left it at 0 the on active i want to cap is in windowDef loading

windowDef Desktop
{

rect 0,0,640,480
backcolor 0,0,1,1
visible 1
background "guis/assets/splash/crusher"
matcolor 1,1,1,1
windowDef title1
{
rect 182,31,287,100
visible 1
forecolor 1,1,0,1
text "MISSION 1"
textscale 1
font "fonts/english"
textalign 1
}
onAction
{
set "loading::visible" "1";
set "title1::visible" "0";
set "title2::visible" "0";
set "title3::visible" "0";
resetTime "0" ;
}
windowDef title2
{
rect 66,168,509,69
visible 1
forecolor 1,1,0,1
text "click to load"
textscale 1
font "fonts/english"
textalign 1
}
onAction
{
set "loading::visible" "1";
set "title1::visible" "0";
set "title2::visible" "0";
set "title3::visible" "0";
resetTime "0" ;
}
windowDef title3
{
rect 32,328,580,100
visible 1
forecolor 1,1,0,1
text "Mission Hellknight"
textscale 1
font "fonts/english"
textalign 1
}
onAction
{
set "loading::visible" "1";
set "title1::visible" "0";
set "title2::visible" "0";
set "title3::visible" "0";
resetTime "0" ;
}


windowDef loading {
rect 91,227,440,100
visible 0
forecolor 1,1,0,1
text "BEGIN MISSION !!!"
textscale 1
font "fonts/english"
onAction
{
set "loading::visible" "1";
set "title1::visible" "0";
set "title2::visible" "0";
set "title3::visible" "0";
resetTime "0" ;
set "cmd" "activate";
}
}
}



zeh@Posted: Fri Sep 23, 2005 2:56 am :
son_of_sam2: this is kind of unrelated to the topic, instead of replying on old topics you should create a new thread next time.

If I understood your question, to create a button that's only activated once, you have to have some variable that holds the information of whether that button was already activated. Then the action checks if it has not been activated, and if not, it sets the variable to 'activated' and then do whatever it should do. This is not complex -- it's basic programming logic.

I don't know why you're using resettime on your code, but it's completely unrelated to what you're trying to achieve, specially because you're not using any kind of animation at all.



son_of_sam2@Posted: Fri Sep 23, 2005 5:01 am :
yaya it does have remnants of trying to get it to reset on its own in it
ie.......resettime <-----failed attempt left in
see im stuipid where codeing is concerned

i came up with this but it did not work, based on what you said

if (activated) { set "cmd" "activated" ; }

se what i was hopeing was that you know what you are doing, and i do not that you would fix it to do it yourself
or tell me what to do. Your answer was to general------>
"Then the action checks if it has not been activated, and if not, it sets the variable to 'activated' and then do whatever it should do".... sorry but if i had a clue as to what to change and where to put it i would do it myself....lol thats why i sent the whole code so you could see it all... mistakes , failed attempts and all thanks for your reply



RedWolf@Posted: Sat Oct 15, 2005 9:59 pm :
Quote:
se what i was hopeing was that you know what you are doing, and i do not that you would fix it to do it yourself
or tell me what to do. Your answer was to general------>


Well atleast he was honest! I realize that this is an old thread, but someone else may have the same question. And while I will not do it for you I will add a little bit to be more specific. (this will get almost a zero response, when you even appear to "using" others in this mannor -ie doing the work for you)

This is the same as items like the "visible" "on" off" switch. (0 and 1) So create your variable under the def desktop. Something like float "activated" "0"
Then ad an if statement like if(activated == 0) {activate, and set activated to 1} basicly change def that has your "activate" in it to this if and else so in the brackets that are onAction {if and else}
Then do an else {what ever if it was alread activated once, like an error sound or something}
Basicly this says that if the variable activated is off, then activate and set the variable to on. Then if it is run again, it will see that activated is on, and it will do the what ever you have listed in the else statement.



son_of_sam2@Posted: Sun Oct 16, 2005 12:35 am :
since my last post on this thread i have gained alot of scxripting knowledge to finish what i had in mind but what you have mentioned here will for sure help in the future, so thanks.... and finally the doom3 wiki does not have all the scripting variables so im left to just pull em outa my bung hole most of the time



rich_is_bored@Posted: Sun Oct 16, 2005 5:17 am :
The wiki is a work in progress that unfortunately isn't as active as I'd like it to be. Right now we have a hundred or so members but only a handful of them (you know who you are, thanks btw) make contributions.

I'm hoping that the release of Q4, Prey, ect... will encourage more people to contribute as it should attract more people to the community.