Game Development Community

dev|Pro Game Development Curriculum

Commander Map

by Kyle Carter · 03/01/2004 (10:52 am) · 155 comments

Download Code File

To use this resource:

1. Copy guiCommanderHud.cc to your engine/gui subfolder.
2. Open your IDE of choice (or a text editor if that's what you like).
3. Add the source file to your project so it will be compiled into Torque.
4. Do a clean rebuild.
5. Go to the GUI of your choice and add a GuiCommanderHud to it. Poof, instant GUI!

Security and Scoping Issues:

The HUD is designed to prevent people from abusing it in-game. It does not change how anything is scoped, so people will only see what the net code would have previously allowed them to see. If you want to have a special "commander map" behavior that shows all active objects you will need to add it either via server side scripting or in GameConnection::doneScopingScene(). Remember, the best way to prevent cheating and improve net performance on your game is to only scope what people need to see.

If you're particularly paranoid, you may want to remove the ConsoleMethods at the end of the file and control the commander map with events; this is probably unnecessary in most cases, as the commander map reveals no more information than the player could get by opening a map in single player mode or a dedicated server and flying/walking around.

Panning and Zooming:

There are three useful actions you can do via scripting with the GuiCommanderHud. First, you can zoom. This basically consists of changing the FOV of the overhead camera that the view is being rendered from (see GuiCommanderHud::processCameraQuery()), so you want to stick to values between 0 and one half pi. Larger values can give "interesting" results though no value should crash the engine. The available script methods for this are zoom() to instantly set the zoom level and zoomTo() to smoothly interpolate to the requested zoom level.

pan() and panTo() let you set the x,y co-ordinates of the overhead camera - essentially setting where the center of the map is. If you wish to pan onto an object, get the first two words of its position and pass them to pan() or panTo().

There are also two exposed fields, panSpeed and zoomSpeed, which let you tweak how quickly panning and zooming occurs. Negative values for these will give catastrophic results!

Performance:

On a 1.8Ghz P4 with 512mb of RAM and a GeForce 4 440 Go (equivalent to a fast GeForce 2), I get 70fps in the test FPS level without the map on screen, and 40fps with it on the screen (in addition to the normal FPS view). Suggested usage is to run the GuiCommanderHud in its own GUI, so that you don't have the overhead of the PlayGUI and its view of the world.

The GuiCommanderHud only renders interior, terrain, water, and environmental objects. You can easily change the mask it uses for rendering the scene by editing GuiCommanderHud::renderWorld() - there is a comment to guide you. Re-enabling all DTS shapes will bring a significant performance hit, since you'll be rendering potentially all the objects in the game world!

Extensions:

There are three major areas in which the GuiCommanderHud could be extended for your game.

First, you may want to render an overlay or icons indicating where objectives/players are. There is a comment located where you would want to render these objects, in GuiCommanderHud::onRender(). Using standard DGL calls will give good results.

Second, you may want to have the control handle mouse input. Looking at EditTSCtrl (located in engine/editor/editTSCtrl.*) will be an excellent guide for this process. You could either inherit from EditTSCtrl, or copy its mouse handling code to the GuiCommanderHud. The project() and unproject() functions that GuiCommmanderHud inherits from GuiTSCtrl may also be useful here.

Finally, you may wish to optimize rendering speed by caching output to a texture, rendering only the texture, and updating the texture as needed when the map needs to change its view. (You could even render to a large texture and simply pan it around, though this would kill the neat parallaxing and depth buffering effects that you could gain from actually rendering the world into the GuiCommanderHud.) This would be a bit tricky, but GuiCommanderHud::onRender would be your starting point. Look at TSShapeInstance::snapshot() for guidance on how to implement this sort of functionality in OpenGL - beware that the DirectX layer may not place nice with this.

Usage Restrictions:

None. Knock yourself out! :) I would ask that you put my name in your credits somewhere and send me a free copy of your game if this resource is a major piece of functionality for your project, but you are by no means required to do so. I'd also appreciate it if, if you found bugs or made enhancements for the code, if you'd send me your change(s) so I can merge them back into this resource. Many eyes find more bugs. ;)

Addendums:

Neat bit of code to try from the console(replace 1494 with the SimObject you want to track). Notice my commander hud is named commHud.

commHud.zoomTo(0.02);
function panPlayer() { commHud.panTo(getWord(1494.getPosition(), 0), getWord(1494.getPosition(), 1)); schedule(100, 0, panPlayer); }
panPlayer();
#61
02/25/2006 (7:28 pm)
Thanks for updating this, Dreamer! :)
#62
02/26/2006 (4:03 am)
Welcome, mind if I roll this into the MMORPG Enhancement Kit, I got really tired of trying (and failing) to get the RTSMap to work, it was just simpler to update this and merge in the functionality I wanted.
#63
02/26/2006 (5:15 pm)
I had a need for the map to track the player so I decided to implement Ben's little script above at the bottom of his post. Problem is your aren't always Object 1498 or whatever, so I added in a little bit of script I found elsewhere on the site, and modified it to make it simpler and work in true multiplayer mode w/o modifying the server in anyway... Here is the code, it assumes you have added the MapHUD to your PlayGUI and called it mapHud.

In client/scipts/client_commands.cs (or just client/scripts/client.cs is fine as well), add the following code...

//Returns the ID of the current Player
function getClientPlayerId(){
 for (%c = 0; %c<ServerConnection.getCount(); %c++)
 { 
   %obj = ServerConnection.getObject(%c);
   if (%obj.getClassName() $= "AIPlayer")
   { 
     %name = StripMLControlChars(%obj.getShapeName());
	
     if (%name $= $Pref::Player::Name)
     {
       return %obj.getId();
     }
   }
  }
 return -1;
}

MapHud.zoomTo(0.5);
function panPlayer() { 
	if($PlayerID $=""){
		$PlayerID = getClientPlayerId();
	}
	if($PlayerID $="-1"){
		MapHud.setVisible(0);
		return;
	}
	
	MapHud.panTo(getWord($PlayerID.getPosition(), 0), getWord($PlayerID.getPosition(), 1)); 
	schedule(100, 0, panPlayer); 
}

Then in playgui.cs in the PlayGUI::OnWake function at the bottom, just add...
panplayer();

There ya go, now your player will be tracked and you also have a global called $PlayerID, that you can access locally to do whatever with, I plan to bring my targeting code clientside to reduce the load on the server.
#64
02/26/2006 (5:23 pm)
Thanks for the last comment... it works well.
#65
03/14/2006 (10:31 pm)
I modifty Ben's code for TSE version

Enjoys!

Shannon

PS: There were flicking outside of the GUI but I'm not the expert in Atlas terrain :D

//-----------------------------------------------------------------------------
// Commander Map HUD
//
// Portions Copyright (c) GarageGames.Com
// Copyright (c) Ben Garney
// Convert into TSE by Shannon Scarvaci
//-----------------------------------------------------------------------------

#include "atlas/atlasInstance.h"
#include "atlas/atlasInstanceEntry.h"
// These includes are probably overkill -- BJG
#include "platform/platform.h"
#include "platform/platformVideo.h"
#include "platform/platformAudio.h"
#include "platform/platformInput.h"
#include "core/findMatch.h"

//#include "dgl/dgl.h"
#include "game/game.h"
#include "math/mMath.h"
#include "console/simBase.h"
#include "console/console.h"
//#include "terrain/terrData.h"
//#include "terrain/terrRender.h"
//#include "terrain/waterBlock.h"
#include "game/collisionTest.h"
#include "game/showTSShape.h"
#include "sceneGraph/sceneGraph.h"
#include "gui/core/guiTSControl.h"
#include "game/moveManager.h"
#include "console/consoleTypes.h"
#include "game/shapeBase.h"
#include "core/dnet.h"
#include "game/gameConnection.h"
#include "core/fileStream.h"
#include "gui/core/guiCanvas.h"
//#include "dgl/gTexManager.h"
#include "sceneGraph/sceneLighting.h"
#include "terrain/sky.h"
#include "game/ambientAudioManager.h"
#include "core/frameAllocator.h"
#include "sceneGraph/detailManager.h"
#include "gui/controls/guiMLTextCtrl.h"
#include "platform/profiler.h"
#include "game/fx/underLava.h"

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

class GuiCommanderHud : public GuiTSCtrl
{
private:
   typedef GuiTSCtrl Parent;


   Point2F mPanSpeed;
   F32     mZoomSpeed;
   S32     mLastRenderTime;

public:
   Point2F mPanGoal, mCurPan;
   F32     mZoomGoal, mCurZoom;

   GuiCommanderHud();

   bool processCameraQuery(CameraQuery *query);
   void renderWorld(const RectI &updateRect);

   void onRender( Point2I, const RectI &);
   static void initPersistFields();

   DECLARE_CONOBJECT( GuiCommanderHud );
};


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

IMPLEMENT_CONOBJECT( GuiCommanderHud );

GuiCommanderHud::GuiCommanderHud()
:   mPanSpeed(10, 10), mZoomSpeed(1), mCurPan(0,0), mCurZoom(M_PI_F/2),
    mPanGoal(0,0), mZoomGoal(M_PI_F/2), mLastRenderTime(0)
{
}

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

   addField("panSpeed",  TypePoint2F, Offset(mPanSpeed,  GuiCommanderHud), "Set the speed (x/y) we pan to our goal.");
   addField("zoomSpeed", TypeF32,     Offset(mZoomSpeed, GuiCommanderHud), "Set the speed we zoom with to our goal.");
}

static void findObjectsCallback(SceneObject* obj, void * val)
{
   Vector<SceneObject*> * list = (Vector<SceneObject*>*)val;
   list->push_back(obj);
}

bool GuiCommanderHud::processCameraQuery(CameraQuery *q)
{
   // draw all the objects
   Vector<SceneObject*> objects;
   U32 mask = AtlasObjectType;
   gClientContainer.findObjects(mask, findObjectsCallback, &objects);
	 Box3F box;
	 Point3F pt;
   for(U32 i = 0; i < objects.size(); i++)
   {
      // get the color
		 if(objects[i]->getTypeMask() & AtlasObjectType) {
			 pt = objects[i]->getBoxCenter();
			 box = objects[i]->getWorldBox();
			 break;
		 }
	 }
   // Scale ranges based on the highest/lowest point in the terrain
   F32 maxHi = box.max.x/2;//gClientSceneGraph->getCurrentTerrain()->findSquare(8, 0,0)->maxHeight / 10;
   F32 minHi = box.min.x/2;//gClientSceneGraph->getCurrentTerrain()->findSquare(8, 0,0)->minHeight / 10;

   q->object = NULL;
	 q->nearPlane = maxHi - box.max.z;
	 q->farPlane = box.len_z() + q->nearPlane + 100;
   //q->nearPlane = 1;
   //q->farPlane  = mFabs(maxHi) + mFabs(minHi);
   q->fov       = mCurZoom;

   // Make us high up, facing straight down.
   q->cameraMatrix = MatrixF(EulerF(3.14/2, 0, 0)); // rotate us to look straight down
   q->cameraMatrix.setPosition(Point3F(mCurPan.x+pt.x,mCurPan.y+pt.y, maxHi + 100)); // and high enough we won't clip

   return true;
}

void GuiCommanderHud::renderWorld(const RectI &updateRect)
{
   // Set up state
   // draw all the objects
   Vector<SceneObject*> objects;
   U32 mask = AtlasObjectType;
   gClientContainer.findObjects(mask, findObjectsCallback, &objects);
	 Box3F box;
	 Point3F pt;
   for(U32 i = 0; i < objects.size(); i++)
   {
      // get the color
		 if(objects[i]->getTypeMask() & AtlasObjectType) {
			 pt = objects[i]->getBoxCenter();
			 box = objects[i]->getWorldBox();
			 break;
		 }
	 }
   //TerrainRender::mRenderingCommander = true;
   F32 oldVisDist = gClientSceneGraph->getVisibleDistance();
   gClientSceneGraph->setVisibleDistance(box.len_x());
   F32 oldFogDist = gClientSceneGraph->getFogDistance();
   gClientSceneGraph->setFogDistance(box.len_x());

   // set up the camera and viewport stuff:

   // Render (stolen from GameRenderWorld)
   PROFILE_START(GameRenderCommanderWorld);
   FrameAllocator::setWaterMark(0);

#if defined(GATHER_METRICS) && GATHER_METRICS > 1
   TextureManager::smTextureCacheMisses = 0;
#endif

   /*glEnable(GL_DEPTH_TEST);
   glDepthFunc(GL_LEQUAL);
   glClear(GL_DEPTH_BUFFER_BIT);
   glDisable(GL_CULL_FACE);
   glMatrixMode(GL_MODELVIEW);*/
	 GFX->setZEnable(true);
	 GFX->setZFunc(GFXCmpLessEqual);
   GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI(0,0,0), 1.0f, 0 );
	 GFX->setCullMode(GFXCullNone);
   //dglSetCanonicalState();
   // If you want to render other things, change this mask.
   gClientSceneGraph->renderScene(  StaticObjectType | VehicleObjectType |  EnvironmentObjectType | AtlasObjectType | InteriorObjectType | WaterObjectType );
	 GFX->setZEnable(false);
   //glDisable(GL_DEPTH_TEST);

#if defined(GATHER_METRICS) && GATHER_METRICS > 1
   Con::setFloatVariable("Video::texResidentPercentage",
                         TextureManager::getResidentFraction());
   Con::setIntVariable("Video::textureCacheMisses",
                       TextureManager::smTextureCacheMisses);
#endif

   AssertFatal(FrameAllocator::getWaterMark() == 0, "Error, someone didn't reset the water mark on the frame allocator!");
   FrameAllocator::setWaterMark(0);
   PROFILE_END();


   // Restore state
   gClientSceneGraph->setVisibleDistance(oldVisDist);
   gClientSceneGraph->setFogDistance    (oldFogDist);
   //TerrainRender::mRenderingCommander = false;

   GFX->setClipRect( updateRect);//dglSetClipRect(updateRect);
}

void GuiCommanderHud::onRender(Point2I offset, const RectI &updateRect)
{
   // Update pan/zoom
   S32 time = Platform::getVirtualMilliseconds();
   S32 dt = time - mLastRenderTime;
   mLastRenderTime = time;

   mCurPan  += (mPanGoal  - mCurPan)  * (F32)dt/1000.f;
   mCurZoom += (mZoomGoal - mCurZoom) * (F32)dt/1000.f;

   // Render the world...
   Parent::onRender(offset, updateRect);

   // If you wanted to render custom GUI elements, like a sensor map, icons for
   // players/vehicles/objectives, you would do it here by calling project()
   // for all their positions and drawing bitmaps at the appropriate locations.
}

ConsoleMethod(GuiCommanderHud, pan, void, 4, 4, "(x, y) Cut to a location.")
{
   object->mPanGoal.set(dAtof(argv[2]), dAtof(argv[3]));
   object->mCurPan.set (dAtof(argv[2]), dAtof(argv[3]));
}

ConsoleMethod(GuiCommanderHud, panTo, void, 4, 4, "(x, y) Smoothly pan to a location.")
{
   object->mPanGoal.set(dAtof(argv[2]), dAtof(argv[3]));
}

ConsoleMethod(GuiCommanderHud, zoom, void, 3, 3, "(val) Zoom to a specified level.")
{
   object->mZoomGoal = dAtof(argv[2]);
   object->mCurZoom  = dAtof(argv[2]);
}

ConsoleMethod(GuiCommanderHud, zoomTo, void, 3, 3, "(val) Smoothly zoom to a specified level.")
{
   object->mZoomGoal = dAtof(argv[2]);
}

ConsoleMethod(GuiCommanderHud, zoomToArea, void, 6, 7, "(top, left, right, bottom, bool cut) Smoothly zoom to view the specified area. If cut is set, we jump there.")
{
   // Parse arguments
   F32 top, left, right, bottom;

   top    = dAtof(argv[2]);
   left   = dAtof(argv[3]);
   right  = dAtof(argv[4]);
   bottom = dAtof(argv[5]);

   // Figure out the center of the area
   Point2F center;

   center.x = (left + right) * 0.5f;
   center.y = (top + bottom) * 0.5f;

   object->mZoomGoal = mFabs(left - right) / 200; // Cheesy scaling fakery.

   // And set our motion
   object->mPanGoal = center;

   // Cut if requested
   if(argc > 6)
      if(dAtob(argv[6]))
      {
         object->mCurPan  = object->mPanGoal;
         object->mCurZoom = object->mZoomGoal;
      }
}
#66
03/28/2006 (7:17 am)
How exactly does one add a guicommanderhud object, as specified in step 5?

Nevermind. It helps if you notice that the object is GuiMapHud, not GuiCommanderHud.
#67
03/29/2006 (9:15 am)
*Deleted*
#68
04/12/2006 (2:13 am)
Did anybody figure out exactly how to implement players icons??
I used project() as wrote in the code comment , but project() always return false..
I debugged at project() and noticed that the condition (winz>1) is always true (winz has a very small fractional number).
Here is my small code....
GameConnection* conn = GameConnection::getConnectionToServer();
for (SimSetIterator itr(conn); *itr; ++itr) {
  // Make sure the shapebase object is a Vehicle object.
  if ((*itr)->getType() & VehicleObjectType) {
	ShapeBase* shape = static_cast<ShapeBase*>(*itr);
	Point3F pos = shape->getPosition();
	Point3F newCoord;
	if (!project(pos, &newCoord)){
	  Con::printf("CodeLog----:Projection Error.  pos: x=%f y=%f z=%f",pos.x,pos.y,pos.z);
        }
	//else draw an icon at newCoord...
  }
}

appreciate any quick help...
.
#69
04/18/2006 (6:02 am)
#70
05/06/2006 (8:25 am)
Has anyone else noticed that with Robert Geiman's changes the map sometimes zooms in and out when the player rotates? Any idea why that happens?

Thanks
Todd
#71
05/06/2006 (8:38 am)
William, yes, I noticed this myself. I didn't notice it originally when I posted, because I didn't use "mouse look" in my game (ie, moving the mouse moved the player forward/backward rather than looking up/down), and my changes work great when not using "mouse look".

Someone posted a fix to that issue in this forum recently, bit it looks like they've since deleted it. I'm assuming this is because their fix had a bug in it.

I'll try taking a look at the code again, but my 3D math leaves a lot to be desired, so if anyone finds the fix before me, please feel free to post it.
#72
05/17/2006 (5:31 am)
This is great, I have it working fine except one thing, I cannot zoom to see the whole mission area properly because it clips some pieces of the terrain or something...

Here's a screen shot of the problem (I marked the mission area with a green rectangle):

www.upimages.net/upload/4cfd1535.png

Any ideas what might cause the clipping?
#73
06/09/2006 (1:06 am)
Hi,

The script was very very useful and I could able to add a mini map into my demo in TSE. But I need the background of the mini map to be transparent instead of a Grey background. Please help.

Thanx.
#74
06/12/2006 (11:16 pm)
My player object is some where in the bottom left corner of my commander HUD.
and even if i zoom using the concole cmd zoomToArea the zoom is with respect to the center of the map and not with respect to the player position. Could any one suggest any method to make my player position to be the center of my commander HUD.
Thanks in advance
#75
07/05/2006 (8:39 am)
This seems to mimick the tscontrol and add a map control
I see the world around the map as well, it appears twice
I have TLK 1.4 and drl, any ideas?
#76
07/15/2006 (2:33 am)
Guys, I've tried this Commander Map, and I wonder can we add some dots in the map that actually represents the player(s) movements around the map ?

=================================================
void GuiCommanderHud::onRender(Point2I offset, const RectI &updateRect)
{
......
// If you wanted to render custom GUI elements, like a sensor map, icons for
// players/vehicles/objectives, you would do it here by calling project()
// for all their positions and drawing bitmaps at the appropriate locations.
}
=================================================

how exactly "calling project()" works ?
#77
07/19/2006 (9:19 pm)
Shannon "ScarWars", thanks for the TSE port!
#78
09/21/2006 (7:16 am)
Guys this is great,
I will be putting this into my engine. Well done.
Mal
:)
#79
10/26/2006 (9:09 am)
I have the same question like Bisher Tarakji

Did anybody figure out exactly how to implement players icons??
I used project() as wrote in the code comment , but project() always return false..
I debugged at project() and noticed that the condition (winz>1) is always true (winz has a very small fractional number).
Here is my small code....

GameConnection* conn = GameConnection::getConnectionToServer();
for (SimSetIterator itr(conn); *itr; ++itr) {
// Make sure the shapebase object is a Vehicle object.
if ((*itr)->getType() & VehicleObjectType) {
ShapeBase* shape = static_cast(*itr);
Point3F pos = shape->getPosition();
Point3F newCoord;
if (!project(pos, &newCoord)){
Con::printf("CodeLog----:Projection Error. pos: x=%f y=%f z=%f",pos.x,pos.y,pos.z);
}
//else draw an icon at newCoord...
}
}



appreciate any quick help...
#80
10/27/2006 (11:35 am)
I find solution for my problem ^_^

for (SimSetIterator itr(conn); *itr; ++itr) {
// Make sure the shapebase object is a Player object.
if ((*itr)->getType() & PlayerObjectType) {
ShapeBase* shape = static_cast(*itr);

// Make sure that the object is the client
if (shape == control || shape->getShapeName())
{
// Make sure the shapebase object is a player
if (shape->getType() & PlayerObjectType)
{
mPlayer = conn->getControlPlayer();
if (mPlayer)
{
mPlayer->getEyeTransform( &eyePlayer);
eyePlayer.getColumn(3, &mMyCoords);
}
Point3F newCoord;
Point3F dest;
// Get coords of player object
newCoord = shape->getPosition();

project(newCoord, &newCoord);
project(mMyCoords, &mMyCoords);

//if (!project(newCoord, &dest)) continue;
dest.x = newCoord.x - mMyCoords.x;
dest.y = newCoord.y - mMyCoords.y;
//dest.y = -dest.y;
dest.z = 0;

glColor4f(1,0,0,1);
glVertex2f(dest.x,dest.y);
}
}
}
}

this code works fine for me, and I think this is the way the "project()" function is used :D