Fog of war is commonly used in RTS games to alter gameplay and tactics. It allows players to hide their tactics and unit manouvres around the map, and in my opinion this makes for more interesting matches as a result. Although sometimes people like to disable it for more casual game modes such as skirmishes against the AI I definitely think it is a key feature for multiplayer. It also can add a certain mystique in single player as areas around the map are gradually revealed to the player. This article is going to discuss how I’ve implemented fog of war so far.
First of all, there are already lots of articles/posts that discuss fog of war and I did some research first. Here’s a list of some source I found useful:
This was a good read and introduces the basics. One of the things I did differently was to do a breath-first floodfill from the units location rather than trace a line (bresenham grid line?) from each unit like they discuss. It’s a good article worth a read as it covers everything from initial CPU fog grid computation to the rendering and mentions the idea of bitmasks to flag which players cells are visible to.
Eduard mentions doing a flood-fill from unit positions and using reference counts of fog cells. Using reference counted cells allows you to only update cells around units that have crossed a fog cell boundary (so only a subset of the moving units).
Riot Games – A Story of Fog and War
Talks about upscaling the fog texture to make it look better. I haven’t done this yet but might consider it for the future, once graphics become more of a priority.
GameDev stack exchange answer by Kromster
Brief but interesting answer that mentions dimming the fog texture. Another graphic polish idea that could be done in the future.
A fog grid is created to cover the entire map. The grid resolution can be lower than a single game unit, in my case I have opted for a cell size of 8 for now. For each player; entities that reveal fog of war are added to buckets for each cell they overlap (so a cell hash to entities multi-hashmap) each update. Their current cell is compared against their previous to determine if it’s changed, and if a units cell does change it is added to a list of moved units for that player. Initially, all units will have a previous cell that is invalid, and so all units will be added to the ‘moved’ units list. Note, that this means technically a static building could be added to the moved units list on the first update.
A list is stored for each unit containing all the previous cell indices it referenced. For each moved unit, all the cells in this list have their reference count decremented by one. The units previous cell list is then cleared before a flood fill is done from the units new position. The flood fill spreads out around the unit up to it’s maximum range or until it enters a cell who’s height is above the units vision height. Each cell visited by the flood fill has it’s reference count incremented by one.
An array of the previous grid reference counts is compared to the new reference counts to set another array of changed cells. For each changed cell the units are retrieved via a cell hash to unit entities map and those units have a FogCull component set to culled or visible depending on the updated cell value.
Once static obstacles are revealed they have their FogCull component removed and also removed from the internal data structures in the FogSystem as they stay permanently revealed in the discovered fog of war. Only dynamic moving units are re-hidden in the fog.
Finally, the fog texture is updated for the local players grid. Each pixel in the texture has it’s alpha set to be fully clear if the corresponding cell reference count is >0. If the reference count is zero, then the new alpha is set to semi-transparent if it was previously semi or fully transparent, or fully opaque if it was previously fully opaque. This keeps revealed parts of the map partially visible when units no longer have vision in an area.
If there are no new units or units that have moved to a new fog cell then no extra fog-of-war processing is done. Most updates require no processing. When a unit does cross into a different fog cell, only the fog cells within vision range around the unit are changed. This keeps everything reasonably efficient.