Doom3world

The world is yours! Doom 3 - Quake 4 - ET:QW - Prey - Rage
Last visit was: Sat May 22, 2010 5:51 pm It is currently Sat May 22, 2010 5:51 pm

All times are UTC




 [ 12 posts ] 
Author Message
 Post subject: Complete GUI Scripting - 9: Animating the marine face HUD
PostPosted: Sun Sep 05, 2004 3:47 pm 
Offline
Reinventor of the wheel
User avatar

Joined: Sun Jul 18, 2004 2:38 pm
Posts: 1789
Location: New York, NY
Hello, and welcome to lesson number nine in the GUI scripting series. This also is the 10th lesson on this series -- it has come a long way.

In the classic HUD we're creating, we'll finally get the marine face to update. We'll use a little animation, and then some more conditional statements to respond to certain events.

Complete GUI Scripting - 9: Animating the marine face HUD

One of the coolest things the marine face could do on the original DOOM game was react to what was happening around him. He would:

  • Continually look around suspiciously, rising an eyebrow as he looked to his sides
  • Get a happy/twisted look to his face when he got a new gun
  • Do a pain face when suffered damage, looking to the side the pain came from
  • Start to bleed and look in bad shape according to his health
While we won't get him to look to the sides he took damage from - it might be possible, but I never found a way to get this information to use on the GUI system - the rest is easily doable and we'll implement that on this lesson. The first thing you might want to do is download the tutorial files for this lesson as it contains all the materials file we'll use (all the marine faces) and unzip it to the correct directories (the same <doom 3>/classichud/guis and <doom 3>/classichud/guis/assets folders we used on the previous lessons).

Anyways, here's our HUD as it is until now.

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1

//=============================================================================
// Events
//=============================================================================

   onNamedEvent newWeapon {
      // Now turns on the correct number when a new weapon is picked
      if ("gui::newweapon" == 2) {
         set "gun_3::forecolor" "1,1,0,1";
      }
      if ("gui::newweapon" == 3) {
         set "gun_4::forecolor" "1,1,0,1";
      }
      if ("gui::newweapon" == 4) {
         set "gun_5::forecolor" "1,1,0,1";
      }
      if ("gui::newweapon" == 5) {
         set "gun_6::forecolor" "1,1,0,1";
      }
      if ("gui::newweapon" == 6) {
         set "gun_7::forecolor" "1,1,0,1";
      }
   }

//=============================================================================
// Main bar
//=============================================================================

   windowDef base {
      rect   0,416,1024,64
      visible   1
      background   "guis/assets/hud_base"
      matcolor   1,1,1,1
   }

//-----------------------------------------------------------------------------
// Standard indications: ammo, armor, health
//-----------------------------------------------------------------------------

   windowDef bar_health {
      rect   110,420,60,49
      visible   1
      forecolor   1,1,1,1
      text   "gui::player_health"
      textscale   0.6
      textalign   2
      font   "fonts/english"
   }
   windowDef bar_health_percent {
      rect   170,420,40,49
      visible   1
      forecolor   1,1,1,1
      text   "%"
      textscale   0.6

      textalign   0
      font   "fonts/english"
   }
   windowDef bar_armor {
      rect   370,420,60,49
      visible   1
      forecolor   1,1,1,1
      text   "gui::player_armor"
      textscale   0.6
      textalign   2
      font   "fonts/english"
   }
   windowDef bar_armor_percent {
      rect   430,420,40,49
      visible   1
      forecolor   1,1,1,1
      text   "%"
      textscale   0.6
      textalign   0
      font   "fonts/english"
   }
   windowDef bar_ammo {
      rect   15,420,60,49
      visible   1
      forecolor   1,1,1,1
      text   "gui::player_ammo"
      textscale   0.6
      textalign   2
      font   "fonts/english"
   }

//-----------------------------------------------------------------------------
// Weapon numbers
//-----------------------------------------------------------------------------

   windowDef gun_2 {
      rect   218, 416, 40, 40
      visible   1
      forecolor   1,1,0,1
      text   "2"
      textscale   0.25
      font   "fonts/micro"
   }

   windowDef gun_3 {
      rect   242, 416, 40, 40
      visible   1
      forecolor   1,1,1,0.4
      text   "3"
      textscale   0.25
      font   "fonts/micro"
   }

   windowDef gun_4 {
      rect   266, 416, 40, 40
      visible   1
      forecolor   1,1,1,0.4
      text   "4"
      textscale   0.25
      font   "fonts/micro"
   }

   windowDef gun_5 {
      rect   218, 436, 40, 40
      visible   1
      forecolor   1,1,1,0.4
      text   "5"
      textscale   0.25
      font   "fonts/micro"
   }

   windowDef gun_6 {
      rect   242, 436, 40, 40
      visible   1
      forecolor   1,1,1,0.4
      text   "6"
      textscale   0.25
      font   "fonts/micro"
   }

   windowDef gun_7 {
      rect   266, 436, 40, 40
      visible   1
      forecolor   1,1,1,0.4
      text   "7"
      textscale   0.25
      font   "fonts/micro"
   }

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Normal face
//-----------------------------------------------------------------------------

   windowDef marineFace {
      rect   290,418,64,64
      visible   1
      background   "guis/assets/hud_face_100_center"
      matcolor   1,1,1,1
   }

}



So we get our basic textfields with armor, ammo and health data, the weapon numbers, and a static marine face.

Image

What we will do first is animate the marine so he can look around suspiciously. So open up <doom 3>/classichud/guis/hud.gui, or create a new one with the code provided above, use our classic hud testing shortcut to run DOOM 3 in windowed mode with the HUD preview, and let's get this party started.

If you remember the original game, the marine face would, from time to time, look left, right, or centered, as if he was checking if there was something to worry about on his sides. This was more of a random movement. We won't do it here, though; the animation will be scripted and not random at all -- I don't think the GUI system can handle anything random without the use of crazy engine interactions so it'll be easier this way.

Ok, so we have to create a simple animation: marine faces up front, faces left, faces up front again, then faces right, then gets back to facing up front, and continue like this on an endless loop. Easy.

This is our marine code as it is now.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Normal face
//-----------------------------------------------------------------------------

   windowDef marineFace {
      rect   290,418,64,64
      visible   1
      background   "guis/assets/hud_face_100_center"
      matcolor   1,1,1,1
   }

}


What we will do now is add onTime events. Like onNamedEvents, onTime is an special event block that gets executed when a certain condition has been met - in this case, after a certain time has passed. With the use of several onTime blocks, you can create timed animations... emulating a timeline, in a sort of way.

Let's add the first onTime event.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Normal face
//-----------------------------------------------------------------------------

   windowDef marineFace {
      rect   290,418,64,64
      visible   1
      background   "guis/assets/hud_face_100_center"
      matcolor   1,1,1,1

      onTime 0 {
         // set face: LOOKING STRAIGHT
         set "background" "guis/assets/hud_face_100_center";
      }
   }
}


So, when the GUI system time reaches 0 milliseconds (that is, right after starting), it sets the windowDef background to "guis/assets/hud_face_100_center". This will have absolutely no action in this case - the default background is already this. This will be used, however, when looping our animation, so we need to reset it from time to time.

Now we can simply put the other images on our animation, with different times.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Normal face
//-----------------------------------------------------------------------------

   windowDef marineFace {
      rect   290,418,64,64
      visible   1
      background   "guis/assets/hud_face_100_center"
      matcolor   1,1,1,1

      onTime 0 {
         // set face: LOOKING STRAIGHT
         set "background" "guis/assets/hud_face_100_center";
      }

      onTime 2500 {
         // set face: LOOKING LEFT
         set "background" "guis/assets/hud_face_100_left";
      }

      onTime 3500 {
         // set face: LOOKING STRAIGHT
         set "background" "guis/assets/hud_face_100_center";
      }

      onTime 4000 {
         // set face: LOOKING RIGHT
         set "background" "guis/assets/hud_face_100_right";
      }

      onTime 4500 {
         resetTime "0";
      }
   }
}


Easy enough. Notice, now, that when the windowDef animation time reaches 4500 milliseconds, a new command is issued - resetTime. This command will reset the time back to 0, that is, executing the statements inside of the "onTime 0 {}" block once again, and ultimately creating a looped animation.

Now, get back to the game, do a reloadguis and let's see if it works.

Image
Our marine starts looking right at you..

Image
Do a quick glance at the left...

Image
Gets back to looking straight...

Image
Looks right...

Image
Then gets straight again and restart the cycle.

Nice, it's working. You can play with the time values if you wish; you can make the animation faster, for example, if you want a marine face on crack.

We get to an important point right now. On the original DOOM, the marine face wasn't always this mean-looking straight face. Depending on how much damage he had taken, the marine face would start to bleed and look more and more wasted. Just like the game had graphics for straight, right and left looks with a perfect normal marine, it also had graphics for looking straight, left and right for the marine in increasingly bad shape.

We have to use it on our animation cycle, then. Instead of just setting the correct face at the time, we have to set the correct face related to the health.

Getting back to our "animation reset" code block, it looks like this.

Code:
(...strip...)

      onTime 0 {
         // set face: LOOKING STRAIGHT
         set "background" "guis/assets/hud_face_100_center";
      }

(...strip...)


But instead of using "guis/assets/hud_face_100_center" as the background, we have to be able to select which is the correct image at the time. We'll do something like this:

  • If the player health is higher than 79, use "guis/assets/hud_face_100_center"
  • Otherwise, if the player health is higher than 59, use "guis/assets/hud_face_80_center"
  • Otherwise, if the player health is higher than 39, use "guis/assets/hud_face_60_center"
  • Otherwise, if the player health is higher than 19, use "guis/assets/hud_face_40_center"
  • Otherwise, if the player health is higher than 0, use "guis/assets/hud_face_00_center"
  • Otherwise (the player is dead) use "guis/assets/hud_face_00_center"

And, of course, this is achieved by the use of conditional if/else statements.

Code:
(...strip...)

      onTime 0 {
         // set face: LOOKING STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_center";
         } else {
         }
      }

(...strip...)


Notice that on the IF line, I'm comparing the built-in variable "gui::player_health" against 80. If it's higher than or equal to 80, use "guis/assets/hud_face_100_center" as a background. If not, skip to the "else" block, where we will do other checks. To make a long story short, this will be the end result.

Code:
(...strip...)

      onTime 0 {
         // set face: LOOKING STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_center";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_center";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_center";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_center";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_center";
                     } else {
                        set "background" "guis/assets/hud_face_00_center";
                     }
                  }
               }
            }
         }
      }

(...strip...)


This may look bad to read, but it's how it goes with nested IF statements. It continuously check each condition, until it finally finds out the background value it has to use. Unfortunately, the GUI system doesn't seem to have switch statements or single-line IF statements, so this is probably the simplest way to do it.

Anyway, let's check how (and if) it works in game. Back to the game, do a reloadguis, and do something to take damage (I usually just fall from the bridge).

Image

Hey, cool - our marine nose's bleeding! (off-topic: You know what this means...)

However, there's something wrong. As soon as the animation cycle starts and the marine looks to the right and left sides, his face is back to the normal, brand new state.

That is, of course, due to the fact that we only changed the first step on the animation cycle. We have to add conditional IF statements on all other time blocks, so he'll look to the sides using the correct animation. This can be lengthy, but it's easy enough - just a repetition of the concept used above - so here we go.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Normal face + look around animation
//-----------------------------------------------------------------------------

   windowDef marineFace {
      rect   290,418,64,64
      visible   1
      background   "guis/assets/hud_face_100_center"
      matcolor   1,1,1,1

      onTime 0 {
         // set face: LOOKING STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_center";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_center";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_center";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_center";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_center";
                     } else {
                        set "background" "guis/assets/hud_face_00_center";
                     }
                  }
               }
            }
         }
      }

      onTime 2500 {
         // set face: LOOKING LEFT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_left";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_left";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_left";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_left";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_left";
                     }
                  }
               }
            }
         }
      }

      onTime 3500 {
         // set face: LOOKING STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_center";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_center";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_center";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_center";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_center";
                     } else {
                        set "background" "guis/assets/hud_face_00_center";
                     }
                  }
               }
            }
         }
      }

      onTime 4000 {
         // set face: LOOKING RIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_right";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_right";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_right";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_right";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_right";
                     }
                  }
               }
            }
         }
      }

      onTime 4500 {
         resetTime "0";
      }

   }
}


So here we go. You'll notice that if the health is less than 1, it will never get updated on the animation cycles too - he's dead, there's no looking around animation.

Well, back to the game, reloadguis, wait for him to look around and...

Image

Nice. He even looks a bit more worried when looking to the left. Let me take more damage for the effect to be a bit more obvious...

Image

Cool.

Now, one more thing, something not so obvious but that should not be forgotten. While our animation looks and works good, we have to make sure the marine face gets updated as soon as he is hit; or else, we're at the risk of having a bright looking marine face when he's barely alive, even if it's for just a few seconds.

So we have to use one new named event - "updateArmorHealthAir". This HUD event is fired up when there's some change on the armor, health, or air values, so it's fired when the player takes damage.

Getting back to the begin of our HUD script file, it looks like this.

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1

//=============================================================================
// Events
//=============================================================================

(...strip...)


So let's add the main block for our event.

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1

//=============================================================================
// Events
//=============================================================================

   onNamedEvent updateArmorHealthAir {
   }

(...strip...)


Easy enough. What we need to do is make sure the marine face windowDef gets updated with the right face. We don't need to have all those IF statements again to check which one is the correct marine face to be displayed, though; since the marine face uses an animation, we just need to reset its time -- it'll automatically fire up the updating animations. So we just do a resetTime again, this time with two parameters so we can reset another windowDef 'remotely'.

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1

//=============================================================================
// Events
//=============================================================================

   onNamedEvent updateArmorHealthAir {
      resetTime "marineFace" "0";
   }

(...strip...)


Easy. Now, when the player takes damage or grabs some health, his animation will restart and immediately update the face with the correct one. Test it in-game if you want; I won't provide a screenshot for this change because it won't make much sense (it'll be the same as the previous ones).

We can now add the next step in our marine face: makes him grin when he grabs a new gun.

We could make it in two ways: first, when a new gun is picked up, change the "background" of the marineFace windowDef to the grinning face for a while. Or second, create a new windowDef with a grinning face and make it visible or invisible.

I tried the first option, since it'd be easier with less windowDefs, but in the end I had some trouble with resetTime. While you can change the time the windowDef is at the moment, if you have some alternative 'animation paths' to follow, there's a big can of worms to be dealt with. When you do a resetTime, it looks like all previous time code blocks are executed, so I couldn't have alternative animations... it's complicated to explain and I don't want to extend much with personal ramblings on this lesson, so suffice to say I decided for the second alternative.

So we will have a simple (hidden) grinning windowDef at the same position of the original marine face. When a new gun is grabbed, the animation on this grinning windowDef is activated: it makes itself visible, makes the normal marineFace invisible, and after a while gets back to normal. This is probably easier for the engine to do, since there will be no file reading involved, just toggling windowDefs visibilities on and off. While I don't believe the background setting we did in the normal cycle of the marine face is heavy at all - backgrounds get cached by the engine - you never know what to expect.

Anyways, back to our HUD code, we have the marineFace windowDef.

Code:
(...strip...)

//-----------------------------------------------------------------------------
// Normal face + look around animation
//-----------------------------------------------------------------------------

   windowDef marineFace {
      rect   290,418,64,64
      visible   1
      background   "guis/assets/hud_face_100_center"
      matcolor   1,1,1,1

(...strip...)


We'll add a new windowDef, marineFacePickup, and make it invisible, right above our marineFace windowDef (order doesn't matter anyways).

Code:
(...strip...)

//-----------------------------------------------------------------------------
// Pickup (happy) overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePickup {
      rect   290,418,64,64
      visible   0
      background   "guis/assets/hud_face_100_pickup"
      matcolor   1,1,1,1
   }

//-----------------------------------------------------------------------------
// Normal face + look around animation
//-----------------------------------------------------------------------------

   windowDef marineFace {

(...strip...)


Notice how the "marineFacePickup" windowDef has a "visible" property of 0 - it's hidden by default.

Now, we need a timed animation. This animation will be fired when a new weapon is picked up, and it'll simply make the marineFacePickup windowDef visible while hiding the normal marineFace windowDef. It goes like this...

Code:
(...strip...)

//-----------------------------------------------------------------------------
// Pickup (happy) overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePickup {
      rect   290,418,64,64
      visible   0
      background   "guis/assets/hud_face_100_pickup"
      matcolor   1,1,1,1
      notime   1

      onTime 0 {
         set "marineFacePickup::visible" "1";
         set "marineFace::visible" "0";
      }

      onTime 800 {
         set "marineFacePickup::visible" "0";
         set "marineFace::visible" "1";
      }
   }

(...strip...)


Okey, there are a few things to be considered. First, notice I've added a new property to the windowDef "notime" with a value of "1". This tells the GUI system that, even though this windowDef has some time event blocks, they should not be used right now, they shouldn't fire up by default - they will be fired manually through a resetTime command. By using "notime 1", you have the ability to create complex animations elsewhere - animations that can be fired when you desire.

Now, looking at the animation: when it is fired (time 0), it simply makes itself visible and hides the original marineFace. When it reaches 800, it gets back to normal. Pretty easy.

We're almost ready to go. While our happy marine face is done, we still have to fire it. Now, remember we already used a "newWeapon" named event on the previous lesson...

Code:
(...strip...)

   onNamedEvent newWeapon {
      // Now turns on the correct number when a new weapon is picked
      if ("gui::newweapon" == 2) {
         set "gun_3::forecolor" "1,1,0,1";
      }

(...strip...)


...we just need to add one more command to this event: resetTime firing up the animation on "marineFacePickup".

Code:
(...strip...)

   onNamedEvent newWeapon {
      // Makes the marine face happy
      resetTime "marineFacePickup" "0";

      // Now turns on the correct number when a new weapon is picked
      if ("gui::newweapon" == 2) {
         set "gun_3::forecolor" "1,1,0,1";
      }

(...strip...)


Great. Save, do a reloadguis on the game, and let's test.

Image
So we're getting close to the gun...

Image
...and grabbing it. Notice the happy look in the face of the marine. Mad happy.

It's all fine and dandy, but there's something wrong. Of course, our marine already had his nose bleeding, but when he grabs the shotgun, he's all fine again. Obviously, the health/bleeding states also apply to the happy marine face, so we have to add conditional statements to the marineFacePickup animation too. Just like the previous marineFace animation, we do it with one big nest of if statements at the "onTime 0 {}" event block, this time right after everything visible or invisible.

Code:
(...strip...)

//-----------------------------------------------------------------------------
// Pickup (happy) overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePickup {
      rect   290,418,64,64
      visible   0
      background   "guis/assets/hud_face_100_pickup"
      matcolor   1,1,1,1
      notime   1

      onTime 0 {
         set "marineFacePickup::visible" "1";
         set "marineFace::visible" "0";
         // set face: PAIN STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_pickup";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_pickup";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_pickup";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_pickup";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_pickup";
                     }
                  }
               }
            }
         }
      }

      onTime 800 {
         set "marineFacePickup::visible" "0";
         set "marineFace::visible" "1";
      }
   }

(...strip...)


Getting back to the game, and doing a "reloadguis; map game/mp/d3dm1" console command to reset the map...

Image
The marine is in a bad shape, then we approach the shotgun...

Image
...now he's in a bad shape and happy. Mad happy.

Great! We got our marine looking to the sides, changing his face according to his damage status, getting happy when he grabs a new gun... what else?

The pain look, that's what else. When the marine gets hit, his face stretches in pain. Just like the weapon pickup we did above, this is done with a new overlay windowDef with a timed animation and an event block, but there's a little twist I'll discuss further. Let's get started.

This is part of our code right now, the beginning of the marineFaceHappy windowDef.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Pickup (happy) overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePickup {

(...strip...)


We'll add a new windowDef on top of it (again, order doesn't really matter): marineFacePain.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Pain overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePain {
      rect   290,418,64,64
      visible   0
      background   "guis/assets/hud_face_100_paincenter"
      matcolor   1,1,1,1
      notime   1

      onTime 0 {
         set "marineFacePain::visible" "1";
         set "marineFace::visible" "0";
         // set face: PAIN STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_paincenter";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_paincenter";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_paincenter";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_paincenter";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_paincenter";
                     }
                  }
               }
            }
         }
      }

      onTime 500 {
         set "marineFacePain::visible" "0";
         set "marineFace::visible" "1";
      }
   }

//-----------------------------------------------------------------------------
// Pickup (happy) overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePickup {

(...strip...)


No big deal here: it works just like the marineFacePickup windowDef. The first real difference done is the time on the second block (500 instead of 800), since I wanted the "pain" face animation to last shorter. The rest has been adapted to work with the pain backgrounds and marineFacePain windowDef accordingly.

There's one catch, though. You see, when the marineFacePickup animation was fired, we made it visible and hide the standard marineFace.

Code:
(...strip...)

      onTime 0 {
         set "marineFacePickup::visible" "1";
         set "marineFace::visible" "0";

(...strip...)


But since we're dealing with two overlay windowDefs, now, this won't do. We could have the marine grab a gun at the same (or at a close) time he suffered damage, so we'd end up with two different overlay windowDefs standing on top of each other.

To avoid this, we'll have to make sure that when each animation/overlay windowDef is activated, it disables all previous overlays. So we need to make sure the marineFacePickup disables the marineFacePain windowDef, and vice versa. We do this by adding one more line to the blocks, like this:

Code:
(...strip...)

      onTime 0 {
         set "marineFacePickup::visible" "1";
         set "marineFacePain::visible" "0";
         set "marineFace::visible" "0";

(...strip...)


Easy enough. After doing this where it's needed, this is what our "overlay" windowDefs will look like.

Code:
(...strip...)

//=============================================================================
// Marine faces
//=============================================================================

//-----------------------------------------------------------------------------
// Pain overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePain {
      rect   290,418,64,64
      visible   0
      background   "guis/assets/hud_face_100_paincenter"
      matcolor   1,1,1,1
      notime   1

      onTime 0 {
         set "marineFacePickup::visible" "0";
         set "marineFacePain::visible" "1";
         set "marineFace::visible" "0";
         // set face: PAIN STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_paincenter";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_paincenter";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_paincenter";
               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_paincenter";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_paincenter";
                     }
                  }
               }
            }
         }
      }

      onTime 500 {
         set "marineFacePickup::visible" "0";
         set "marineFacePain::visible" "0";
         set "marineFace::visible" "1";
      }
   }

//-----------------------------------------------------------------------------
// Pickup (happy) overlay
//-----------------------------------------------------------------------------

   windowDef marineFacePickup {
      rect   290,418,64,64
      visible   0
      background   "guis/assets/hud_face_100_pickup"
      matcolor   1,1,1,1
      notime   1

      onTime 0 {
         set "marineFacePickup::visible" "1";
         set "marineFacePain::visible" "0";
         set "marineFace::visible" "0";
         // set face: PAIN STRAIGHT
         if ("gui::player_health" >= 80) {
            set "background" "guis/assets/hud_face_100_pickup";
         } else {
            if ("gui::player_health" >= 60) {
               set "background" "guis/assets/hud_face_80_pickup";
            } else {
               if ("gui::player_health" >= 40) {
                  set "background" "guis/assets/hud_face_60_pickup";

               } else {
                  if ("gui::player_health" >= 20) {
                     set "background" "guis/assets/hud_face_40_pickup";
                  } else {
                     if ("gui::player_health" >= 1) {
                        set "background" "guis/assets/hud_face_20_pickup";
                     }
                  }
               }
            }
         }
      }

      onTime 800 {
         set "marineFacePickup::visible" "0";
         set "marineFacePain::visible" "0";
         set "marineFace::visible" "1";
      }
   }

//-----------------------------------------------------------------------------
// Normal face + look around animation
//-----------------------------------------------------------------------------

(...strip...)


There's still one catch to this code, though. If, for example, a damage animation is fired, then a pickup animation is fired, the marine face will get back to normal as soon as the damage animation finishes. This could be avoided with the use of some auxiliary variables, but it'll be a rare case and I'll chose to ignore it. It'll never be anything so blatant as having two faces activated at the same time on top of each other.

Now we need to get this pain animation fired just like we did with the pickup animation. This is done using the "updateArmorHealthAir" name event again, just like we used it to reset the marine face status. This is what our updateArmorHealthAir event block looks right now.

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1

//=============================================================================
// Events
//=============================================================================

   onNamedEvent updateArmorHealthAir {
      resetTime "marineFace" "0";
   }

(...strip...)


We can't just fire up the marineFacePain animation when there's a change to health, though: this event is fired when there's damage (less health) or when a health pack is picked up (more health). This is the twist I mentioned earlier: we need to make sure the animation only gets fired when the new health is less than the previous health, that is, damage has been done.

To do this, we need to use an auxiliary variable. I'll call it "lastHealth", since it'll control the last known player health. We add a new variable by declaring it as float on the windowDef properties declaration -- in this case I'll use the desktop windowDef itself.

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1
   float lastHealth 0

//=============================================================================
// Events
//=============================================================================

   onNamedEvent updateArmorHealthAir {
      resetTime "marineFace" "0";
   }

(...strip...)


I've also initialized it with a value of "0".

What we have to do - in the "updateArmorHealthAir" block - is this: check if the current health is more or less than the previous known health.

If it's more, the player has grabbed a health pickup, so just update the marineFace normally.

If it's less, fire up the damage animation.

After this is done, update the auxiliary variable "lastHealth" with the current value, so we know what to compare to in the next iteration of health update. So here we go...

Code:
windowDef Desktop {
   rect   0,0,640,480
   visible   1
   noevents   1
   nocursor  1
   float lastHealth 0

//=============================================================================
// Events
//=============================================================================

   onNamedEvent updateArmorHealthAir {
      if ("lastHealth" != "gui::player_health") {
         if ("lastHealth" > "gui::player_health" && "gui::player_health" > 0) {
            // Health change, less
            resetTime "marineFacePain" "0";
         } else {
            // Health change, more
            resetTime "marineFace" "0";
         }
         set "lastHealth" "gui::player_health";
      }
   }

(...strip...)


Easy. You'll notice I also added a "lastHealth" != "gui::player_health" condition - this way, the animation update will only get fired when there's a change in health. Or else, we'd have the risk of having the marineFace animation reset when the player picked up armor or his air supply level changed.

Back to the game, testing again through doing a reloadguis and resetting the map...

Image
We're on the top of the bridge...

Image
...the marine falls down, takes damage, and makes a pain face.

Image
We get next to the health pack...

Image
...and sure enough, the pain animation doesn't fire - rather, his face updates and he stops bleeding.

Wew! That's it, our marine face works. That was kind of long, but covered animation and a few more events and conditional cases to a great extent. I'm pretty happy with the results, even though the marine can't look to the sides he's taking damage from.

The next lesson - hopefully, as I depend on information probably present on the SDK - will be on putting the original DOOM fonts to work, making the graphic assets on our HUD 100% like the original. See you there!

Download final mod files in DOOM 3-friendly PK4 format (42kb) (use on the "<doom 3>/classichud" folder)

_________________
[D3W] Staff :: Additional D3/id Tech modding links: General help guide | http://www.modwiki.net | http://www.iddevnet.com | http://www.quake3bits.com


Last edited by zeh on Sat Aug 13, 2005 3:59 pm, edited 7 times in total.

Top
 Profile E-mail  
 
 Post subject:
PostPosted: Sun Sep 05, 2004 3:59 pm 
Offline
picked up 100 armour

Joined: Thu Aug 12, 2004 8:11 pm
Posts: 115
Damn nice again zeh!


Top
 Profile  
 
 Post subject:
PostPosted: Sun Sep 05, 2004 4:03 pm 
Offline
found a secret
User avatar

Joined: Tue Aug 03, 2004 5:31 pm
Posts: 509
Location: Sweden
I need time to try all this out man!

GJ as hell, keep up the awsome work.

_________________
Doom³ Tutorial Listings


Top
 Profile E-mail  
 
 Post subject:
PostPosted: Sun Sep 05, 2004 5:18 pm 
Offline
found a secret

Joined: Thu Aug 26, 2004 1:34 pm
Posts: 559
Location: info_player_start
Once again zeh: you rock my world :)


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 06, 2004 6:11 pm 
Offline
a gun & a nice word
User avatar

Joined: Sat Jan 11, 2003 8:30 pm
Posts: 8482
Location: Orlando, FL
Great tutorial zeh. :)

_________________
Image Staff
Learn something today? Why not write an article about it on modwiki.net?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 07, 2004 7:21 pm 
Offline
is connecting to Doom3world.org

Joined: Tue Sep 07, 2004 7:14 pm
Posts: 2
Location: Los Angeles
I was making THIS, and they told me to look here.
Would you add a new face for GOD mode?

_________________
Fragger Mod


Top
 Profile  
 
 Post subject: what do i do?
PostPosted: Mon Sep 07, 2009 6:31 pm 
Offline
is connecting to Doom3world.org
User avatar

Joined: Mon Sep 07, 2009 6:27 pm
Posts: 3
Firstly, great job with the classic hud conversion, but I have a question. How do I get it to work on doom 3?

_________________
Today i drink, tomorrow i drink again.


Top
 Profile E-mail  
 
 Post subject: Re: Complete GUI Scripting - 9: Animating the marine face HUD
PostPosted: Mon Sep 07, 2009 6:41 pm 
Offline
Last man standing
User avatar

Joined: Fri Apr 22, 2005 10:55 pm
Posts: 1095
It's written at the end.
Download the pk4, place it in "<doom 3>/classichud" folder (create it if needed!) and load it from the mod list.

_________________
Fragging Free - a frantic Doom3:ROE single-player modification.


Top
 Profile  
 
 Post subject: Re: Complete GUI Scripting - 9: Animating the marine face HUD
PostPosted: Mon Sep 07, 2009 6:58 pm 
Offline
is connecting to Doom3world.org
User avatar

Joined: Mon Sep 07, 2009 6:27 pm
Posts: 3
The trouble is, it doesn't show up in the mod list. How do I get the game to read the folder as a usable mod?

_________________
Today i drink, tomorrow i drink again.


Top
 Profile E-mail  
 
 Post subject: Re: Complete GUI Scripting - 9: Animating the marine face HUD
PostPosted: Tue Sep 08, 2009 7:33 am 
Offline
Last man standing
User avatar

Joined: Fri Apr 22, 2005 10:55 pm
Posts: 1095
Every folder is usable for mods.
Only requirements:
- it has to be in the main doom3 folder (at the same level as "base")
- it has to include a pk4

I tried to dowload this pk4, created "classichud" folder, placed it in there, started the game and the "classichud" mod does appear in the mod list.

_________________
Fragging Free - a frantic Doom3:ROE single-player modification.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 08, 2009 5:15 pm 
Offline
is connecting to Doom3world.org
User avatar

Joined: Mon Sep 07, 2009 6:27 pm
Posts: 3
Ah... worked that out. Thanks for the help!

_________________
Today i drink, tomorrow i drink again.


Top
 Profile E-mail  
 
 Post subject: Re: Complete GUI Scripting - 9: Animating the marine face HUD
PostPosted: Tue Sep 08, 2009 8:54 pm 
Offline
picked up 75 health
User avatar

Joined: Thu Nov 07, 2002 10:56 am
Posts: 80
This is too much awesome. Getting now.


Top
 Profile  
 
Display posts from previous:  Sort by  
 [ 12 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 0 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group