Part 5 - Side Topic - Fog of War and Light Aura effects

Forum poster Sando asked about creating a Fog of War type effect with the tile engine, where each square starts out hidden by darkness and needs to be revealed by the player moving around the map. By expanding on the MapCell class, we can create this effect fairly easily. I'll also look at adapting the basic Fog of War code to create an aura of light around the player, where everything outside the light aura is hidden by darkness.

To keep things simple, I'm going to base this tutorial on the square-tile maps from Part 2, so we will be starting off with this version of the source code.

This will be a pretty basic "Fog of War" setup, so we will simply use a black square the same size as one of our tiles. The image below is 48x48 pixels, matching our map tiles:

Go ahead and add this image to the Textures folder of the Content Project in the tile engine solution. We need to add a declaration to the Game1 class to hold the texture:

Texture2D fogOfWar;

And add a line to the LoadContent() method to load the texture:

fogOfWar = Content.Load<Texture2D>(@"Textures\FogOfWar");

Open up the MapCell.cs class file and add the following property declaration:

public bool Explored { get; set; }

and update the class constructor (public MapCell(int tileID)) to initialize the Explored property to false:

Explored = false;

Switch back over to the Game1 class and create a new loop after the existing loop that draws our tile map images:

for (int y = 0; y < squaresDown; y++)
    for (int x = 0; x < squaresAcross; x++)
        if (myMap.Rows[y + firstY].Columns[x + firstX].Explored == false)
                new Rectangle(
                    (x * Tile.TileWidth) - offsetX, (y * Tile.TileHeight) - offsetY,
                    fogOfWar.Width, fogOfWar.Height),

If you go ahead and run your project right now, you'll just get a completely black map! That's because we haven't yet introduced any way for the player to "explore" the map. In order to do that, we'll add a super-simple player character to the screen and modify our control scheme to move the character instead of simply scrolling the map.

NOTE: This isn't a reasonable way to actually implement a player-controlled character on the map... in fact, you'll be able to wander right off the edges of the screen without the map scrolling, etc. We are simply interested in a method for dismissing the fog at this point!

Add the following image to the Textures folder of your Content project. This sprite is a scaled down version of the female NPC sprite from Game Poetry.

In the Game1.cs file, add variables to hold the player texture and position vector to the declarations area:

Texture2D playerTexture;
Vector2 playerPosition = new Vector2(15,15);

Of course, we will also need to load the image in the LoadContent() method:

playerTexture = Content.Load<Texture2D>(@"Textures\Player");

In the Draw() method, add a call to draw the player's sprite. This can be placed right before spriteBatch.End():

spriteBatch.Draw(playerTexture, playerPosition, Color.White);

All that remains now is to replace the code in our Update() method that scrolls the map around. Here is the full content of the Update() method:

protected override void Update(GameTime gameTime)
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)

    KeyboardState ks = Keyboard.GetState();

    if (ks.IsKeyDown(Keys.Left))

    if (ks.IsKeyDown(Keys.Right))

    if (ks.IsKeyDown(Keys.Down))


// TODO: Add your update logic here base.Update(gameTime); }

As you can see, we need to implement the UpdateFogOfWar() method, so here it is:

private void UpdateFogOfWar()
    int playerMapX = (int)playerPosition.X / Tile.TileWidth;
    int playerMapY = (int)playerPosition.Y / Tile.TileHeight;

    for (int y = playerMapY - 1; y <= playerMapY + 1; y++)
        for (int x = playerMapX - 1; x <= playerMapX + 1; x++)
            if ((x >= 0) && (x < myMap.MapWidth) && (y >= 0) && (y < myMap.MapHeight))
                myMap.Rows[y].Columns[x].Explored = true;

The UpdateFogOfWar() method determines what map square the player's character currently resides in by simply dividing the X and Y position by the TileWidth and TileHeight values. After these are determined, we simply set up a small loop to run through the squares surrounding the player's current square. If the square falls inside the map (both X and Y are 0 or greater, and less than the map's width and height values) we set Explored to true.

Go ahead an launch the project. As you move the princess around on the screen, areas of the map that you move adjacent to will become uncovered and remain that way.

Creating a Light Aura Effect

We can adapt what we have already written for the Fog of War to create a halo of light centered on the player that moves as the player moves. Add the following image to the Textures folder of your Content project:

As usual, lets create a Texture2D to hold the image:

Texture2D lightAura;

and load it during the LoadContent() method:

lightAura = Content.Load<Texture2D>(@"Textures\lightaura");

In the Draw() method, add the following right after drawing the player's character:

    new Rectangle(
        (int)playerPosition.X - (lightAura.Width / 2), 
        (int)playerPosition.Y - (lightAura.Height / 2), 
        lightAura.Width, lightAura.Height),

Here, we draw the light aura centered on the player's position by subtracting half of the textures width and height from the X and Y coordinates for the destination rectangle. Go ahead and run your code... At first, it looks like everything is ok, but as you move further away from the starting position, a couple of problems become evident:

First, the aura doesn't match up well with the surrounding Fog of War masks, resulting in solid lines visible around the edges of the aura. Secondly, the aura leave explored cells visible in its wake. This is because currently, once a MapCell has been Explored it stays visible at all times. While our lightaura image is big enough to cover a few tiles with darkness, it doesn't cover the whole screen. To do the entire effect with a single image, we would need an image that was twice as high and twice as wide as our screen resolution (at least!) which wastes quite a bit of memory.

We can solve our problem by revising our UpdateFogOfWar() method:

private void UpdateFogOfWar()
    int playerMapX = (int)playerPosition.X / Tile.TileWidth;
    int playerMapY = (int)playerPosition.Y / Tile.TileHeight;

    for (int y = playerMapY - 3; y <= playerMapY + 3; y++)
        for (int x = playerMapX - 3; x <= playerMapX + 3; x++)
            if ((x >= 0) && (x < myMap.MapWidth) && (y >= 0) && (y < myMap.MapHeight))
                if ((x >= playerMapX - 2) && (y >= playerMapY - 2) &&
                    (x <= playerMapX + 2) && (y <= playerMapY + 2))
                    myMap.Rows[y].Columns[x].Explored = true;
                    myMap.Rows[y].Columns[x].Explored = false;

We have expanded our loop to cover an area of +/- 3 cells above, below, and around the cell the player is currently located in. Once we have checked to make sure that the cell we are interested in is actually part of the map, we do another check to see if the cell is within 2 squares of the player. If it is, we set Explored to true, just as before. If it isn't, we set Explored to false. This means that the "explored area" will move with the player. Since the player's speed is limited (they can never "skip" a cell in either direction) the outer loop will catch any cells the player moves out of range of and re-hide them.

We will also solve the sharp edges, because we have pushed the "explored area" out so that it will always be larger than the light aura surrounding the player, but smaller than the dark areas of the light aura image.

Go ahead and execute the project once more, and explore the map:

Thanks Sando for posting the initial question, and I hope folks find this mini-tutorial useful.


Site Contents Copyright 2006 Full Revolution, Inc. All rights reserved.
This site is in no way affiliated with Microsoft or any other company.
All logos and trademarks are copyright their respective companies.