====== Lab 6 - Avatars and HUD ======
==== Before You Start ====
* Download and unzip the [[http://www.ru.is/kennarar/hannes/classes/ve2008/Lab6Assets.zip|Lab 6 Asset File]] into your working directory
==== Roaming Ralph with a HUD ====
{{public:t-vien-08-1:ve-lab6-screenshot.png|}}
In this exercise you'll start with a fully working 3rd person world with Ralph as your avatar. You'll then add a couple of classic HUD features, namely the **health bar** and the **overhead map**.
- **Setting Up:**\\ Get the **advancedralph.py** running and explore the controls, making sure you understand how to control both Ralph himself and the camera (rotating and tilting by approaching the edge of the screen with your mouse; zooming with the mouse wheel). Have the application run in **Fullscreen** mode, simply by changing False to True in the call to **setFullscreen** near the bottom of the code. It is easier to control the camera with the mouse in this mode, but the windowed mode might be better while debugging.
- **Adding a Health Bar:**\\ The following code is a **HealthBar** object that can be attached to a scene graph. A health bar can display some fractional property in a graphical manner, such as the health or power of a character. Attach this bar to the Avatar so that the bar appears to be floating over the Avatars head (use the regular 3D scene graph). Make sure you can see it (but it may disappear when you start moving around - see below)
class HealthBar(NodePath):
""" Adapted from drwr's code on the Panda 3D forums """
def __init__(self):
NodePath.__init__(self, 'healthbar')
self.group = self.attachNewNode('group')
self.group.setPos(-0.5,0,0)
cmfg = CardMaker('fg')
cmfg.setFrame(0, 1, -0.1, 0.1)
self.fg = self.group.attachNewNode(cmfg.generate())
cmbg = CardMaker('bg')
cmbg.setFrame(-1, 0, -0.1, 0.1)
self.bg = self.group.attachNewNode(cmbg.generate())
self.bg.setPos(1, 0, 0)
self.fg.setColor(0, 1, 0, 1.0)
self.bg.setColor(0.5, 0.5, 0.5, 1.0)
self.value = 0.8
self.update()
def update(self):
self.fg.setScale(self.value, 1, 1)
self.bg.setScale(1.0 - self.value, 1, 1)
def setHealth(self, value):
self.value = value
self.update()
def decrease(self, delta):
if (self.value - delta) >= 0:
self.value -= delta
self.update()
- **Improving the Health Bar:**\\
- Since your health bar rotates with your Avatar, you may notice that it fully disappears when you're looking at the Avatar from another angle. You can use the so called **[[http://panda3d.org/wiki/index.php/Billboard_Effects|Billboard effect]]** to ensure that the health bar always faces you, no matter how the Avatar is facing. You enable this effect on any nodepath by calling its **setBillboardAxis** method. Try using this to make your health bar always visible.
- The healthbar calls a little too much attention to itself on the display. Make it semi-transparent so that it blends in better. Don't forget to enable transparency in the scene graph (HINT: setTransparency).
- **Displaying Dynamic Health:**\\ Now make the healthbar start decreasing **while** the Avatar is running and then gain full health when the Avatar stops running. HINT: For any FSM class, you can query the **state** member variable to see what state it is in.
- **Adding a Sliding HUD Panel:**\\ So far you have been adding visual information to the 3D scene, but let's create a simple HUD (Heads-Up-Display) panel that stays in front of the 3D scene, no matter where you are looking. For this construction, start filling in the **setupHUD()** method in **World**. You will actually just create a single HUD panel that will contain two elements: (1) A button that makes the panel slide up and down, and (2) an overhead map that works like a GPS. First, get the panel showing and the panel sliding button working. Create a new node under "aspect2d" that you can call **panel**. You attach everything to this node that you want sliding with it.\\
- Attach the panel background image to the panel node. You can create the panel image as a node in a tree with a single call to **[[http://panda3d.org/wiki/index.php/OnscreenImage|OnScreenImage]]** - you set its parent with the **parent** attribute (otherwise the parent defaults to aspect2d). Create the panel image with the texture called **panel.tif** and scale it to **(0.36,1,0.40)**. Within the World **redraw** method, set the position of this panel to **(-base.getAspectRatio()+0.36,0,-0.60)**.
- Attach the panel button to the panel node. You can use the following button specification:
= DirectButton(
text_fg=(1,1,1,1),
text_shadow=(0,0,0,1),
frameColor=(0.4,0.5,0.4,1),
borderWidth=(0.1,0.1),
relief=DGG.RAISED,
text=("Map","Map","> Map <",""),
pressEffect=1,
scale=0.035,
frameSize=(-3,3,-0.5,1.0),
command= ,
rolloverSound = ,
clickSound = ,
parent=)
You can use the sounds from this week's gui demo or simply not specify new sounds to use the default Panda3D sounds. The function that the button calls, should be a function that toggles the HUD panel between fully visible and mostly hidden. There's already a **toggleHUD** method stub you could use. Remember that you have to set the button position in the **redraw** method. A good position of the button is **(-base.getAspectRatio()+0.535,0,-0.26)**.
- Set up a couple of intervals that will slide the panel in and out of view. You can make it slide down until all you can see is the button. Fill in the **toggleHUD** method to start the correct interval when the button is pressed.
- **Adding an Overhead Map:**\\ The following code is an **OverheadMap** object that can be attached to a scene graph. Add this object to your panel and have it show you where your avatar is located any given moment. It is good for you to know that the world dimensions in the x-y plane are approximately **(-128,-68,48,48)**. You will need to scale and reposition your map until it fits the panel depression. Also, to enable transparency on the 2D scene graph, you'll need to call **setTransparency** again because it is a separate scene graph from **render**. Make sure this is all working fine.
class OverheadMap(NodePath):
def __init__(self, x1, y1, x2, y2):
""" Create a square map with the given world corners in the x-y plane """
NodePath.__init__(self, 'overheadmap')
self.setBoundary(x1,y1,x2,y2)
cmbg = CardMaker('bg')
cmbg.setFrame(0,1,0,1)
self.prime = self.attachNewNode(cmbg.generate())
self.prime.setColor(0.5,0.7,0.5,0.1)
cmav = CardMaker('am')
cmav.setFrame(-0.02,0.02,-0.02,0.02)
self.avatar = self.prime.attachNewNode(cmav.generate())
self.avatar.setPos(0,0,0)
self.avatar.setColor(0,1,0,1)
def addLandmarks(self, landmarks, color=Vec4(1,0,0,0.5)):
""" Take in a list of (x,y) touples in world coordinates and place them
as little dots on the map in the given color """
## To be filled in...
def setBoundary(self, x1, y1, x2, y2):
""" Define the extent of the map in world coordinates. Create the proper
offset and scaling values that transform world coordinates to map coordinates"""
self.xoffset = x1
self.xscale = 1.0/(x2-x1)
self.yoffset = y1
self.yscale = 1.0/(y2-y1)
def setAvatarPos(self, x, y):
""" Sets the position of the avatar marker to the given world coordinates """
self.avatar.setPos((x-self.xoffset)*self.xscale,-0.01,(y-self.yoffset)*self.yscale)
- **Populating the Overhead Map:**\\ Now add some landmarks to your world and have them show up on your map as little red squares. You can use the **Models/box.egg** object. Initialize a few of these boxes by storing a list of (x,y) tuples and then iterate through the list to place them in the world. You should also be able to send the list directly into the map's **addLandmarks** method and have the locations of the boxes light up. You'll need to fill in the rest of that method. Test to see if this is all working.
- **Make it a Game:**\\ If you feel adventurous, change this application into a game where the health bar starts to drop as time passes and the only way to regain full health is to touch a box, which then disappears. You win the game if you reach all the boxes. By randomizing the initial box locations, you'll really need the map to help you play this game.