Game Development Community

dev|Pro Game Development Curriculum

Checkpoint Arrow pointer for the racing starter kit.

by Peterjohn Griffiths · 11/13/2006 (2:58 pm) · 5 comments

Checkpoint Arrow pointer.

This has been tested in Torque 1.4 head release.

This resource uses the "FuelOMeter, SpeedOMeter like control" resource as the arrow pointer as it can be used as a univeral control. The "FuelOMeter, SpeedOMeter like control" resource needs to be setup and running before you implement the below code. www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=2676

I would like to start by thanking Frank Bignone for updating the guiMeterCtrl in the above resource and for helping with questions I had relating to dot product math.

AGAIN make sure you have got the "FuelOMeter, SpeedOMeter like control" resource implemented and working before you continue. www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=2676

C++ code
Next, open the file guiMeterCtrl.h and find the following code.
GuiMeterValueTypeEnergy,
and add the following on the next line.
GuiMeterValueTypeCheckPointArrow,


Then in the file guiMeterCtrl.cc, find the following code.
{ GuiMeterCtrl::GuiMeterValueTypeEnergy, "Energy" },
and add the following on the next line.
{ GuiMeterCtrl::GuiMeterValueTypeCheckPointArrow, "CheckPointArrow" },


Find the function
void GuiMeterCtrl::onRender(Point2I offset, const RectI &updateRect)
and replace it with
void GuiMeterCtrl::onRender(Point2I offset, const RectI &updateRect) 
{
	if  ( ( mValueType == GuiMeterValueTypeSpeed ) ^ ( mValueType == GuiMeterValueTypeEnergy ) )
	{
		// Must have a connection and player control object
		GameConnection* conn = GameConnection::getConnectionToServer();
		if (!conn)
			return;
		Vehicle* control = dynamic_cast<Vehicle*>(conn->getControlObject());
		if (!control || !(control->getType() & VehicleObjectType))
			return;
		

		if ( mValueType == GuiMeterValueTypeEnergy )
		{
			mValue = control->getEnergyLevel();//getEnergyValue();
		}

		if ( mValueType == GuiMeterValueTypeSpeed )
		{
			mValue = control->getVelocity().len();
		}

		if (mValue > mMaxValue)
		{
			mValue = mMaxValue;
		}
	}

	if ( mValueType == GuiMeterValueTypeCheckPointArrow )
	{
		// Must have a connection and player control object
		GameConnection* conn = GameConnection::getConnectionToServer();
		if (!conn)
			return;
		Vehicle* control = dynamic_cast<Vehicle*>(conn->getControlObject());
		if (!control || !(control->getType() & VehicleObjectType))
			return;
		//Position of next check point.
		//Forced to use script as can't figure out how to get stuff to work in c++.
		Con::executef(2, "UpdateCheckPointArrow", control->getIdString() );
	}
	if ( !mInvert )
	{
		mAngle = mMinAngle + (mMaxAngle - mMinAngle) * (mValue / mMaxValue);
	}
	else
	{
		mAngle = mMaxAngle - (mMaxAngle - mMinAngle) * (mValue / mMaxValue);
	}

	// Center of widget
	Point2F half(mBounds.extent.x / 2  - 1,mBounds.extent.y / 2 - 1); 

	// Make center the UI object's coordinate center 
	half.x += offset.x; 
	half.y += offset.y; 

	// Take back the tecture handle for spin and frame
	TextureObject* spin = (TextureObject *) mMeterSpin;	
	TextureObject* frame = (TextureObject *) mTextureHandle;

	// Draw frame
	if(frame) {
		dglSetBitmapModulation(mColor);
		Point2I texSize(frame->bitmapWidth, frame->bitmapHeight );
		Point2I corner(half.x-frame->bitmapWidth/2, half.y-frame->bitmapHeight/2); 
		RectI srcRegion(0,0,frame->bitmapWidth,frame->bitmapHeight); 
		RectI dstRect(corner, texSize);
	    dglDrawBitmapStretchSR(frame, dstRect, srcRegion, false);
	}

	// Draw Spin
	if(spin) {
		F32 ang = mAngle * (mSpinAng.y - mSpinAng.x) / 100.0 + mSpinAng.x;
		Point2I texSize(spin->bitmapWidth, spin->bitmapHeight );
		Point2I corner(half.x-spin->bitmapWidth/2 + mSpinOffset.x, half.y-spin->bitmapHeight/2 +mSpinOffset.y); 
		RectI srcRegion(0,0,spin->bitmapWidth,spin->bitmapHeight); 
		RectI dstRect(corner, texSize);
	    dglDrawBitmapRotated(spin, dstRect, srcRegion, false, ang, mSpinBitmapOffset);
	}
  renderChildControls(offset, updateRect); 
}
Recompile your exe and we are ready to put in the scripting.


Script code
Create a new file in your client\scripts folder called checkPointArrow.cs and add the following code to it.
function UpdateCheckPointArrow(%cl){
if (isobject(localclientconnection.car.CheckPoint)){
   %targetObj = localclientconnection.car.CheckPoint.position;
}
else {
   return;
}

NextCheckPointArrow.value = getAngleFromVectors(%targetObj, localclientconnection.car.getForwardVector(), localclientconnection.car.position); //%destPos, %fromFwd, %fromPos
}


function clientCmdSetCheckPoint(%tmpCheckPoint){
//Purpose: To set the local client variable CheckPoint for the checkpoint arrow to point to.
localclientconnection.car.CheckPoint = %tmpCheckPoint;
}


function getAngleFromVectors(%itemPos, %playerFwd, %playerPos){
	//Work out the forward vector between the two position vectors.
	%itemFwd = VectorSub(%playerPos, %itemPos);
	
	//Work out the angle the object is from the player fwd vector. This will always be an angle >= 0.
	%myAngle = mRadToDeg(mAcos(VectorDot(VectorNormalize(%itemFwd), VectorNormalize(%playerFwd))));
	//%myAngle is the oposite 180 of the angle we require. If %myAngle is 10 Degrees then its actualy 170 degrees.
	%myAngle = 180 - %myAngle;

	//Make the angle sloppy by 10 degree's, 5 either side of 0 to counter act the needle jumping when passing the object up close.
	%myAngle = %myAngle - 5;
	if (%myAngle < 0 ){
		%myAngle = 0;
	}
	
	//Work out the side the object is on from the players forward vector. + left or - Right.
	%crossProduct = VectorCross(VectorNormalize(%itemFwd), VectorNormalize(%playerFwd));
	%rotation = GetWord(%crossProduct, 2);
	if (%rotation < 0 ) {
		//Angle needs to be a negative angle of the same amount as its on the right.
		%myAngle = %myAngle - (%myAngle * 2);
	}
	
	//Return the angle.
	return %myAngle;
}


In file client\init.cs, find the following
// Client scripts
and add the following on the next line.
exec("./scripts/checkPointArrow.cs");


Replace the contents of file server\scripts\checkpoint.cs, with the following code.
datablock TriggerData(CheckPointTrigger)
{
   // The period is value is used to control how often the console
   // onTriggerTick callback is called while there are any objects
   // in the trigger.  The default value is 100 MS.
   tickPeriodMS = 100;
};

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

function CheckPointTrigger::onEnterTrigger(%this,%trigger,%obj)
{
	Parent::onEnterTrigger(%this,%trigger,%obj);
	if(%obj.client.CheckPoint == %trigger)
	{
		if(%trigger.isLast)
		{
			error ("Is last checkpoint");
			// Player has completed a lap.
			%obj.client.lap++;

			if(%obj.client.lap >= $Game::Laps)
			{
				// Increase his score by 1.
				%obj.client.incScore(1);
            	// End the game
             	cycleGame();			
			}
			else {
				%obj.client.nextCheck = 1;
				//Get handle of next check point.
				SetClientCheckPoint(%obj.client);
				commandToClient(%obj.client, 'IncreaseLapCounter');
			}
		}
		else {
			// Continue to the next checkpoint.
			%obj.client.nextCheck++;
			//Get handle of next check point.
			SetClientCheckPoint(%obj.client);
		}
	}
}

function GetHandleOfNextCheckPoint(%client){
	%groupName = "MissionGroup/CheckPoints";
	%group = nameToID(%groupName);
	if (%group == -1){
		error ("CHECKPOINT ARROW: Error in function GetHandleOfNextCheckPoint. No SimGroup (MissionGroup/CheckPoints) could be found with checkpoint objects within it.");
	}
	else {
		%groupCount = %group.getCount();
		for (%i=0; %i<%groupCount; %i++){
			%tmpObj = %group.getObject(%i);
			if (%tmpObj.CheckPoint==%client.nextCheck){
				return %tmpObj;
			}
		}
		//If object wasn't found then reset to default.
		Error ("CHECKPOINT ARROW: Checkpoint '"@%client.nextCheck@"' not found. Setting to default checkpoint 1.");
		for (%i=0; %i<%groupCount; %i++){
			%tmpObj = %group.getObject(%i);
			if (%tmpObj.CheckPoint==%client.nextCheck){
				return %tmpObj;
			}
		}
		error ("No checkpoint found!");
	}
}

function SetClientCheckPoint(%client)
{
%client.CheckPoint = GetHandleOfNextCheckPoint(%client);
//Inform the specific client so their Arrow Pointer to next Check Point updates.
commandToClient(%client, 'SetCheckPoint', %client.CheckPoint);
}


In file server\scripts\game.cs, find the following line.
%cl.nextCheck = 1;
and add the following on the next line.
SetClientCheckPoint(%cl);


Next we need to add the control to the PlayGui Control.
Open up file client\ui\playGui.gui and find the following code.
new GuiTextCtrl(LapCounter) {
      Profile = "GuiBigTextProfile";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "400 10";
      Extent = "124 40";
      MinExtent = "8 2";
      Visible = "1";
      text = "Laps: 0";
      maxLength = "255";
   };
and add the following on the next line.
new GuiMeterCtrl(NextCheckPointArrow) {
      Profile = "GuiDefaultProfile";
      HorizSizing = "center";
      VertSizing = "relative";
      position = "280 1";
      Extent = "80 80";
      MinExtent = "80 80";
      Visible = "1";
      bitmap = "~/data/shapes/fuel/panel.png";
      wrap = "0";
      spinBitmap = "~/data/shapes/fuel/spin.png";
      color = "1 1 1 1";
      value = "0";
      maxvalue = "180";
      maxangle = "180";
      minangle = "0";
      spinAngle = "0 100";
      spinBitmapOffset = "2 1";
      spinOffset = "0 0";
      valuetype = "CheckPointArrow";
      invert = "0";
      angle = "0";
   };

Last we need to create a SimGroup in our mission and place all the CheckPoints into it.
Open up the mission in the mission editor (F11), select Window/WorldEditorCreator (F4) and select the simgroup (MissionGroup) from the World Editor Tree (right hand top).
In the World Editor Creator Tree (right hand bottom) click on "Mission Objects\System\Simgroup".
In the "Building Object: SimGroup" window that appears enter "CheckPoints" and click [ok]. NOTE Be carefull of case this must be exact.
In the World Editor Tree (right hand top) select the "CheckPoints" sim group you have just created. (This should be at the bottom of the list).
Move all the Checkpoints into this Sim Group using drag and drop. Make sure you get them all in there.
Save the mission using the "File\Save Mission As" and then load it up and test out the new control to make sure it is working.

Thats it.

Bugs and future improvements:
The arrow always points to one corner of the checkpoint and this can be seen as you get close to the checkpoint.
I have read that Torque 1.5 has a getcenter() method and if this was used instead of .position it would cure the problem.
I don't currently have Torque 1.5 so maybe someone could test this out and post the result below.

Files
Here is a link to the image files I used for my NextCheckPointArrow meter incase you want them.
www.uklanparty.net/modules/Torque/downloads/arrowPointer.zip
Put the two files into a new folder "/data/shapes/arrowPointer"
new GuiMeterCtrl(NextCheckPointArrow) {
      Profile = "GuiDefaultProfile";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "712 9";
      Extent = "80 80";
      MinExtent = "80 80";
      Visible = "1";
      bitmap = "~/data/shapes/arrowPointer/panel.png";
      wrap = "0";
      spinBitmap = "~/data/shapes/arrowPointer/pointer.png";
      color = "1 1 1 1";
      value = "22.612";
      maxvalue = "180";
      maxangle = "180";
      minangle = "0";
      spinAngle = "0 100";
      spinBitmapOffset = "1 -1";
      spinOffset = "0 0";
      valuetype = "CheckPointArrow";
      invert = "0";
         angle = "38.8273";
   };


Pictures
www.uklanparty.net/uup/Pejayuk_Checkpoint_1.JPG.jpg
www.uklanparty.net/uup/Pejayuk_Checkpoint_2.JPG.jpg
www.uklanparty.net/uup/Pejayuk_Checkpoint_3.JPG.jpg
www.uklanparty.net/uup/Pejayuk_Checkpoint_4.JPG.jpg

#1
11/14/2006 (2:45 am)
Images are not working.
#2
11/14/2006 (2:00 pm)
My host was moving my website to a new cluster. Just tested and they are now working fine.
I am also currently upgrading my game to Torque 1.5 so will test this resource in 1.5 very soon.
#3
11/14/2006 (5:00 pm)
I was expecting something like Halo's objective marker -- From these pics, I can't tell what this is really supposed to do.... uh... The fuel gauge points to the next checkpoint?! Okay.....
#4
11/16/2006 (5:57 am)
Yes that right. All you need to do is create your own background for the fuel guage and possibly change the pointer image if you like. It always points to the next check point. Makes sure people know where they are heading. Works a charm.
If I get a chance I will try and get a background and pointer made up but i'm busy upgrading my game to Torque 1.5 at the moment.
#5
12/03/2006 (8:57 pm)
I have created a background for the control and a arrow pointer. Use then if you wish or create your own.
I have also updated the screenshots to show the control background and pointer.
Hope this helps.