
Introduction
A few days back, a question was posted on the GSE forum about how to retrieve the color underneath the mouse cursor, with the intent of identifying what country was selected in a strategy-type game.
I responded to that post with a suggestion about using an off-screen color-keyed map to do the country identification, and I decided to expand on that concept and put up a tutorial about the process.
The Map
I searched on the web for a random map generator to use to get a map to work from (since I can't draw stick figures properly) and found This One. Using this site, I generated the following image:

The Color-Key Concept
As you can see, it would be impossible to tell anything about the area under the mouse based on the colors in this image. Instead, we create a second version of the map image that we never display to the user, but that has each "country" colored as a single, distinct, solid color. When we look up where the mouse is, instead of getting the color under the cursor on the normal map image, we look up the same spot on our "color key" image instead.
I took the image above into Photoshop and, after some selecting and recoloring, came up with this:
So if we look up the location of the mouse cursor on our hidden color-key image, and we get a red pixel, we know we are in "Country 1". The same is true for any of the other colors we define as countries. If we aren't over one of our pre-defined colors, we know we must be between countries.
There are a couple of other benefits here too... If we needed to overlay player information, map resources, text, etc, we don't have to worry about it interfering with our color key lookup because we really don't care what is displayed on the screen.
Creating the Project
I'm going to create a very basic project to demonstrate how to do the lookup here. After we get the basics working, I'll show another technique of overlaying a transparent image to hilight the selected country on the displayed map.
Open up Game Studio Express and create a new project. I called mine ColorKeyMap.
We'll need to set up a few things so we can use content manager to load our images, so right click on the project in the solution explorer and create a directory called content, under that create a directory called textures. Copy your map and color key images into the textures folder and add them to the project through solution explorer.
Next we'll need to set the screen size and enable the mouse. We can do this during the game's Initialize method:
protected override void Initialize()
{
// TODO: Add your initialization logic here
this.IsMouseVisible = true;
graphics.PreferredBackBufferHeight = 400;
graphics.PreferredBackBufferWidth = 640;
graphics.ApplyChanges();
base.Initialize();
}
We'll also need to add a couple of items to our declarations area so that we have textures to work with. Find these two lines near the top of your project:
GraphicsDeviceManager graphics;
ContentManager content;
And add the following right after them:
Texture2D t2dMap, t2dColorKey;
SpriteBatch spriteBatch;
And finally, we'll need to update LoadGraphicsContent so we use Content Manager to load the textures and initialize our SpriteBatch object so we can draw to the screen:
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
// TODO: Load any ResourceManagementMode.Automatic content
t2dMap = content.Load<Texture2D>(@"content\textures\map_display");
t2dColorKey = content.Load<Texture2D>(@"content\textures\map_colorkey");
}
// TODO: Load any ResourceManagementMode.Manual content
}
Drawing the Map
I updated the Draw method to drop the map to the screen:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(t2dMap,new Rectangle(0,0,t2dMap.Width, t2dMap.Height), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
Determine the Color Data
The next step is to figure out how the colors we used on our map are represented in the texture data. To do this, I put this in as my temporary Update method. It shows the coordinates and the color value of the pixel on the colorkey as the window title:
protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
MouseState ms = Mouse.GetState();
string sColorval = "";
uint[] myUint = new uint[1];
if (ms.X >= 0 && ms.X < t2dColorKey.Width && ms.Y >= 0 && ms.Y < t2dColorKey.Height)
{
t2dColorKey.GetData<uint>(0, new Rectangle(ms.X, ms.Y, 1, 1), myUint, 0, 1);
sColorval = myUint[0].ToString();
}
Window.Title = ms.X.ToString() + "," + ms.Y.ToString() + " - " + sColorval;
base.Update(gameTime);
}
This is temporary code, but it does contain the basics of what we are going to be doing, so lets run thru them:
We create a MouseState object called "ms" and get the current mouse state so that we can get the X,Y location of the mouse relative to the upper left corner of our window (0,0).
We also create an array of uints (unsigned integers) since that is the data format the texture data will be stored in inside the Texture2D object. We check to make sure that the mouse is within the texture area (if we aren't drawing at 0,0 we would need to offset that appropriately) and then we get the data from the texture. The GetData command returns raw data from the Texture and, in this usage, takes the following parameters:
- the <uint> part tells GetData that we want the data returned as unsigned integers
- The 0 indicates that we are reading from Mip Map layer 0. Mip Maps levels are different sized versions of the texture that can be stored within the same texture. They are used in 3D graphics as a replacement for large textures as you get further from the object.
- The rectangle parameter specifies what region of the texture we want returned. In this case, it is a 1x1 pixel area at the mouse coordinates.
- The "myUint" is the array we are using to store the returned results. Unlike most functions, GetData doesn't return anything (it's a void) but it updates the array passed to it instead.
- The next 0 indicates that we want to start filling data at element 0 in our array. Since we are using an array with only 1 value in it, 0 is the only value in our array.
- Finally, the last parameter (1) indicates how much data we want copied to our array. In this case, 1 data item (a single unsigned integer).
We don't actually have to run this to get the values, as we *could* calculate them ahead of time in hex. In hex, the values are AARRGGBB (Alpha Channel, Red, Green, Blue). But for some reason pink (rgb 255,0,255) comes out as just plain 0, and I don't even pretend to know why it isn't 4294902015 (FFFF00FF) instead.
Anyway, a fully non-transparent red pixel will be FFFF0000 in hex, or 4294901760 in decimal. A half-transparent red pixel would be 7FFF0000 in hex, or 2147418112 in decimal. Since I used completely non-transparent values to create the color key map above, the colors work out pretty simply.
I ran my project and placed the mouse over each of my "countries" and wrote down the reported color value. I used these to create a set of constants in my code. Right up under my texture and spritebatch declarations, I added:
const uint color_red = 4294901760;
const uint color_green = 4278255360;
const uint color_pink = 0; // This one struck me as strange, but that's what I got...
const uint color_cyan = 4278255615;
const uint color_blue = 4278190335;
const uint color_yellow = 4294967040;
const uint color_black = 4278190080;
Now I simply use those constants in a switch statement to determine what country the mouse is above. I replaced my temporary update routine with this one:
protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
MouseState ms = Mouse.GetState();
uint uCountry=color_black;
string sCountry = "";
uint[] myUint = new uint[1];
if (ms.X >= 0 && ms.X < t2dColorKey.Width && ms.Y >= 0 && ms.Y < t2dColorKey.Height)
{
t2dColorKey.GetData<uint>(0, new Rectangle(ms.X, ms.Y, 1, 1), myUint, 0, 1);
uCountry = myUint[0];
}
switch (uCountry)
{
case color_red: sCountry = "Reddistan"; break;
case color_green: sCountry = "Greenburg"; break;
case color_pink: sCountry = "Pinkshire"; break;
case color_cyan: sCountry = "Cyanville"; break;
case color_blue: sCountry = "United Blues"; break;
case color_yellow: sCountry = "Republic of Yellow"; break;
}
Window.Title = sCountry;
base.Update(gameTime);
}
And now the window title will display the name of the country the mouse is over, or blank if the mouse cursor is over the water.
There area couple of things to note here. I'm displaying the map image at 0,0. If it was somewhere else, I'd have to compensate for that by offsetting the lookup into the color-key image by the appropriate number of pixels (ie, if the map was displayed at 50,25 instead of 0,0, I would have to subtract 50 from all of the X values above, and 25 from all of the Y values so that 50,25 on the screen is 0,0 in my lookup onto the color key.
Some Extra Fun
With a little more poking around in Photoshop, I was able to produce the following images that have the countries outlined and a little bit of an outer glow set to them:






Save all of these to your content\textures directory and add them to your project with Solution Explorer.
Next, update add some declarations for the textures and a variable to track what country should be hilighted (if any) We'll default it to -1 (for none):
Texture2D[] t2dCountries = new Texture2D[6];
int iCountryToHilight = -1;
And update our LoadGraphicsContent method to load these new textures:
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
// TODO: Load any ResourceManagementMode.Automatic content
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
t2dMap = content.Load<Texture2D>(@"content\textures\map_display");
t2dColorKey = content.Load<Texture2D>(@"content\textures\map_colorkey");
t2dCountries[0] = content.Load<Texture2D>(@"content\textures\map_country1");
t2dCountries[1] = content.Load<Texture2D>(@"content\textures\map_country2");
t2dCountries[2] = content.Load<Texture2D>(@"content\textures\map_country3");
t2dCountries[3] = content.Load<Texture2D>(@"content\textures\map_country4");
t2dCountries[4] = content.Load<Texture2D>(@"content\textures\map_country5");
t2dCountries[5] = content.Load<Texture2D>(@"content\textures\map_country6");
}
// TODO: Load any ResourceManagementMode.Manual content
}
Next, we need two modifications to our Update method. The first sets the iCountryToHilight variable to -1 at the beginning of every update loop. Next, in the Switch statement where we determine what country is selected we set it to the appropriate country value:
protected override void Update(GameTime gameTime)
{
// Allows the default game to exit on Xbox 360 and Windows
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
MouseState ms = Mouse.GetState();
uint uCountry=color_black;
string sCountry = "";
iCountryToHilight = -1;
uint[] myUint = new uint[1];
if (ms.X >= 0 && ms.X < t2dColorKey.Width && ms.Y >= 0 && ms.Y < t2dColorKey.Height)
{
t2dColorKey.GetData
(0, new Rectangle(ms.X, ms.Y, 1, 1), myUint, 0, 1);
uCountry = myUint[0];
}
switch (uCountry)
{
case color_red: sCountry = "Reddistan"; iCountryToHilight = 0; break;
case color_green: sCountry = "Greenburg"; iCountryToHilight = 1; break;
case color_pink: sCountry = "Pinkshire"; iCountryToHilight = 3; break;
case color_cyan: sCountry = "Cyanville"; iCountryToHilight = 4; break;
case color_blue: sCountry = "United Blues"; iCountryToHilight = 2; break;
case color_yellow: sCountry = "Republic of Yellow"; iCountryToHilight = 5; break;
}
Window.Title = sCountry;
base.Update(gameTime);
}
And finally, we add a if clause to our Draw routine. If iCountryToHilight isn't -1 then we draw the appropriate overlay texture:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(t2dMap,new Rectangle(0,0,t2dMap.Width, t2dMap.Height), Color.White);
if (iCountryToHilight != -1)
{
spriteBatch.Draw(t2dCountries[iCountryToHilight], new Rectangle(0, 0, t2dMap.Width, t2dMap.Height), Color.White);
}
spriteBatch.End();
base.Draw(gameTime);
}
That's it! A pretty simple but effective means to determine exactly what the user is mousing over in a situation like this. It isn't limited to just maps though, or even 2D games. If you have an interface that you overlay onto your game with buttons and widgets the user can click on, you can make a color-keyed image and use the technique above to determine what "controls" the player has clicked on instead of checking dozens of little rectangles in code. It also makes it very easy to make non-rectangle shaped controls, since the color pattern on the keyed image is what you are using to determine what was clicked on.