Game Development Community

Making a 2D billboarded life meter on a player

by Kuju Manila · in Torque Game Engine · 07/22/2007 (8:11 am) · 10 replies

Hello all.

I've been fiddling with the player.cc engine code specifically the renderImage function to render a life meter using a simple color-filled quad directly facing the camera. I've been trying to get it to set it's projection to gluOrtho2D but I can't seem to display anything on the screen with it.

Btw, my plan of action to do this in a high-level perspective are as follows:
1. Set OpenGL state to a 2D orthographic projection
1.1. Draw a simple quad to check if it's working right.
2. Get the 3D coordinate of the player position and the 3D coordinate of the camera position to compute for the directional vector.
3. Convert 3D player position to 2D.
4. Compute offset and add to 2D camera.
5. Draw on 2D space.

As far as I can go, I am stuck with 1. to 1.1. and not having to be able to render a simple 2D quad that should be rendering in front of the whole screen.

How do I do this? Looks like gluOrtho2D doesn't work, or something is different with the coordinate system in Torque?

#1
07/22/2007 (9:15 am)
There is a simple trick of making the rotational parts of the object matrix match the camera's to get billboarding. This should work - and it's a case of changing a few components of the object matrix.
#2
07/22/2007 (10:30 am)
Since it sounds like you want your life meter on top, an alternative approach would be to do the meter as a gui element and then dynamically derive its position from the player's position projected into screen coordinates.

GuiShapeNameHud does much the same thing with text, You can look at that code for information about how to project the player's position into the gui control's positioning space.
#3
07/25/2007 (5:03 am)
^ Thanks Man! Well I'm trying to figure out the basic 3D math of Torque too that's why I didn't go for the GuiControl yet. But, ridding off the masochism I tried your suggestion on GuiShapeNameHud and I saw there what I'm looking for and made one just right.

I made a GuiRTSHealthBar Gui control and it's great so far. However, I'm having trouble getting the values of my Players life bar. On my player class I added something like:
Player::GetMaxRTSLife(); // gets the maximum amount of life it can hold
Player::SetMaxRTSLife( F32 p_Value ); // sets the maximum amount of life it can hold
Player::GetValueRTSLife(); // gets the current amount of the life
Player::SetValueRTSLife( F32 ); // sets the current amount of the life

Now, my GuiRTSHealthBar gui control has it's onRender method traverse throught the SimSet for the shapes that has PlayerObjectType and cast it to Player*. But whenever I access the Player methods above it gives off the wrong value, which is 0. But, whenever I access the player's methods using the in-game console using script, the values are correct and accessible. Why is this? My only I guess is that it has to do with server-client thing, and I need confirmation.
#4
07/25/2007 (6:53 am)
Quite possibly a server-client thing. Those internal values that your added methods manipulate; are you propagating them down to the client via packUpdate() and unpackUpdate()?
#5
07/25/2007 (5:07 pm)
^ No. I haven't yet. I'm doing it right now but I don't know how many bits I should provide for my:
stream->writeFloat( GetMaxRTSLife(), ? );
stream->writeFloat( GetValueRTSLife(), ? );

How will I know how much bits I should give for a datatype?

Update:
writeFloat seems to be a pain on the network packet. Since I just dealing with integer health points, I just changed it to integers. Hence, I used writeInt and readInt with bitcount of 16. On my, GuiRTSHealthBar gui control, it prints out the correct values now. However, when I use my console method setValueRTSLife() and change it -- the change is not reflected on the ghosted object. Anything else I am missing? I already followed this documentation: http://tdn.garagegames.com/wiki/Torque/Networking/Making_a_Ghostable_Object
#6
07/26/2007 (12:20 am)
Int have 32bit as have Float
16bit would be short.
#7
07/26/2007 (7:08 pm)
I changed the bitcounts of Int or float, but it doesn't do any change at all. Initially when the code is on the first iteration it gets the correct values but on the successive calls it doesn't. Scripting-wise the ghost doesn't get updated.

Here are my codes:

near the end of Player::packUpdate()
//+ AOV 070726
   if( stream->writeFlag( mask & RTSBarMask ) )
   {
	   stream->writeFlag( IsRTSBarShown() );
	   stream->writeInt( GetMaxRTSLife(), 32 );
	   stream->writeInt( GetValueRTSLife(), 32 );
   }
   //- AOV 070726

near the end of Player::unpackUpdate()
//+ AOV 070726
   if( stream->readFlag() )
   {
	   ShowRTSBar( stream->readFlag() );
	   SetMaxRTSLife( stream->readInt( 32 ) );
	   SetValueRTSLife( stream->readInt( 32 ) );
   }
   //- AOV 070726


my code that renders the life bar on GuiRTSHealthBar::onRender()
...

                                                // Render the shape's name
			if( mShowFrame && shape->IsRTSBarShown() )
			{
				dglDrawRect( Point2I( (S32) projPnt.x - 50, (S32) projPnt.y ),
					Point2I( (S32) projPnt.x + 50, (S32) projPnt.y + 10 ),
					mFrameColor );
				dglDrawRectFill( Point2I( (S32) projPnt.x - 49, (S32) projPnt.y + 1 ),
					Point2I( (S32) ( projPnt.x - 49 ) +
					( 99 * ( shape->GetValueRTSLife() * 1.0 / shape->GetMaxRTSLife() ) ),
					(S32) projPnt.y + 10 ),
					mFillColor );
				Con::errorf( "This shape %s is %d/%d. Fraction is %d",
					shape->getIdString(),
					shape->GetValueRTSLife(), shape->GetMaxRTSLife(),
					( shape->GetValueRTSLife() * 1.0 / shape->GetMaxRTSLife() )
					);
			}
#8
07/26/2007 (8:35 pm)
When values for MaxRTSLife, etc are changed on the server are you calling setMaskBits()? You need to call setMaskBits() to trigger a new update to the clients. setMaskBits() takes a mask that you also use to flag what portions of the packUpdate() need to be packed. ActionMask is an example mask used in Player.
#9
07/27/2007 (4:37 am)
Yes sir. I already did.

Here it is:
void Player::SetMaxRTSLife( S32 p_Value )
{
   mMaxRTSLife = p_Value;

   if( mMaxRTSLife < mValueRTSLife )
   {
	   mMaxRTSLife = mValueRTSLife;
   }

   if( mMaxRTSLife < 1 )
   {
	   mMaxRTSLife = 1;
   }
   setMaskBits( RTSBarMask );
}

void Player::SetValueRTSLife( S32 p_Value )
{
   mValueRTSLife = p_Value;
   setMaskBits( RTSBarMask );
}

void Player::ShowRTSBar( bool p_Value )
{
   mShowRTSBar = p_Value;
   setMaskBits( RTSBarMask );
}

S32 Player::GetMaxRTSLife()
{
   return mMaxRTSLife;
}

S32 Player::GetValueRTSLife()
{
   return mValueRTSLife;
}

bool Player::IsRTSBarShown()
{
   return mShowRTSBar;
}

In my player.h:
//+ AOV 070726
RTSBarMask = Parent::NextFreeMask << 4,
//- AOV 070726
NextFreeMask = Parent::NextFreeMask << 5

The weird thing is, the Con::errorf statement as shown in my previous post, generates wrong value for
( shape->GetValueRTSLife() * 1.0 / shape->GetMaxRTSLife() )

ex:
This shape 3590 is 2/2. Fraction is 0

it shouldn't be 0 right and note that 2/2 is also retrieved using GetMaxRTSLife() and GetValueRTSLife()
#10
07/29/2007 (6:52 pm)
As how Homer Simpson would say it: "Doooh!"

I just reviewed my codes as compared to the SimpleNetObject documentation.
It seems I was a bit stubborn on my end and didn't follow the whole instructions as it was intended to be.

What I did:
1. Instead of adding a single mask for all kinds of updates, I made a new mask for each.
2. I carefully compared the code with the tutorial, and strictly followed which of which should be accessed via member variable ( ie. object->mMemberVariable ) and method ( ie. object->MyMethod() ).
3. Added a printf on the unpackUpdate ( it seems to be needing this? ).


Now it works.