Unity 2D Fog of War

When I started down my journey of making a roguelike in Unity I knew fog of war was a feature I wanted. I’ve always liked the ambiance of the unknown, going back to when Diablo was released. The exploration the player has to do and the unknown of what they’ll uncover I feel adds a lot of value to the game.

So I implemented this feature three times and I wasn’t really happy with it until now. I’ll explain the ways it was implemented and why I think the last version is ideal (at least for my project).

My ideal fog of war implementation looks like this:

Full transparency in the spaces adjacent to the player, and 50% transparency to the outer edges. This gives the illusion of what the player can see near them and what they can barely see; and then obviously the unknown. My game is using procedural dungeon generation as with most roguelikes. If you’re unfamiliar with this type of implementation, the dungeon regenerates itself on each play through or level change. This minimizes the amount of level design needed.

Here’s a sample of my level being generated.

So applying a fog of war implementation on this map can get tricky as you have individual tile locations on a X,Y Cartesian grid.

Attempt Number 1 (holy fuck was this idea stupid)

I think Benjamin Franklin had a quote about failing and learning, that’s how I felt about this process. I now know how NOT to implement fog of war.

So I have my grid of tiles after dungeon generation has happened, I had the brilliant idea that I would just Instantiate a new tile above all the existing tiles and set it color to black. Then I would use a circle collider on the player and when the collider had a resulting collision with one of these black tiles, it would set their transparency value to either full or half depending on distance from the player transform.

The implementation worked…kind of.

While I was pleased to see my handy code craftsmanship work right off the bat. I soon realized what I had done. I had effectively doubled my tile count, put colliders on all those objects. My FPS in debug mode dropped nearly 60%.

Here’s a brief video of it in action.

While it seems playable at first, you have to understand I have no sounds, very few enemies and AI implemented, no collectibles. It’s a shell of a game at this point and losing 60% of my frames was alarming to say the least.

Attempt 2: (Slightly better)

On my second attempt I took a large black sprite and covered the entire area where the map could spawn. I then used a sprite mask on an image the same shape that was desired and with some fiddling with culling settings and mask interactions I was able to achieve the same result as the video above. However, I didn’t get a 60% drop in frames. Infact, it hardly had any impact. I still wasn’t happy with the result, I wanted the areas the player had visited to be 50% transparent after they left. The player would know where they had visited and I could avoid a mini-map implementation

Attempt 3: (my implementation)

After eight hours of sleep and a hearty breakfast I got to work on another implementation. I’ll preface this by saying, sometimes you just need to walk away from your code. Its not until you give your brain a break that you can fully realize potential mistakes and come through with a solution.

It dawned on me that I’m already using sprite renderers on all my tiles. This means I could set this color and transparency without instantiating any other objects.

So on my dungeon generation class I have this code:

// Sets a member variable color to black
private Color DarkTileColor = new Color(0.0f, 0.0f, 0.0f, 1.0f);
//After each Instantiation of a tile I call this
created_tile.GetComponent<SpriteRenderer>().color = DarkTileColor;

So now I have a map of tiles that is entirely black

So this is where the magic comes in. I use a Unity physics2D object called “CircleCastAll”. This object returns all colliders it comes in contact with for a specified radius and distance around an origin point. What this means is, I put a circle around my player and anything the circle touches, it tells me about.

public class CheckFog : MonoBehaviour
{
    RaycastHit2D[] hit;
    int frames = 0;
    void FixedUpdate()
    {
        frames++;
        if (frames == 4)
        {
            frames = 0;


            hit = Physics2D.CircleCastAll(transform.position, 2f, new Vector2(0, 0), 3f);

            foreach (var item in hit)
            {
                Vector3 dis = transform.position - item.collider.transform.position;
                if (dis.x == 2 || dis.x == -2 || dis.y == 2 || dis.y == -2)
                {
                    item.collider.transform.gameObject.GetComponent<SpriteRenderer>().color = new Color(.5f, .5f, .5f, 1.0f);
                }
                else
                {
                    item.collider.transform.gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1.0f);

                }
            }
        }
    }
}

So after I get an array of colliders. I iterate over them with a foreach loop and then I check the distance from my player to the collider and if the x or y value meets the criteria of 2 or -2, it tells me its an outer tile and we’ll set the transparency to half. Otherwise we’ll set the transparency to full as the other tiles left are the ones adjacent to me. This implementation also has the added benefit of adding a mask to monsters and it gives a nice silouhette effect when the monster is “out of range”. Here’s what it looks like:

In the code above I’m also only checking colliders every fourth frame. Checking every frame in a turn based game seemed like a waste of resources and I could barely tell a difference.

Happy coding

-Diaonic

Leave a Reply