Game Development Community

Mini map fog of war

by Jared Barger · in RTS Starter Kit · 11/17/2004 (10:28 am) · 44 replies

img95.exs.cx/img95/7571/mini_map.jpg
Additions below for changing the ugly boxes into squares (Different post in this thread)
11/19 Fixed bug that would cause any visible enemy units to render a vision blob on the minimap (ie. if you could see an enemy you could also see the radius around that unit). Changes are bolded.


First off we need to whip out the mightiness that is paint brush and build the fog image. Just a 16x16 pure black jpg is all we need. Now open up the starter app and add a GuiBitmapCtrl to the MapDisplay group.

r-click MapDisplay in the object list
click new control
select GuiBitmapCtrl
stretch it to fill the MapDisplay except for the border around the map

It should be completely covering the map at this point. Now we want to place this "in the middle" of that group.

With your black image selected go to layout->send to back.
Now select the background image and layout->send to back again.

The MapDisplay group should look like...
MapDisplay - GuiControl
|
|-MapDisplayBackground - GuiBitmapCtrl
|-FogImage - GuiBitmapCtrl
|-MapHud - GuiMapHud

...and the black image you added should be completely covered by the map itself now. Alrighty, done with the boring part.

All the code changes needed are within the engine/game/rts/guiMapHudRender.cc file. So first off back that bad boy up.

Fist we need to setup a little structure to hold onto some data for our units. Below the includes add...
struct ClientUnit{
 S32 team;
 Point2I point;
};//ClientUnit

Next we need to comment out the current calls that render the map. Change...
//render the map
dglSetBitmapModulation(ColorF(1.0, 1.0, 1.0));
dglDrawBitmap(mRenderTexture, offset, GFlip_Y);
to..
//render the map
// dglSetBitmapModulation(ColorF(1.0, 1.0, 1.0));
// dglDrawBitmap(mRenderTexture, offset, GFlip_Y);

Now we need to setup some variables...
Point2I drawChunkPoint;			// Will hold a point to render some map at
RectI mapRect;				// Will hold our source rect from the map image
ClientUnit* unitList = 0;		// List of important info about our units
ClientUnit* unitIter;			// To iterate through the list later
int listSize = 0;			// Count of the unit list
int vision = 10;			// Unit vision range on the map (in pixels?)
[b]RTSConnection* myConn = 0; // Will hold our connection to the game
S32 localTeam = 0;                  // WIll hold our team id
[/b]

Now we need to figure out what team we're on
myConn = (RTSConnection*)mConnection->getServerConnection();
localTeam = myConn->getTeam(); // Now get our team id


Now we're gonna need to figure out how many units we have to deal with and allocate the unit list for that.
for(SimSetIterator itr(mConnection); *itr; ++itr){
 if((*itr)->getType() & PlayerObjectType) listSize++;
}//for
unitIter = unitList = new ClientUnit[listSize];

Ok, here's where most everything is happening. We're going to alter the unit rendering loop to render our map objects and to collect data from our units. We need to hold off on rendering the unit dots to the map until we're done so they don't get covered up by pieces of the map.

Comment out everything from "//render the units" to the beginning of the loop and add in a call to dglSetBitmapModulation so that it looks like this...
//render the units
// glPointSize(2.0f);
// glColor3f(0.0, 0.0, 1.0);
// glBegin(GL_POINTS);
//  static char buff[18];
//  dSprintf(buff, 18, "$Server::TeamInfo0");
dglSetBitmapModulation(ColorF(1.0, 1.0, 1.0));
Page «Previous 1 2 3 Last »
#1
11/17/2004 (10:28 am)
Continued...

Now we're going to totally change the if block that currently renders the units. We need to comment out that whole chunk and change it to look like this...
if(projectPoint(unit->getPosition(), &screenPos))
{
 /*
 S32 team = unit->getTeam();					The original block of code that's removed now
 buff[17] = '0' + team; 
 const char* pref = Con::getVariable(buff);
 ColorF teamColor;
 dSscanf(pref, "%f %f %f", &teamColor.red, &teamColor.green, &teamColor.blue);
 glColor3f(teamColor.red, teamColor.green, teamColor.blue);
 glVertex2f(offset.x + screenPos.x, offset.y + screenPos.y);
 */

 [b]if(localTeam == unit->getTeam()){         // Only give us map vision for our team[/b]

								// Get the XY of the upper left corner that the 
								// unit can see
 drawChunkPoint.set(offset.x + screenPos.x - vision, offset.y + screenPos.y - vision);

								// Get the rect of the map to render. Since it's flipped
								// on the Y axis before rendering we need to "come in"
								// from the opposite side
 mapRect.set(screenPos.x - vision, mTextureSize - screenPos.y - vision, vision * 2, vision * 2);

								// Drop the rect from the map at point drawChunk with
								// the clipping rectangle mapRect and then flip it
 dglDrawBitmapSR(mRenderTexture, drawChunkPoint, mapRect, GFlip_Y);

								// Save the info we'll need to render the blip later
 [b]}//if[/b]

 unitIter->point.set(offset.x + screenPos.x, offset.y + screenPos.y);
 unitIter->team = unit->getTeam();
 unitIter++;
}

Ok, at this point the map will render and work except we don't have any unit dots shown so we need to add this one last loop

which is just a consolidated version of what used to render the units.

glPointSize(2.0f);
glColor3f(0.0, 0.0, 1.0);
glBegin(GL_POINTS);
static char buff[18];
dSprintf(buff, 18, "$Server::TeamInfo0");
unitIter = unitList;						// Reset the iterator

for(int i = 0; i < listSize; i++){
 buff[17] = char(48) + unitIter->team;             // forum font doesn't seem to like single quotes...char(48) == '0'
 const char* pref = Con::getVariable(buff);
 ColorF teamColor;
 dSscanf(pref, "%f %f %f", &teamColor.red, &teamColor.green, &teamColor.blue);
 glColor3f(teamColor.red, teamColor.green, teamColor.blue);
 
 glVertex2f(unitIter->point.x, unitIter->point.y);

 unitIter++;
}//for
glEnd();


delete []unitList;						// Cleanup!

That's it, compile and go. The mini-map should be crankin with a fog of war look to it now.

Things that need updated/changed...

A prettier way than running this through three loops. Particularly the first loop where its only purpose is to find out how many units we have...this seems like a complete waste to me, gotta be a better way. I suppose it could use a vector list, but I'm afraid of templates.

The vision variable should be getting pulled from the unit's datablock rather than hard coded like it is now...otherwise how am I ever gonna have watch towers! I tried at that for a bit but the computer beat me, for now.
#2
11/17/2004 (10:48 am)
Looking pretty sweet!

I'm looking forward to your smoothed/circular vision ranges implementation for sure!

I'm curious, how bad did this affect your map rendering time?
#3
11/17/2004 (11:19 am)
Well, just for giggles I set the row size in the game connection to 25 (625 units) and turned on unit shadows. Had visual studio, web browser, yadda yadda still open. 800x600x32 windowed and it was running fine. Pardon the ignorance, but I'm not sure exactly how to check the actual fps.

p4 3ghz
1024mb
geforce 5200

Sorry for the big ass picture, but I just thought this was too cool to not post it hehe

img130.exs.cx/img130/8892/minmap_3.jpg
#4
11/17/2004 (1:25 pm)
Error C3209: '0 ' : Unicode identifiers are not yet supported at this line:
buff[17] = '0' + unitIter->team;
What character should '0' be ? '0' ?
Copy paste appears to mess up the token
#5
11/17/2004 (1:43 pm)
It's gotta be a copy paste thing, but I really don't understand why. It's supposed to be a single quoted, just for the character 0. If I copy back out I get the same thing though...odd.

@shawn
I changed that line to...
buff[17] = char(48) + unitIter->team;
to avoid this, only thing I can think is it's the font on the forums. Thanks for mentioning it
#6
11/17/2004 (2:14 pm)
This is just awesome.
#7
11/17/2004 (2:44 pm)
Yea thanks for the resource! I would call it a fog of viewable distance, what ideas do you have on making it persistent so areas already visited are unmasked?
#8
11/17/2004 (2:51 pm)
@Shawn

Showing a semi-transparent overlay for areas visited would be great, in fact ideal. The rub is the way this works is to blit the blocks of the map itself over the top of the black fog. Technically the map is "above" the "fog". The only way I can see to do an overlay would be to have things work the other way around. ie.

maphud
map
semitransparent fog
dark fog

and then blit in "transparency" onto the dark fog area permanently and into the semitransparent area for the duration that a unit is there. All that being said...I don't have a damn clue where to start with that =P
#9
11/17/2004 (5:35 pm)
In the console: metrics(fps);

:)
#10
11/17/2004 (7:01 pm)
img85.exs.cx/img85/4747/minmap4.jpg
Making the boxes into squares
Alright, for this we need to add a function to draw subregions of an image that are circular. This is considerably heftier on the system than just using the boxes, but imo is worth it for the look. That being said I've not made this functionality all that flexible. Where I could I made it do specifically what I needed for the rts minimap, although this would be easy enough to undo if you had another use for it.

In engine/dgl/dgl.h we need to add two new prototypes
// texture = texture to render
// in_rAt & dstRect = destination rectangle
// in_rSubRegion & srcRect = source rectangle
// complexity = number of sides to the circle
// in_flip = orientation : hard coded to flip how we need for the rts
void dglDrawBitmapCircularSR(TextureObject* texture, const Point2I& in_rAt, 
					       const RectI& in_rSubRegion, int complexity = 10, const U32 in_flip = GFlip_None);
void dglDrawBitmapCircularSR(TextureObject* texture, const RectI& dstRect,
						 const RectI& srcRect, int complexity = 10, const U32 in_flip = GFlip_None);

At the top of engine/dgl/dgl.cc after the includes add
const F32 PI_OVER_180 = 0.01744;		// Close enough for government work
#11
11/17/2004 (7:01 pm)
Continued...

Now to add the implementations into engine/dgl/dgl.cc
// Calls overloaded dgldrawbitmapcircularsr telling it not to stretch the image
void dglDrawBitmapCircularSR(TextureObject* texture, const Point2I& in_rAt, 
						 const RectI& srcRect, int complexity, const U32 in_flip){
			

   RectI stretch(in_rAt.x, in_rAt.y, srcRect.len_x(), srcRect.len_y());
   dglDrawBitmapCircularSR(texture, stretch, srcRect, complexity, in_flip);
}

void dglDrawBitmapCircularSR(TextureObject* texture, const RectI& dstRect,
						 const RectI& srcRect, int complexity, const U32 in_flip){

 // Make sure we're given valid info
 AssertFatal(texture != NULL, "GSurface::drawBitmapStretchSR: NULL Handle");
 if(!dstRect.isValidRect()) return;
   
 AssertFatal(srcRect.isValidRect() == true, "GSurface::drawBitmapCircularSR: routines assume normal rects");

 // Some open GL setup
 glDisable(GL_LIGHTING);
 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);

 // Initialize variables for the source and destination locations
 F32 dstCenterX = F32((dstRect.extent.x / 2) + dstRect.point.x);
 F32 dstCenterY = F32((dstRect.extent.y / 2) + dstRect.point.y);
 F32 srcCenterX = F32((srcRect.extent.x / 2) + srcRect.point.x) / F32(texture->texWidth);
 F32 srcCenterY = F32((srcRect.extent.y / 2) + srcRect.point.y) / F32(texture->texHeight);
 F32 dstRadius = F32(dstRect.extent.x);
 F32 srcRadius = F32(srcRect.extent.x) / F32(texture->texWidth);

 F32 i;    // used in loop counting 
 
 if(complexity < 6) complexity = 6;   // Make sure requested sides is reasonable
 else if(complexity > 36) complexity = 36;

 F32 stepBy = F32(360 / complexity); // Cuts out two more divisions pre iteration

 glColor4ub(sg_bitmapModulation.red, sg_bitmapModulation.green,
	        sg_bitmapModulation.blue, sg_bitmapModulation.alpha);

 glBegin(GL_TRIANGLE_FAN);

 glTexCoord2f(srcCenterX, srcCenterY); // Center vertex to the circle
 glVertex2f(dstCenterX, dstCenterY);

                                // Loop through building the circle. We 
                                // need to texture going the opposite
                                // direction to compensate for the map
                                // hud being rendered flipped on the
                                // Y axis
 for(F32 n = 0, i = 360; n <= 360; n += stepBy, i -= stepBy){
  glTexCoord2f(srcCenterX + srcRadius * cos(i * PI_OVER_180), srcCenterY + srcRadius * sin(i * PI_OVER_180));
  glVertex2f(dstCenterX + dstRadius * cos(n * PI_OVER_180), dstCenterY + dstRadius * sin(n * PI_OVER_180));
 }//for

 glEnd();

 glDisable(GL_BLEND);
 glDisable(GL_TEXTURE_2D);
}//dglDrawBitmapCircularSR

Now we just need to go back to the guiMapHudRender.cc file and in the loop that draws the map change the call to dglDrawBitmapSR to a call to...
dglDrawBitmapCircularSR(mRenderTexture, drawChunkPoint, mapRect, 9, GFlip_Y);
The 9 there is the number of sides to use when building the circle. Play around with it to see what you get, just don't get too crazy =p
#12
11/19/2004 (6:21 pm)
This looks like it should be included to the head
#13
11/19/2004 (6:38 pm)
There's a "head" to this? cool ! (i mean the rts kit) i thought it was a 1 time download. so how do you get into head? like: your_login@cvs.garagegames.com:/cvs/rts

damn thats a lot of bots hows the frame rate?

paste your copy to word pad then copy and paste it to your editor to keep the format
#14
11/19/2004 (7:15 pm)
@Ace: No, there isn't actually a "head", but GG has made indications that with all the resources and code provided since the initial release, they will be putting together an updated version in the future with much of the things that have been in the forums!

Disclaimer: I don't work for GG, this is just my impression based on various posts over the last week + 1 day.
#15
11/19/2004 (8:08 pm)
To note, since the response to the RTS kit has been so great, we'll probably set-up a CVS repository soon. We'll let it be known here on these forums when we do so, and we'll describe how to access it. Of course, we'll also provide updates to the installers when we have updates worthy of inclusion.

We want you guys to be able to stay up-to-date with the RTS kit's development, and this definitely seems like the best solution. Note that we can't guarantee we'll continue updating the RTS kit... the response has been great so far, and the better it gets the more likely updates will be, but all of us on the team want to keep up with the pack as much as we can afford to. Really, we've all been impressed with all the contribution and collaboration coming to the pack so far. You guys rock. :)
#16
11/19/2004 (9:17 pm)
Awe man i got really excited for a few there,
#17
11/19/2004 (9:24 pm)
@Josh: Please please ask if we can use SVN instead ;)

I know that you guys were concerned about how access control is set up, but we configured ours relatively easily to handle different categories of users (torque-license, nemv-artist, etc), and have our access scripts check for grouping prior to access.

SVN can I admit be a bit harder to configure originally, but it's well worth the trouble--we're doing some damned amazing things with SVN I would have never attempted with CVS, and we started off as newbies with it just like everyone else.

EDIT: I guess it could go both ways with "management"--yes, this is a good size customer base to explore using SVN fully, internal and external, or it could go "no, let's just use what we know works".

My input would be that in the long run svn is worth it, but that's just me :)
#18
11/20/2004 (5:57 pm)
SVN isn't that hard to configure, but it's authentication system isn't set-up to efficiently and securely handle large numbers of users yet. So we'll have to stick with our CVS implementation for now. But yes, I agree, SVN rocks. :)
#19
12/01/2004 (4:48 pm)
This resource is great, thanks Jared, but it is not very useful on it's own.

I don't mean that as a criticism but with the play gui and its camera view the way it is you can still "see" everything in the play GUI even though a lot is in darkness on the mini-map.

Can anyone who is in the know about such things tell me where to start looking to limit the camera to only showing 'known' areas of the map as well. Or would this require a major overhaul to the way camera work?
#20
12/01/2004 (8:11 pm)
I completely agree, Jeff.

Unfortunately, to be quite blunt, I don't have a clue where to really start with such a thing. I have to think that to accomplish this would require a fairly substantial rewrite of the camera class itself and possibly the terrain as well. A similar scheme as I used for the minimap (only rendering the appropriate chunks of the map that your'e nearby) would require some sort of subsectioning of the terrain object. This is pretty beyond the scope of what I know how to do with Torque. Furthermore I'm left wondering if there isn't a much better method that I just haven't thought of.

Of course there's always the cheater way out of removing the camera scrolling entirely and only letting the camera be positioned over the top of a unit you control, but this isn't ideal either.
Page «Previous 1 2 3 Last »