Fog of War in Astro Force (RTS)

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! :slight_smile:

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)

44 Likes

Finally an actual RTS in Roblox.

TC3 is good, but it’s not RTS enough for me.

9 Likes

This is very cool man, i have played this and it is the best RTS game on roblox i have ever seen. Especially I like that this heavily reminds me of Starcraft 2.

However i think it should be posted on #help-and-feedback:cool-creations.

4 Likes

@ Performance Impact of Point Lights

I’ve got an idea for your Fog Of War implementation, and you wouldn’t even need to use PointLight’s but you’d have a very similar effect :slight_smile:

So basically, we want a smoothly bordered/gradient edge effect that gives the impression that what is outside the view is darker or shaded.

Instead of lighting the 3D space, I bet a nearly-as-good solution could be made using transparent images on the 2D GUI. The only lacking feature is the shadows cast by the FoW point light, which you probably have disabled anyway per performance.

It seems like you already have a complex grid set up to represent your maps, unit positions, and what “cells” are visible or not visible, so it should be very straightforward to translate that 3d cell position to the 2d position on the user’s GUI.

I’m sure you could optimize the 3d->2d mapping, given some initial inputs like user screen size, pan/zoom location, and your custom cell grid, but here’s the brute force way: Camera:WorldToScreenPoint

Then, once you know what you want to “draw” for a given 2d square of GUI-space, you just do a bit of optimization to chunk together spaces of say:

  • A uniform .5 transparency black image (in red)
  • A linear gradient edge piece (yellow)
  • And simple case, a 45 degree gradient piece (green)

Of course, you could do some extra math to add in a 30 deg or more refined edge pieces to give smoother curves and all that.

If it were me I’d just grab a circular radial gradient and fill up the edges with that for a “smooth” look, but you seem to be very adept at mathematical grid geometries :+1:

Here’s a rough example without the nice pretty gradient edges
https://i.gyazo.com/1d6dce3f201b855150ee7356ed1dd2c7.mp4

samples of what I mean by mask/gradient textures
Radial


Diagonal

Linear

Uniform

6 Likes

The fog of war is looking awesome, nice work on that.
CUBE GANG!

4 Likes

cube gang
I also agree on the FoW looking great and stuff (put this in cause of 30 “key” minimum)

1 Like

Need something similar for my RTS named Dunkeep.
But It will be only undiscovered fog of war. Not sure how it is actually named.
Everything is black, but area you have discovered is not. And everything in there is always visible.
Perhaps I can use same system @Atrazine described.
But I had another thought, and perhaps @Atrazine you can give some advice.
My thought is, that there will be once big black block, that we start to subtract parts of cylinders, using SubtractAsync.
I am most worried about the looks, the edges will sharp - would like gradiented/foggy edges.
And the second thing is performance. I know I can reduce the parts I will cut out, as this game is also grid-based, and will cut out discovered grid only.

Tried out this reveal of the undiscovered area. Visually it would actually work, If material is black neon.
But there are a couple of issues.
Jul-17-2022 13-22-34low

  1. SubtractAsync can only be done on the server side. So if there are multiple teams, then each team will have its own union part that we will subtract parts from. We will hide those on server side and for each team, it will be shown locally. Locally we watch for that union part changes and show it again. Because after part changes original properties are replicated in the local environment. If the game lags then in a brief moment you can see all of the map. It would help if we could do SubtractAsync in the local environment.
  2. SubtractAsync gets really slow after cutting a bunch of parts. And it does not matter if we are cutting cylinder or rectangular parts from union part. It still gets slow. Even with CSG v3. Don’t think this will improve.

Thinking of building a parts system, that will remove parts if that area is discovered.

If only I was this skilled… Great work it’s a tragedy there aren’t more players.

hey, can you explain how to mask this(gyazoo file) with radial gradient?