Game Development Community

dev|Pro Game Development Curriculum

TorqueBack: Gui Controls Part One.

by Phil Carlisle · 01/29/2002 (9:54 am) · 5 comments

Download Code File

Zoombapups Torque-back

Gui Controls Part 1: A simple Altimeter control.

Why an altimeter control?

I was working on some flying vehicles, playing with some flight models etc and decided that I'd try and do a heads up display (HUD) like you see in helicopter flight simulators and such. So here is a very simple version of a control that displays your height (altitude) in a fashion similar to a flight sim.

Gui controls - the basics.

Gui controls are just a class of object that inherit from a single class (guicontrol). They are used to display anything on the canvas (you can think of that as the screen, and the guicontrol as something drawing to the screen).

Typically, gui controls have a few variables, read some data from the game and draw something to the screen (canvas). Our new altimeter is no different.

Typically, you can create gui controls by just creating a .cc file, if the control is very simple its easier to not create a seperate .h and .cc file because the class definition is simple enough to not be required in a seperate file (its perfectly legal to declare a class in a .cc file as long as you dont need to include it somewhere else.

Lets create our guiAltimeterHud.cc file now. Create a new .cc file and add it to the project (I'll assume you know how this works for your build process).

Lets explain the key points of the code. Firstly, there is the class definition, this is much like any other class definition. Note that we are deriving from guicontrol.
class GuiAltimeterHud : public GuiControl
{
   typedef GuiControl Parent;

   bool		mDigital;
   bool     mShowFrame;
   bool     mShowFill;
   		
	ColorF   mFrameColor;
	ColorF   mTextColor;
	ColorF   mFillColor;

   F32		mLowerBounds;
   F32		mUpperBounds;
    		
public:
   GuiAltimeterHud();

   void setLowerBounds(F32 flLower);
   void setUpperBounds(F32 flUpperBounds);

   void onRender( Point2I, const RectI &);
   static void initPersistFields();
   DECLARE_CONOBJECT( GuiAltimeterHud );
};
Ok, so its fairly standard as C++ goes, we have a few variables declared there, so we can alter a little of the control's functionality, such as an upper and lower bounds value *flight floor and ceiling. Also, you will note that we have a couple of functions, to set the upper and lower bounds. As you will see shortly, this is how you create functions that can be used by the script or console to set values.

The other main thing to note here, is the DECLARE_CONOBJECT macro call. What this does is register the type of this control with the console, its important because this is what allows us to use this control in the engine (and in the editors).

The next few lines call the IMPLEMENT_CONOBJECT macro to finish the definition of this class with the system (everything is derived from a console object). We then do standard C++ constructor initialization.

So the meat of this code is all in one place (as it will be with most simple gui controls) and that’s in the onRender method.
//---------------------------------------------------------------------

IMPLEMENT_CONOBJECT( GuiAltimeterHud );

GuiAltimeterHud::GuiAltimeterHud()
{
   mShowFrame = true;
   mFrameColor.set(0, 1, 0, 1);
   mTextColor.set( 0, 1, 0, 1 );
   mFillColor.set(0, 0, 0, 0.5);
   mLowerBounds = -100.0f;
   mUpperBounds = 500.0f;	
   mShowFill = false;
   mDigital = false;
   
}

void GuiAltimeterHud::initPersistFields()
{
   Parent::initPersistFields();

   addField( "Digital", TypeBool, Offset( mDigital, GuiAltimeterHud ) );
   addField( "showFill", TypeBool, Offset( mShowFill, GuiAltimeterHud ) );
   addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiAltimeterHud ) );
   addField( "fillColor", TypeColorF, Offset( mFillColor, GuiAltimeterHud ) );
   addField( "textColor", TypeColorF, Offset( mTextColor, GuiAltimeterHud ) );
   addField( "lowerbounds", TypeF32, Offset(mLowerBounds,GuiAltimeterHud) );
}
As you can see, the onRender method is the meat of this code, its fairly well commented, so I'll let the comments speak for themselves here.

//-----------------------------------------------------------------------------

void GuiAltimeterHud::onRender(Point2I offset, const RectI &updateRect)
{

	// Must have a connection and player control object
   GameConnection* conn = GameConnection::getServerConnection();
   if (!conn)
      return;
   ShapeBase* control = conn->getControlObject();
   if (!control) return;

	// Target pos to test, if it's a player run the LOS to his eye
    // point, otherwise we'll grab the generic box center.
    Point3F shapePos;
	
    if (control->getType() & PlayerObjectType) 
	{
       MatrixF eye;
       control->getEyeTransform(&eye);
       eye.getColumn(3, &shapePos);
    }
    else
	{
       shapePos = control->getBoxCenter();
	};
	
	// ok, here we have our height in shapePos.z so lets see if we are to draw 	// as a "digital" display or the wireframe type.

	if (mDigital)
	{
	   // Background first
	   if (mShowFill)
		  dglDrawRectFill(updateRect, mFillColor);
   
	   // Convert ms time into hours, minutes and seconds.
	   S32 height = (S32) shapePos.z;

	   // Currently only displays min/sec
	   char buf[256];
	   dSprintf(buf,sizeof(buf), "Alt: %04d",height);

	   // Center the text
	   offset.x += (mBounds.extent.x - mProfile->mFont->getStrWidth(buf)) / 2;
	   offset.y += (mBounds.extent.y - mProfile->mFont->getHeight()) / 2;
	   dglSetBitmapModulation(mTextColor);
	   dglDrawText(mProfile->mFont, offset, buf);
	   dglClearBitmapModulation();

	   // Border last
	   if (mShowFrame)
		  dglDrawRect(updateRect, mFrameColor);

		return;
	};

	// to get here, we must need to be drawn as the wireframe control.


    F32 flYStep = (mUpperBounds - mLowerBounds) / 10.0f;
    F32 flYScreenStep = ((F32) updateRect.extent.y) / 10.0f;

	F32 left = (F32) updateRect.point.x;
	F32 top  = (F32) updateRect.point.y;
	F32 right = (F32) updateRect.point.x + (updateRect.extent.x / 2) - 1;
	F32 bottom = (F32) updateRect.point.y + updateRect.extent.y - 1;


	// top line
	dglDrawLine(left,top,right,top,mFrameColor);
	// down line
	dglDrawLine(right,top,right,bottom,mFrameColor);
	// bottom line
	dglDrawLine(left,bottom,right,bottom,mFrameColor);
	// draw the "tick" marks.. at intervals down the line.
	for (F32 i = 1; i < 10; i++)
	{
		dglDrawLine(left + ((right-left) / 2),top + (flYScreenStep * i),right,top + (flYScreenStep * i),mFrameColor);
	};	

	F32 flHeight = shapePos.z;
	if (flHeight > mUpperBounds) flHeight = mUpperBounds;
	if (flHeight < mLowerBounds) flHeight = mLowerBounds;

	F32 flYOffset = bottom - (bottom - top) * (flHeight / (mUpperBounds - mLowerBounds));
	

	// height mark top angle.
	dglDrawLine(right,flYOffset,right + 10.0f,flYOffset + 10.0f,mFrameColor);
	// bottom angle
	dglDrawLine(right,flYOffset,right + 10.0f,flYOffset - 10.0f,mFrameColor);
	// height.
	dglDrawLine(right + 10.0f,flYOffset + 10.0f,right + 10.0f,flYOffset - 10.0f,mFrameColor);

}
As you can see, all the render method really does, is get the z value of the object (its height) and plot that on a bar as a percentage of the bar. This could easily be done as a vertical progress control if you required, but I thought the bare line look was quite funky. The only other thing is that i put an mDitigal bool in to be able to toggle between the wireframe look and a boring old text box display.

Finally, this is where we define the functions and setup the console methods so that we can set our upper and lower bounds from script/console.
//-----------------------------------------------------------------------------

void GuiAltimeterHud::setLowerBounds(F32 lower)
{
	// lower bounds of flight
	mLowerBounds = lower;
}

void GuiAltimeterHud::setUpperBounds(F32 upper)
{
	mUpperBounds = upper;
}

ConsoleMethod(GuiAltimeterHud,setLowerBounds,void,3, 3,"(flight floor in meters)Sets the current lower flight floor for the Altimeter")
{
   GuiAltimeterHud *hud = static_cast<GuiAltimeterHud*>(object);
   hud->setLowerBounds(dAtof(argv[2]));
}

ConsoleMethod(GuiAltimeterHud,getTime,void,3, 3,"(flight ceiling in meters)Sets the current flight ceiling.")
{
   GuiAltimeterHud *hud = static_cast<GuiAltimeterHud*>(object);
   hud->setUpperBounds(dAtof(argv[2]));
}
And thats it! thats all there is to a gui control. Or at least a very simple one.

Take the code included in the zip, include it in your project and you should get a new control type in the gui editor. It may also be useful to setup a gui "profile" in defaultProfiles.cs (take a look in that file and you will see that its merely a bunch of pre-defined data for choosing attributes for gui controls, such as colour etc.

Anyway, I hope you found this useful. There should be more tutorials coming along soon.

If there's something you find a bit puzzling, please try and understand the code by looking at other controls first, then try the forums on garagegames.com, finally, if all else fails, get in touch :))

Zoom. - Aka Phil.

#1
01/30/2002 (1:16 am)
Excellent,
very usefull and implemented without a hitch..
great job on the tutorial
#2
01/30/2002 (1:50 pm)
Thanks Badguy.. more on the way. You beat me to a vehicle tut too.

Phil.
#3
07/12/2002 (12:22 am)
hi phil,

im writing a gui builder guide, and id like to include your tutorial and any other help you might want to provide...

Michael Heining
MadWizard@qnet.com
#4
02/21/2004 (10:40 pm)
Just wondering what it draws when Digital is set to true?
All I see is a green box that is nothing like what I have expected.
--James Yong
#5
03/23/2004 (1:43 pm)
It worked! The first attempt at modifying code.. WOW! ps, not to others who follow: I put this under "gui" in torquedemo, after trying it in torque LIB - that one was no go ;)

James: I believe when set to Digital you should see just a box, with "Alt: 0234" with the height. That's what I get

I have an odd question tho. Is the altimeter "visible" floating over the chr's face. I dunno why I wondered about that.. I guess because of how it's drawn?