Video showing progress on a fog of war system for @loravocado and I’s real-time strategy (RTS) game, Astro Force! (Note: fog of war is still WIP and is not in the game yet. It will be coming soon!)
How it works
Here, we’ll provide a high-level overview of how our fog of war system works!
First, we set up a grid on the serverside and the clientside. This grid contains 8x8 stud cells that cover the entire map. The grid is structured the same way on both the server and client, allowing for easier replication.
Serverside
On the grid, we set up cells to store a state for each team in the game. This state determines whether that team can currently see that cell. Additionally, for each cell in the grid, we pre-compute all valid neighboring cells the cell can see up to a maximum range using a breadth-first search. We cache these valid neighboring cells so that when a unit steps into the cell, we can quickly determine which other cells that unit can see.
For the precomputation: in order to determine if a cell is visible to another cell, we do a breadth-first search and check if the expansion (of the breadth-first search) gets blocked. Expansion can be blocked by a corner or by going onto higher ground. If the expansion doesn’t get blocked, the expansion going out in that direction will be visible to the original cell. This is all done at the beginning of the game when we are loading in the map.
Now that we have all the valid neighbors for each cell precomputed, when a unit moves into a cell, we can quickly iterate through all the cells until a cell is outside a unit’s vision range (at which point we stop iterating). For the cells we iterated over, we increment a reference count in the aforementioned state for that unit’s team in each of the cells. If a cell’s reference count is greater than one for that unit’s team, it means that unit’s team can currently see that cell (we can then replicate this information to the client). If a cell’s reference count is 0, then that means that unit’s team cannot current see that cell (and again, we can replicate this information to the client).
Clientside
For the clientside, it is mostly visual based. In each of the cells in the grid, we generate a pointlight. If the pointlight is on, the area will be lit up, communicating to the player that they can currently see that area. We darken the overall environment as well using a ColorCorrection effect to prevent the scene from being overexposed (credit to @Cyber_Star00 for the ColorCorrection suggestion!).
To smooth out the transitions of the pointlights turning on and off when cells become visible or hidden, we gradually increase/decrease the brightness over a given interval. This helps give the illusion that the fog is moving smoothly with your units, even though the pointlights are conformed to an 8x8 grid.
Performance impact and limitations
Unfortunately, using so many pointlights can cause a bit of a performance hit. When we tested the worst-case scenario (where every single pointlight on the map was turned on), FPS can drop anywhere from 20-30% overall. However, this was the best way we have found so far to do fog of war on Roblox.
Another concern we currently have is lights unrendering when the camera is too far away. When a user moves their camera from one end of the map to another end of the map, the pointlights at the far end of the map will unrender. When the user moves the camera back to the other end of the map, it can take up to a few seconds for the pointlights to re-render, causing a jarring effect for the player. We found we could mitigate this by scaling everything in the game down 50%, but we have not committed to doing this yet while we search for a better solution to this limitation. Ideally, we would like to have more control over how lights render/unrender to prevent issues like this.
Conclusion
Thanks for reading! If you’re interested in supporting Astro Force in it’s development, pre-alpha is available to purchase now for 300 Robux here.
Also, check out our other article for some crazy bandwidth optimization tricks! How we reduced bandwidth usage by 60x in Astro Force (Roblox RTS)