Game Development Community

dev|Pro Game Development Curriculum

guiTeleportCtrl

by Stefan Beffy Moises · 07/11/2002 (10:28 am) · 27 comments

Download Code File

I've written a new C++ class (guiTeleportCtrl) based on Frank Bignone's MapGui ...
This control now also shows the interiors, other players/bots, and of course the "TeleportTrigger"s (name comparison!), which are red on this pic -
the little black outlined rectangles are the interiors, the two white
dots are bots running around and the little white arrow above "Target 3" is the
player... you can turn interior and enemy rendering on/off, and you can adjust
all the colors (triggers, text, interiors, background, ...) via script... and
the rest of the GUI is completely up to you... I just threw some bitmaps
together in this 1st version... :-P

The GUI pops up as soon as you enter a trigger, but the teleporting only
starts if you click on one of the "Target" text fields on the right...


You'll find all the necessary files (including the test bitmaps) in this zip file (or by downloading the resource zip above, of course).

Here is a short description of what's going on and how to set it up...

First of all, lets look at the C++ stuff, which is based on Frank B.'s cool MapGui (or, to be exactly,
the extended version I've already made, which is also rendering the interiors and the other players/bots)...
The function responsible for rendering the trigger objects is this one:

// now render TeleportTriggers:
F32 triggerScaleFactorSave = mTriggerScaleFactor;
SimSet * missionGroup = dynamic_cast<SimSet*>(Sim::findObject("MissionGroup"));
for(SimSetIterator itr(missionGroup); *itr; ++itr)
{
	if ((*itr)->getType() & TriggerObjectType) {
		Trigger* trigger = static_cast<Trigger*>(*itr);

		// if it's a "TeleportTrigger"...
		if (trigger->getType() & TriggerObjectType 
			&& (dStrstr(trigger->getName(), "Teleport") != NULL)) {

			// Get center of object
			newCoord = trigger->getBoxCenter();
			shapeBox = trigger->getWorldBox();

			// Convert map coordinates to screen coordinates
			newCoord.x -= camCoord.x;
			newCoord.y -= camCoord.y;
			newCoord.y = -newCoord.y;
			float coord_z = newCoord.z - camCoord.z;
			newCoord.z = 0;
			
			// scale the position to the terrain size representation
			newCoord.x *= invSquareSize;
			newCoord.y *= -invSquareSize;

			// Adjust object's vector
			objectAngle = Vector3dToDegree(newCoord);
			length = newCoord.len();
			DegreeToVector2d( ( 360 - objectAngle ), length, newCoord);
			newCoord.x = -newCoord.x;
			newCoord.y = -newCoord.y;
			newCoord.x += center.x;
			newCoord.y += center.y;
			
			// take care of the "player-bitmap" size
			newCoord.x += texSize.x/2;
			newCoord.y += texSize.y/2;
					
			// check for a minimum size - every trigger below this will become scaled
			if(static_cast<U32>(shapeBox.len_x()) < mMinTriggerSize 
			|| static_cast<U32>(shapeBox.len_y()) < mMinTriggerSize)
			{
				// scale it
				mTriggerScaleFactor = 1/mMinTriggerScaleFactor;
			}
			else
			{
				// leave unscaled - if not set otherwise through mScaleFactor 
				// (saved in triggerScaleFactorSave)
				mTriggerScaleFactor = triggerScaleFactorSave;
			}
			// start point is the object's center minus half the size
			// (scaled according to the terrain block size,
			// with additional scale factor to change the relative size of the triggers to the map)
			Point2I boxStart(newCoord.x-((shapeBox.len_x()/(2*terrSquareSize))/mTriggerScaleFactor),
				newCoord.y-((shapeBox.len_y()/(2*terrSquareSize))/mTriggerScaleFactor));
			// end point is the object's center plus half the size
			// (scaled according to the terrain block size,
			// with additional scale factor to change the relative size of the triggers to the map)
			Point2I boxEnd(newCoord.x+((shapeBox.len_x()/(2*terrSquareSize))/mTriggerScaleFactor),
				newCoord.y+((shapeBox.len_y()/(2*terrSquareSize))/mTriggerScaleFactor));

			// now draw the rectangle
			dglDrawRectFill(boxStart, boxEnd, mTriggerColor);

			// now draw the text with a little offset 
			// from the trigger rectangles and its own color ...
			// change the name from "TeleportTriggerX" to "Target X" ...
			char buf[256];
			const char* name = trigger->getName();
			const char* replace = dStrstr(name,"TeleportTrigger");
			if ( !replace )
				continue;
			char newName[256];
			dStrncpy( newName, name, replace - name );
			newName[replace - name] = 0;
			dStrcat( newName, "Target ");
			dStrcat( newName, replace + 15 );
			dSprintf(buf,sizeof(buf), "%s", newName);
			dglSetBitmapModulation(mTriggerTextColor);
			dglDrawText(mProfile->mFont, 
			Point2I(boxStart.x+1, boxStart.y+1), buf, mProfile->mFontColors);
		}
	
	}
}

PLEASE NOTE:
to get this class to compile, you'll have to add this function to "dgl/dgl.cc" (and declare it in dgl.h) - thanks to Sabrecyd for reminding me... ;-) :
void dglDrawBitmapRotated(TextureObject *texture,const RectI& dstRect,
const RectI& srcRect,const U32 in_flip,F32 spinAngle)
{
   AssertFatal(texture != NULL, "GSurface::drawBitmapStretchSR: NULL Handle");
   if(!dstRect.isValidRect())
      return;
   AssertFatal(srcRect.isValidRect() == true,
               "GSurface::drawBitmapRotated: routines assume normal rects");

   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, texture->texGLName);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

   glDisable(GL_LIGHTING);

   F32 texLeft   = F32(srcRect.point.x)                    / F32(texture->texWidth);
   F32 texRight  = F32(srcRect.point.x + srcRect.extent.x) / F32(texture->texWidth);
   F32 texTop    = F32(srcRect.point.y)                    / F32(texture->texHeight);
   F32 texBottom = F32(srcRect.point.y + srcRect.extent.y) / F32(texture->texHeight);

   F32 screenLeft   = dstRect.point.x;
   F32 screenRight  = dstRect.point.x + dstRect.extent.x;
   F32 screenTop    = dstRect.point.y;
   F32 screenBottom = dstRect.point.y + dstRect.extent.y;

   if(in_flip & GFlip_X)
   {
      F32 temp = texLeft;
      texLeft = texRight;
      texRight = temp;
   }
   if(in_flip & GFlip_Y)
   {
      F32 temp = texTop;
      texTop = texBottom;
      texBottom = temp;
   }
   
   glColor4ub(sg_bitmapModulation.red,
             sg_bitmapModulation.green,
             sg_bitmapModulation.blue,
             sg_bitmapModulation.alpha);
  
   F32 X = 0;

   // calculate the centroid of the rectangle (to use as rotation pivot)
   if (screenLeft < screenRight)
   {
        X = screenLeft + ((screenRight - screenLeft)*0.5f);
   }
   else
   {
        X = screenRight + ((screenLeft - screenRight)*0.5f);
   };

   F32 Y = 0;

   if (screenTop < screenBottom)
   {
        Y = screenTop + ((screenBottom - screenTop)*0.5f);
   }
   else
   {
        Y = screenBottom + ((screenTop - screenBottom)*0.5f);
   };
   
   // spin angle is in degree's so convert to radians..radians = degrees * pi /180
   spinAngle = spinAngle * 3.14 / 180.0f;

   Point2F screenPoint(X,Y); 

   F32 width = dstRect.extent.x;
   width *= 0.5;

   MatrixF rotMatrix( EulerF( 0.0, 0.0, spinAngle ) );

   Point3F offset( screenPoint.x, screenPoint.y, 0.0 );
   Point3F points[4];
   
   points[0] = Point3F(-width, -width, 0.0);
   points[1] = Point3F(-width,  width, 0.0);
   points[2] = Point3F( width,  width, 0.0);
   points[3] = Point3F( width, -width, 0.0);

   for( int j=0; j<4; j++ )
   {
      rotMatrix.mulP( points[j] );
      points[j] += offset;
   }

   glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(texLeft,texTop);
      glVertex2fv(points[0]);

      glTexCoord2f(texLeft, texBottom);
      glVertex2fv(points[1]);

      glTexCoord2f(texRight, texBottom);
      glVertex2fv(points[2]);

      glTexCoord2f(texRight, texTop);
      glVertex2fv(points[3]);
   glEnd();
   glDisable(GL_BLEND);
   glDisable(GL_TEXTURE_2D);
}
What's also necessary is this little addon: (thanks again to Sabrecyd...)
in engine/interior/interiorInstance.cc, add...
const char* InteriorInstance::getInteriorFileName(){
	return mInteriorFileName;
}
and in the header, simply add
const char* getInteriorFileName();
at the end of the "public" section...

What's important here is that it only searches for triggers with "Teleport" in their name,
so once again make sure you set up the names of your objects correctly (in the editor or in script)!
The last piece of code simply replaces the trigger name with "Target X"...

There are some new vars now compared to the MapGui, e.g. scale factors for the triggers, a minimum size of the triggers
"mMinTriggerSize" (if below, they are scaled by this "mMinTriggerScaleFactor" - you wouldn't really see them otherwise in the little MapGui...)
You should only have to change the various color values and maybe the "mMinTriggerScaleFactor", but the default
values should work pretty well... all of them can be accessed via script / the GUI editor, so you can just play with them
and see what happens...

Now lets look at the new trigger, I've made a new file "fps/server/targetTeleportTrigger.cs" which I execute in
"fps/server/game.cs", the important things are:

// new sound file played when entering the trigger now
datablock AudioProfile(TeleportInitSound)
{
   fileName = "~/data/sound/fx/zzap.wav";
   description = AudioClose3d;
	preload = true;
};
...

function TargetTeleportTrigger::onEnterTrigger(%data, %obj, %colObj)
{
   %client = %colObj.client;
   if(!%client)
   {
      echo("not a client!");
      return;
   }
   %checkname = %obj.getName();
   if($numTeleports == 0)
   {
      // search for Triggers by their name once, so every trigger
      // which has "TeleportTrigger" in its name is counted
      $numTeleports = getMultiTriggerCount("TeleportTrigger");
      echo("$numTeleports:" SPC $numTeleports);
      // set the text fields in the TeleportGui
      TeleportGui.initTextControls($numTeleports);
   }
   if(%checkname !$= $currMultiTeleTrigger)
   {
      // show and initialize TeleportGui
      $teleInitSound = serverPlay3D(TeleportInitSound,%client.player.getTransform());
      Canvas.pushDialog(TeleportGui);
      TeleportGui.initializeBeam(%client, %checkname);
   }
}
Look at
TeleportGui.initTextControls($numTeleports);
      ...
      Canvas.pushDialog(TeleportGui);
      ...
      TeleportGui.initializeBeam(%client, %checkname);

These are popping up the gui and calling functions in "fps/client/ui/TeleportGui.cs", which you can see here:

// set the text fields the first time a trigger is entered:
function TeleportGui::initTextControls(%this, %num)
{
   for(%i = 1; %i <= %num; %i++)
   {
      %actText = "TeleportLocation" @ %i;
      %actText.setText("Target" SPC %i);
   }
}
function TeleportGui::initializeBeam(%this, %callingClient, %triggerName)
{
   // save references to client and current trigger
   $teleportClient = %callingClient;
   $teleportCheckname = %triggerName;
}

They are simply setting the text fields in the gui and saving the current client and the current trigger name in global vars
for later use.


Now in the gui there are 6 of these textfield (you can add as many as you want) which look like this
(file "fps/client/ui/TeleportGui.gui"):

new GuiTextCtrl(TeleportLocation1) {
      profile = "GuiBoldGrayTextProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "253 168";
      extent = "38 16";
      minExtent = "8 8";
      visible = "1";
      helpTag = "0";
      maxLength = "255";
   };

and for each of them, there are mouse event handlers in "TeleportGui.cs":
// mouse handlers for the target text fields to make them clickable
// if you need more triggers / teleports, add more text fields to TeleportGui.gui
// *and* here!

function TeleportLocation1::onMouseDown(%this, %obj) {
   TargetTeleportTrigger.startAction($teleportClient, $teleportCheckname, "1");
}
function TeleportLocation2::onMouseDown(%this, %obj) {
   TargetTeleportTrigger.startAction($teleportClient, $teleportCheckname, "2");
}
...

NOTE:
The GuiTextCtrl doesn't have mouse event handlers by default, so you have to change this.
Simply open up "engine/gui/guiTextCtrl.h" and add the following:
(see this thread
for details)
#ifndef _GUIMOUSEEVENTCTRL_H_
#include "gui/guiMouseEventCtrl.h"
#endif
and then change
class GuiTextCtrl : public GuiControl
to
class GuiTextCtrl : public GuiMouseEventCtrl
and also change
typedef GuiControl Parent;
to
typedef GuiMouseEventCtrl Parent;

So if you click on one of the text entries in the gui, "startAction()" in targetTeleportTrigger.cs is called,
which checks the trigger name and if it is *not* the current trigger it starts the teleporting schedule, plays the sound, etc. ...

function TargetTeleportTrigger::startAction(%this, %callingClient, %checkname, %targetNum)
{
   %target = "TeleportTrigger" @ %targetNum;
   // we don't want to beam to the current teleport, do we?
   if(%target !$= %checkname)
   {
      CommandToClient(%callingClient,'bottomprint',"Teleporter initializing... good luck!!",2,10);
      $teleSched = schedule(2000,0,"goScotty",%callingClient,%target);
     	$teleSound = serverPlay3D(TargetTeleportBuzz,%callingClient.player.getTransform());
      %callingClient.player.setCloaked(true);
      // save the target - until the teleported client leaves it, then reset
      $currMultiTeleTrigger = %target;
   }
}

All the other stuff is pretty much the same as in the multiTeleportTrigger ...

This time, you have to change your trigger datablock in the mission file (if you've already used one of the other TeleportTriggers, if not,
please see the "Teleport script" tutorial for further explanations) from
dataBlock = "TeleportTrigger";
or
dataBlock = "MultiTeleportTrigger";
to
dataBlock = "TargetTeleportTrigger";


You could also use this for some transportation system, I guess we will end up using it as a subway system
for our game... if the player enters a subway station, he can pop up the "subway schedule gui" or something like that
and choose his target location - and if he enters the subway then, he gets "transported" to his destination.... :-)


To "install" it,
1) copy the .cc and .h files into "engine/gui", add them to your VC++ project and compile (if you encounter problems, do a CLEAN build)
2) copy all the bitmaps into "fps/client/ui"
3) copy "TeleportGui.gui" and "TeleportGui.cs" into "fps/client/ui"
4) copy "zzap.wav" into "fps/data/sound/fx"
5) copy "targetTeleportTrigger.cs" into "fps/server/scripts"
6) exec the scripts in "game.cs" and "init.cs" respectively
7) set up your triggers (or change the triggers' datablocks if you already have them in your mission),
particle effects and shapes in the mission editor (or edit your *mis file accordingly)... see the previous
tutorial for detailed instructions!
8) NOTE: if you're missing some of the font profiles the GUI is using, you could either copy them from
the "defaultProfiles.cs" provided with the zip or you can use different profiles for your GUI, of course...



Well, that's pretty much it, I'm sure you've got plenty of own ideas how to use it - hope you like it! :-))

You can also download / view this tutorial as PDF
Page «Previous 1 2
#1
07/12/2002 (3:49 am)
The download link don't works!!! (targetteleport.zip)
Thankx,
Martin E.
#2
07/12/2002 (4:02 am)
Mhm, true... GG, could someone please fix this?
Martin, you can use this one instead:
guiTeleportCtrl
or if there are problems with login or something, use this:
guiTeleportCtrl.
#3
07/12/2002 (5:08 am)
Hey Stefan,
I still have some problems..uh..with the code I mean ;)

guiTeleportCtrl.cc
C:\torque-HEAD-7-09-02-SR-7-09-02\engine\gui\guiTeleportCtrl.cc(516) : error C2039: 'getInteriorFileName' : is not a member of 'InteriorInstance'
        ../engine\interior/interiorInstance.h(47) : see declaration of 'InteriorInstance'
C:\torque-HEAD-7-09-02-SR-7-09-02\engine\gui\guiTeleportCtrl.cc(516) : error C2039: 'getInteriorFileName' : is not a member of 'InteriorInstance'
        ../engine\interior/interiorInstance.h(47) : see declaration of 'InteriorInstance'

This is line 516 in guiTeleportCrtl.cc
if (shape->getType() & InteriorObjectType && ((dStrstr(shape->getInteriorFileName(), "cstrt") == NULL) && (dStrstr(shape->getInteriorFileName(), "street") == NULL))) {
if I change it to this:
if (shape->getType() & InteriorObjectType){
It will compile and I can get everything to work (radar images, pop-up gui, sfx, etc) but the mouse select for Targets 1, 2, etc can't be clicked. In other words, I can't activate the teleport.

I also noticed that bots will cause the gui to pop up for the player. I probably messed it up by removing that section of code. Where is the "getInteriorFileName" defined? I didn't see a reference to it in the interiorInstance files.

Oh yeah, I did try this on TGE 1.1.1 and the HEAD. I also tried adding the guiMapCtrlExt files and got similar errors about the "getInteriorFileName". The dgl function seems fine now, but I must be missing something else too :(

Hope that wasn't too long and annoying to read,
-Sabrecyd
#4
07/12/2002 (5:56 am)
Jeez! Sorry again, I don't know where it comes from, but it is in my code... guess I've added it somewhen, somehow... ;-)
So here it goes... (doesn't do very much, though...)
in engine/interior/interiorInstance.cc, add...
const char* InteriorInstance::getInteriorFileName(){
	return mInteriorFileName;
}
and in the header, simply add
const char* getInteriorFileName();
at the end of the "public" section...
But it doesn't have to do anything with the mousehandlers, so I don't understand that problem at the moment... do you have a script error in the console? Is a font definition missing or something?
As for the bots: yeah, that's what I was expecting... I couldn't test it in Multiplayer yet (only the "normal", random teleports, which work fine in Multiplayer - though I didn't explicitly use bots for the test... should check that, too...)
For now, you could just prevent the bots from triggering the teleports by checking if the triggering client is AI controlled or not...
simply change all occurences of
if(!%client)
   {
      echo("not a client!");
      return;
   }
in "targetTeleportTrigger.cs" to
if(!%client || %client.isAIControlled())
{
      echo("not a client!");
      return;
}
Let me know if this helps...
#5
07/12/2002 (6:19 am)
Thanks for the quick update.
Yeah, I don't know where that small extra code came from or why I seem to be missing it. Maybe it was added for the guiMapCtrlExt stuff? Anyway, I'll add it in :)

I didn't see any script errors. It could have to do with the font definitions. I did try dropping in your defaultProfiles.cs file to see if that had anything to do with it, but didn't see a difference. I'll check this out again over the weekend. The gui/radar looks very cool.

zzap!
#6
07/15/2002 (3:12 am)
Sabrecyd, did you get it to run now with these additions? Please let me know if there are any more problems...
#7
07/15/2002 (4:58 am)
Hi Stefan,
I don't have internet at home at the moment. Disconnected my Dial-up and will be switching to a cable modem :) So I just check this site at work during the week (when I can). Besides we have a T1 line so now I'm spoiled. Just can't deal with dial-up anymore. I was getting 26k at best.

All the compile errors are fixed, but I'm still stuck with the mouse/text select problem :/ There aren't any console errors appearing. For some reason I can't select the targets. I have 4 teleports right now and they all show up correctly in the gui select section and in the radar part. They also work fine as a multiteleport, but as the gui-select...nada. Did you maybe add one of those resources that involves the mouse-over selection? Should the target teleport text change colors or anything when the mouse points to it? I'm not really sure what my problem is at the moment :(

-Sabrecyd
#8
07/15/2002 (5:15 am)
Sabrecyd!
Yes, you caught me again... shame on me...
I forgot that the text controls don't have mouse events by default (GG really should add this to the CVS, btw.!!), so you have to add this, which is indeed very simple to do: simply follow this tutorial and you should be all set ... finally! Sorry for these inconveniences :-/
and thanks a lot for checking this thing out!! :-)
EDIT: I've updated the above tutorial and the one on our website (PDF version, too) to reflect these changes.
#9
07/15/2002 (7:04 am)
Very cool. I'll give this a shot tonight. I think that should take care of the last "glitch" for me. You're right the mouse event stuff should be added to the CVS. It seems like it would be useful for so many things.

Might have to take a look at that spell thing now too :)

thanks Stefan,
-Sabrecyd
#10
07/16/2002 (5:02 am)
Guess what! Works perfect now :) I can really see uses for all 3 types that you did. Great resource.
#11
07/16/2002 (5:08 am)
Puh! That's great to hear!! :-)
Next time I will put it into a fresh HEAD version first before I submit anything as a resource... :-P
It's getting harder and harder to tell what was in the engine and what I've added sometime later on...
But now - have fun teleporting!! ;-)
#12
07/16/2002 (5:25 am)
That's for sure. I started keeping some notes on which files I've added to, but it is very hard to keep track of all the changes. Besides going back to the HEAD or stable release for testing is very boring ;)
#13
01/08/2003 (8:46 am)
Stefan, Is this tutorial still availible somewhere ?
#14
01/08/2003 (10:38 am)
Oops, lots of dead links here :(
But yeah, you can find it on our new site at: tork.zenkel.com
#15
01/08/2003 (6:33 pm)
Oh cool ! There it is :) Thanks much Stefan
#16
01/08/2003 (7:23 pm)
Installed and working perfectly. Really cool ! Again Thanks so much !
#17
01/13/2003 (12:28 am)
Stephan, I like it much :) I'm having a little problem in multiplayer. It's working great for the server but when the client enter the trigger, the targetTeleport popup on the server machine. And the server can teleport the client where he wants :) I supsect it is something with my team implementation but I'd like to know if it's working for you. You seems to be really great for the multiplayer parts (seeing your inventory system that is all working in multi, both sides) so that why Im asking :) Thanks
#18
01/13/2003 (7:24 am)
Gilles: bummer, I think this version really didn't work in Multiplayer... :/
but: it works now... :)
I'll try to figure our what I've changed since then and repost the files and / or list the changes here... just give me a few days... :P
#19
01/13/2003 (8:37 am)
OMG I'd really appreciate it Stephan. Thanks a lot ! Really nice and fun ressource
#20
03/06/2003 (8:42 am)
Link seems broken too here =(
(http://www.garagegames.com/uploaded/code/2950.targetteleport.zip)
Page «Previous 1 2