Advanced Fog of War System

Discussion in 'Advanced Programming Discussion' started by Fun Looking Games, Jan 22, 2018.

Tags:
  1. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Hello fellow programmers! I am sure many of you are familliar with fog-of-wars, either you played a game that uses them or saw it in a video or friend playing. In case any of you are not familliar with the system by name I'll leave a screenshot of it being used in the famous game Age of Empires:

    [​IMG]

    As you can see the elephant have a line of sight, this line of sight works for every building and units in the game, this line not only reveals enemy units and buildings that hasn't been explored, but also reveals black areas of the map (areas that none of your units have been yet).

    This system is very useful for any strategy game, specially in multiplayer focused strategy games, as it gives every player in the match opportunities to be sneaky and come up with surprise strategies (it wouldnt be much of a strategy if your enemy always knows what you are doing).

    I am making an RTS where needs this system to work, I've searched many tutorials on youtube and google. Found some that helped me a bit and came up with something that looks like this:

    [​IMG]

    [​IMG]
    Note: This a very early build of the game, something like version 0.01 much of it are place holders.

    As you can see, although what I have is pretty buggy already, I already got the black-fog to be working, what I can't bring it work is the line of sight fog, the one that fades the rest of the world that isnt being seen already by any unit. Here is my code:

    Code:
    Information about object: obj_blackfog_controller
    Sprite:
    Solid: false
    Visible: true
    Depth: 9999
    Persistent: true
    Parent:
    Children:
    Mask:
    
    No Physics Object
    Create Event:
    
    execute code:
    
    res = 5; //resolution
    sfog = surface_create(room_width/res, room_height/res);
    
    surface_set_target(sfog);
    draw_clear(c_black);
    surface_reset_target();
    
    Draw Event:
    
    execute code:
    
    draw_surface_ext(sfog, 0, 0, res, res, 0, 0, 1);
    
    
    
    This is the black-fog code.

    Code:
    Information about object: obj_fog_controller
    Sprite:
    Solid: false
    Visible: true
    Depth: 9999
    Persistent: true
    Parent:
    Children:
    Mask:
    
    No Physics Object
    Create Event:
    
    execute code:
    
    res = 5; //resolution
    sfog = surface_create(room_width/res, room_height/res);
    
    surface_set_target(sfog);
    draw_clear(c_black);
    surface_reset_target();
    
    Step Event:
    
    execute code:
    
    sfog = surface_create(room_width/res, room_height/res);
    
    surface_set_target(sfog);
    draw_clear(c_black);
    draw_set_alpha(.5);
    surface_reset_target();
    
    Draw Event:
    
    execute code:
    
    draw_surface_ext(sfog, 0, 0, res, res, 0, 0, .5);
    
    
    
    This is the secondary fog code, which for some reason just refuses to exist.

    Code:
    surface_set_target(obj_blackfog_controller.sfog);
        draw_set_blend_mode(bm_src_color);
            draw_sprite_ext(spr_fogrevealer, 0, x/obj_blackfog_controller.res, y/obj_blackfog_controller.res, 0.8, 0.8, 0, 0, 1);
        draw_set_blend_mode(bm_normal);
    surface_reset_target();
    
    surface_set_target(obj_fog_controller.sfog);
        draw_set_blend_mode(bm_src_color);
            draw_sprite_ext(spr_fogrevealer, 0, x/obj_fog_controller.res, y/obj_fog_controller.res, 0.8, 0.8, 0, 0, .5);
        draw_set_blend_mode(bm_normal);
    surface_reset_target();
    
    And this, finally, is the fog revealler code, this makes the fog be revealed based on a sprite that is basically a white circle with 'glow effected' border.

    ______________________________________________________________________________________

    The final effect should look something like this (Starcraft series):
    [​IMG]
    I know that grid based fog of war would be easier to make (conceptually), but an grid based system wouldn't work for what I'm aiming.
    _____________________________________________________________________________________

    Also searched for some algorythms on fog of war systems but none of them helped me. I'm sure that the solution to this would help not only me but many developers who wants to implement this system!

    Thank you in advance and have an awesome week!

    Spark, FunLookingGames.
     
    Last edited: Jan 22, 2018
  2. Tthecreator

    Tthecreator Your Creator!

    Joined:
    Jun 20, 2016
    Posts:
    725
    first of all, all draw code should always be in the draw event. Even clearing a surface is considered drawing something. Thus your code
    Code:
    
    sfog = surface_create(room_width/res, room_height/res);
    surface_set_target(sfog);
    draw_clear(c_black);
    draw_set_alpha(.5);
    surface_reset_target();
    
    Then why is this in the step event?
    You are literally recreating this surface every step without destroying the old one. BIG MEMORY LEAK.

    Also your images are broken, please fix them to give us a better idea of what code we are looking at.

    Your approach seems okay I guess. What happens when you disable the black fog, but leave the other fog?
     
    Fun Looking Games likes this.
  3. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Yes, the memory leak got obvious after a couple of runs my computer literally froze completely. Fixed the images.

    I was recreating this surface every step because I thought this was the way to go, since the normal fog would be updated every frame of the game rather than the black fog always being the way it was explored last.

    Once you remove the black fog it would only be the normal fog the map would be visible but wouldnt be updated until you re-explore it, let me show you an example:
    [​IMG]
    Check the minimap, you can see that there's no black-fog. But the normal fog is still there showing what your units can see, an enemy unit or building will be shown as your units approach them, updating the map.
     

    Attached Files:

  4. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Attached the images to the reply. ^^
     
  5. Tthecreator

    Tthecreator Your Creator!

    Joined:
    Jun 20, 2016
    Posts:
    725
    I meant what happens in your game when you only enable the normal fog.
    Like as a debugging step. Is that just an empty surface, or like the same surface but slightly transparent?
    You can also check the state of the surface in the debugger. Just right click on an empty pane and select "surfaces/textures" (or something like that). Then find the right surface in the dropdown menu.
     
    Fun Looking Games likes this.
  6. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    fog 3.png

    Here it is, now its working and its not having memory leak problems but, as you can see once an unit gets close to the other instead of merging the fog revealers, it adds one on top of another, is there a way set a limit to the alpha or something like it?

    Also, when I restart the room or the game the surfaces don't restart, they delete themselves causing them to not exist again. Why does that happen? Is that a problem at all? Because when the game gets a menu it will load this room separatedly and not restart directly on it which might fix the problem. Is there anyway to fix it?
     
  7. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Never mind I'm dumb, just set the fog revealer's alpha to 1 and now they merge eachother, but, still, is there anyway to setup a max value for the alpha?

    Also, I tested it, the black-fog disappears even when changing rooms rather than restarting them. That sucks.

    Appearently this system is still slowing performance on rooms larger and taller than 10.000x10.000.
     
    Last edited: Jan 23, 2018
  8. Tthecreator

    Tthecreator Your Creator!

    Joined:
    Jun 20, 2016
    Posts:
    725
    If you want a max alpha, the easiest would be to just draw the resulting surface with less alpha. (just use draw_set_alpha() before calling draw_surface()).
    Another option would be to use a shader for it if you really want it to have less alpha on the surface itself.
     
    Fun Looking Games likes this.
  9. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,832
    draw_set_blend_mode(bm_src_color);
    that function only accepts bm_normal, bm_add and bm_subract...

    the proper way to do this is to have a sprite that is fully white in the center that fades to black on the edges.

    you create and hold on to a surface that is cleared with alpha 1 c_black
    with draw_clear_alpha

    and you draw that sprite on the surface with bm_subtract ... the resulting pixel math with punch holes in the surface with smooth edges.

    if you are using views, you will need to have your surface the size of a the view in room... and that is a problem because returning to a visited location you need to re-punch the holes for the fog in the view.

    the most efficient way to maintain were holes were punched is to have a ds grid with cells for the entire room that would split the room in 32x32 or 64x64 grid cell size regions.... when a unit goes off, you would flag the cell he's on. so that later you can loop through the grid cells in view and punch holes in the surface where the cell has been flagged. this is also needed when you use a surface as it may be obliterated and needing to be recreated again, check the forum and see why you have to constantly call surface_exists everywhere...

    upload_2018-1-23_18-23-12.png
     
  10. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    I'll have a look into it, my friend and I thought of gridding the entire room but to fill each grid with a surface of its own, but I guess that would lag even more the performance wouldn't it?

    Gonna try to do it your way and see how it will end up.

    So basically I will grid the map and make every cell of the grid record when a unit have gone through it and make the surface refresh itself accordingly to those coordinates and size of the unit field of view.
     
  11. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,832
    you can also store the scale of the vision field in the cell. for example if a grunt walks on the cell and his vision is not very good, you can store the sprite scaling in there of .5
    if a scout explored the map, he can replace the .5 value with a 2 for example....

    cel value = max(cell value, unit vision)

    You may have noticed some games where the unit advance discovering the map, but the FOW is exposed by chunks because he is on the same cell for a while as he walks

    the grid is only needed if the FOW is persistent. some games reset to black when the unit dies. so you don't need that grid as you can clear the fog using the unit's actual position of whatever unit is on screen.

    you can combine the grid method with the per unit coord method for smooth FOG revealing...
     
    Fun Looking Games likes this.
  12. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    "the grid is only needed if the FOW is persistent. some games reset to black when the unit dies. so you don't need that grid as you can clear the fog using the unit's actual position of whatever unit is on screen."

    Do you have some examples so I can imagine it better?

    "you can combine the grid method with the per unit coord method for smooth FOG revealing..."

    How can I do that? I thought that the more numbers the grid have, smoother the FOG reveal would be.
     
  13. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,832
    >Do you have some examples so I can imagine it better?
    Not really. imaging some guy with a lamp walking in the dark and it goes off... this sort of game require units and buildings in key regions for the terrain to be visible. if the unit dies or the building is destroyed the fog returns.

    >I thought that the more numbers the grid have, smoother the FOG reveal would be.
    Not really what I intended to convey.... the grid cell can hold a hole punch sprite the size in pixel a cell would take on the level but that punch sprite can go well beyond the size of the cell... for example.

    upload_2018-1-23_23-16-27.png
     

    Attached Files:

    Fun Looking Games likes this.
  14. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Hold on, you lost me on the last part. Can you clear it up for me please?
     
  15. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,832
    a unit is walking, it has a visual range of 1.

    let's say a visual range of 1 is a hole punching sprite size 32x32 well maybe a little more to cover the corners...

    let's say you setup a grid that divides the level in 32x32 cells

    when the unit walks from x,y 16,16 to 48,16, from center of cell 0,0 to center of cell 0,1 he sets that cell value to 1

    this would be the result
    upload_2018-1-23_23-35-23.png


    now let's say the scout has a range of 2.... he would set the grid cells to larger values, that would make large punch holes in the surface

    upload_2018-1-23_23-39-37.png

    That there is how your persistent grid based FOW would look like. the code would loop though the grid cells and punch holes on the surface mapping the col and row to x,y positions, drawing the punch hole according to the value in the cell.

    you can supplement the system as I mentioned prior, by also adding a punch hole using the unit' actual coordinate... you see he just setup that last cell and he is walking to the next one... buth he has not stepped on to that cell yet... so by using his actual position yu can punch a temporary hole where he is...
    upload_2018-1-23_23-47-25.png
     
  16. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    This is so interesting, thank you for the pacience in explaining, now i just need to code on.
     
  17. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Just let me ask one more thing, is there a way to deactivate all surfaces working outside of the view, and maybe make all instances outside of it invisible so it would optimize the game a bit some more? Just in case when in later builds it starts lagging.
     
  18. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,832
    Deactivate surfaces outside the view? I dont follow. you should have one surface for the view for the FOW

    as for deactivating instances. I'm not a fan.

    and turning things invisible if outside the view is not required if the object does not have a draw event, GM does it for you
     
  19. Bingdom

    Bingdom Googledom

    Joined:
    Jul 1, 2016
    Posts:
    1,675

    So you'd want a chunking system?
    There are a few tutorials out there that should help you on that.
    Examples: [1] [2]
    Once you get the basic principles of it, it should be really easy to draw only chunks that are in view. Just keep in mind that surfaces will have to be active at all times since you'd want un-rendered surfaces still to update on fog-of-war.

    Anyways, I'll tell you my approach to this. I haven't read everything on here but I think this might raise some new ideas.

    In my approach, I will be naming things like this:
    [​IMG]

    I created 2 surfaces, one for the fog of war (fow) and the other for the black mask (bm).
    The fow will be constantly filled with a black colour with a certain amount of alpha (e.g 0.5)

    The bm will be completely black. This would be the area the player hasn't explored.
    Before you draw the revealed area to the fow, fill it with a black colour with 0.5 alpha (in this case). That way, the area around the units will always be "revealed area" and not get overwritten/not updated.

    Result:
    upload_2018-1-24_21-59-16.png
    Note: Art not by me. The result is slightly different than code described.
     
    Fun Looking Games likes this.
  20. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Thanks for the reply! I already came up with this sort of solution and found out that this is not the 'most' correct way to do it, since in smaller rooms and smaller games it should be completely fine, but when you increase the room and game size however it starts lagging because the fow and bm surfaces gets so unbelievably big that the computer have problems working with them.

    icuurd12b42 came up with an much better option which is to divide the room by a 32x32 grid and fill each grid cell with a value of -1 when unexplored and, when explored, update the cell with the value of the unit sight which can be any value from 0 to infinite. Then have a surface that will follow the view all the time that will be constantly updating according to the grid values.
     
  21. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    icuurd12b42, I created the grid system that grid any room size with cells of 32x32, gave all of the cell values to -1 and when a unit walks over it, it updates. Now that all the back engine is working I just need to draw surface that will be following the view and updating it. However I have no clue where to start, can you help me with that?
     
  22. Fun Looking Games

    Fun Looking Games Member

    Joined:
    Sep 13, 2017
    Posts:
    18
    Here it is with just the back engine working.
    fog 4.png
     
  23. Juju

    Juju Member

    Joined:
    Jun 20, 2016
    Posts:
    400
    Quad tree + a metaballs-like shader would probably give you higher quality on redraw.
     
  24. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,832
    for using view, you loop though the grid from the column and row that would be at the upper left of the view to the column and row that would be to the lower right... and draw you sprite on the surface

    pcode be like...
    colstart = XPosToGridPos(view_xview)
    rowstart = YPosToGridPos(view_yview)

    colend = XPosToGridPos(view_xview + view_wview)
    rowend = YPosToGridPos(view_yview + view_hview)

    for(col = colstart; col<colend; col++)
    for(row = rowstart; row<rowend; row++)
    {
    if(ds_grid at col, row has a value)
    {
    draw sprite at col*colwwidthinpixels - view_xview,row * rowwidthinpixels - view_yview on the surface
    }
    }

    Basically that should get you going, expect a few tweak to be done...
     
  25. Ampersand

    Ampersand Member

    Joined:
    Jan 30, 2017
    Posts:
    216
    Well, I started to write up a post about it whilst attempting to cut pieces of my FOW system out of my current project, when I realized it would be easier to just pull it out and write it in a more modular format with less hard numbers for cell sizes and such. So far I have field of view, fog trail permanence, and a 2 team set up that can be expanded to more teams and also be used for game loop purposes (AI vision checks and the likes). I'll edit this post once it's finished with the code as well as an example project and .exe for you to check out. I'm cleaning things up and commenting the code now, should have it ready in a bit ;)

    Judging by what I see so far, you have the right idea. But a combination of ds_grids and smaller, scaled up surfaces will perform better and require less (as in no) pixel checks from the surfaces for game mechanic reasons. I'll finish it up as soon as I can!

    Edit:
    So here is what I have so far, as soon as I figure out a simple fast way to "raycast" across the grid I will release the source. Right now the method by which I'm illuminating things is not user-friendly or very adaptable. Likely some implementation of Bresenham's line algorithm will be what I settle on.

    Features multiple team vision, hiding units, and a bit of a clever buffer for the tiny little FOW surface using two guassian shaders to smooth it out into something nice and organic. It also works with any size view and any size cell. It could also be changed to a "hole punch" style fog, but would require larger surfaces to be drawn every frame (no good)

    FOW Demo

    In the end with hole punch styles you will be using a grid anyways. Best to keep your grid numbers easy to work with and work with the graphics rather than the numbers to make it organic. Any method without a grid to store values would require storing at least one map-sized surface that won't be any fun to work with graphically or logically.

    And a video for those who wish not to download:


    It would be quite easy to copy a Warcraft 3 / Age of Empires style as well by using the grids to parse it like an auto-tiler and draw pixel-art fog. Would likely be a little more intensive graphically, but if you keep the scale small and use shaders to smooth it out a bit it would look really great.

    Edit 2/2:
    I've about finished the grid casting/tracing. Just have to fix some edge cases. Ended up with a supercover type system based loosely on Bresenham's line algorithm. Might be able to finish it up tomorrow. As with everything I make, it was made compatible with YYC from the start. Unfortunately I've made it in 1.4, so hopefully there's nothing deprecated in it (although there ought not be).
     
    Last edited: Feb 2, 2018
    abianche likes this.
  26. Ampersand

    Ampersand Member

    Joined:
    Jan 30, 2017
    Posts:
    216


    The fog looks much better in the actual demo, between me not knowing how to use OBS and Vimeo's compression the video ended up with a lot of weird artifacts and shapes. Anyone else notice that OBS captures blend_mode stuff kind of funky with the Game Capture?

    Source and Demo on my GitHub

    I haven't tested extensively with other views and cell sizes, but they seem to work as far as I have tested. You'll have to think on your game scale and how you plan on handling collisions before you decide on a cell size, but the only code that should need changed (if you don't want to just make your wall pieces the size of the cells) is where the blocks toggle the lyte_grid in their position.

    I will be further cleaning up this code and commenting it for those interested, as well as fleshing out the features a bit more. But for that I will likely start a new topic in the Tutorials section, as I don't want to hijack this thread.

    Edit: There's a memory leak, at the end of fow_cast_lyte it is clearing an array not deleting the priority queue. I'll fix it and push it on the Git tomorrow

    Quadruple ninja edit: memory leak fixed and pushed to GitHub
     
    Last edited: Feb 5, 2018

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice