Fog of war

Advanced game related topics
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Fog of war

Post by Joubarbe »

Hello,

I'd like to implement a fog of war on a big sprite (hand drawn map) that I clipped to the screen size.

The idea would be to do that :

Image
without fog of war


Image
with fog of war


But I have no idea how to do that. Note that it's not a light: when the player moves, the revealed hallway should stay visible.
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Fog of war

Post by #NULL »

you could draw multiple semitransparent black spites (or with a radiant alpha gradient) all over the scene. maybe in a tiled manner or also possibly overlapping. then you store if the player has entered the acording area and simply skip the fog drawing for that region. but there could be limits to the performance of this due to many sprites displayed but there are also ways to reduce this problem.
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Fog of war

Post by #NULL »

actually you could also overdraw your scene with one black opaque sprite and draw some transparency to regions of that sprite while the player is moving, so your fog sprite gets more and more transparent in the known regions while playing.
User avatar
Bisonte
Addict
Addict
Posts: 1226
Joined: Tue Oct 09, 2007 2:15 am

Re: Fog of war

Post by Bisonte »

#NULL wrote:actually you could also overdraw your scene with one black opaque sprite and draw some transparency to regions of that sprite while the player is moving, so your fog sprite gets more and more transparent in the known regions while playing.
Nice idea ! Image
No need of complicated mathematics ;)
PureBasic 6.04 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
English is not my native language... (I often use DeepL to translate my texts.)
Joubarbe
Enthusiast
Enthusiast
Posts: 555
Joined: Wed Sep 18, 2013 11:54 am
Location: France

Re: Fog of war

Post by Joubarbe »

That was my thought too, but I can think of two problems :

1/ Performances : I'd have to redraw in real time everytime the player moves into an unrevealed territory.
2/ The hard transition between revealed an non-revealed portions of the map would probably look a bit strange.

Anyway, I want to avoid complicated math :)
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Fog of war

Post by #NULL »

i would actually try a mix of those two, something like updating the drawing every some frames/time according to the current player position and only if the player moved since last time. only as often enough as to have the exploration somewhat smooth. and i would use a 'static' image as an alpha mask to be drawn on the sprite at those times.
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Fog of war

Post by Demivec »

Here's an example using the method of overdrawing the map with a completely separate sprite containing 'the fog':

Code: Select all

;Author: Demivec
;Created: 5/17/2016
;Updated: 5/18/2016 (minor adjustments and a new feature added)
;OS: Tested with Windows
;Compiler: PureBasic v5.41 x64
;Description: A sample test of showing the 'Fog of War' with sprites.

EnableExplicit

Enumeration
  #map_spr
  #fog_spr
  #mouse_spr
EndEnumeration

#fogClearWidth = 80
#fogClearHeight = #fogClearWidth

#mapWidth = 700
#mapHeight = 300

#mouseWidth = 15
#mouseHeight = #mouseWidth

Declare customFogClear(x, y)
Declare customFogSet(tlx, tly, brx, bry)

InitSprite()
InitKeyboard()

OpenWindow(0, 0, 0, #mapWidth, #mapHeight, "Fog of War Test (use arrows to move around, 'R' resets fog)", #PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, #mapWidth, #mapHeight)

Define i, x, y, fogClear_img

;create sprites
CreateSprite(#map_spr, #mapWidth, #mapHeight)
StartDrawing(SpriteOutput(#map_spr))
  ;make random map
  For i = 1 To 1000
    If Random(1): DrawingMode(#PB_2DDrawing_Outlined): Else: DrawingMode(#PB_2DDrawing_Default): EndIf
    Box(Random(#mapWidth), Random(#mapHeight), Random(100), Random(100), RGB(Random(200, 40), Random(200, 40), Random(200, 40)))
  Next
StopDrawing()
  
CreateSprite(#mouse_spr, #mouseWidth, #mouseHeight)
StartDrawing(SpriteOutput(#mouse_spr))
  DrawingMode(#PB_2DDrawing_Outlined)
  Circle(#mouseWidth / 2, #mouseHeight / 2, #mouseHeight / 2 - 1, RGB(255, 255, 255))
  Circle(#mouseWidth / 2, #mouseHeight / 2, #mouseHeight / 2 - 2, RGB(0, 0, 1))
  Circle(#mouseWidth / 2, #mouseHeight / 2, #mouseHeight / 2    , RGB(0, 0, 1))
  Line(0, 0, #mouseWidth, #mouseHeight, RGB(255, 0, 0))
  Line(#mouseWidth - 1, 0, -#mouseWidth, #mouseHeight, RGB(255, 0, 0))
StopDrawing()
  
CreateSprite(#fog_spr, #mapWidth, #mapHeight, #PB_Sprite_AlphaBlending)
StartDrawing(SpriteOutput(#fog_spr))
  Box(0, 0, OutputWidth(), OutputHeight(), RGB(0, 0, 200))
  customFogSet(0, 0, #mapWidth - 1, #mapHeight - 1)
StopDrawing()

Dim fogClear(#fogClearWidth - 1, #fogClearHeight - 1) ;stores alpha adjustment values (towards transparency)
;use an image with gradient values to generate our values for us
fogClear_img = CreateImage(#PB_Any, #fogClearWidth, #fogClearHeight, 32)
StartDrawing(ImageOutput(fogClear_img))
  DrawingMode(#PB_2DDrawing_Gradient)
  
  BackColor(RGB(64, 64, 64))
  GradientColor(0.60, RGB(20, 20, 20))
  GradientColor(0.9, RGB(4, 4, 4))
  FrontColor(RGB(0, 0, 0)) ;max level of alpha adjustment, it should be low if it will overlap when drawn
  
  ;An example is provided for both rectangular and circular shape for fog clearing.
  ;BoxedGradient(0, 0, #fogClearWidth, #fogClearHeight)
  ;Box(0, 0,  #fogClearWidth, #fogClearHeight)
  CircularGradient(#fogClearWidth / 2, #fogClearHeight / 2, #fogClearWidth / 2)
  Circle(#fogClearWidth / 2, #fogClearHeight / 2, #fogClearWidth / 2)
  
  For x = 0 To #fogClearWidth - 1
    For y = 0 To #fogClearHeight - 1
      fogClear(x, y) = Red(Point(x, y))
    Next
  Next
StopDrawing()
FreeImage(fogClear_img) ;don't need the work image anymore  


x = 0 ;x = #mapWidth / 2
y = #mapHeight / 2

For i = 1 To 20: customFogClear(x, y): Next ;repeatedly draw the initial position to 'clear' the fog completely

Define event, quit, updateFog
Repeat
  ClearScreen(0)
  Repeat
    event = WindowEvent()
    If event = #PB_Event_CloseWindow
      quit = 1
    EndIf
  Until event = 0
  
  updateFog = #False
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Left) And x > 1: x - 3: updateFog = #True: EndIf
  If KeyboardPushed(#PB_Key_Right) And x < #mapWidth: x + 3: updateFog = #True: EndIf
  If KeyboardPushed(#PB_Key_Up) And y > 1: y - 3: updateFog = #True: EndIf
  If KeyboardPushed(#PB_Key_Down) And y < #mapHeight: y + 3: updateFog = #True: EndIf
  If KeyboardPushed(#PB_Key_R)
    StartDrawing(SpriteOutput(#fog_spr))
      customFogSet(0, 0, #mapWidth - 1, #mapHeight - 1)
    StopDrawing()
    For i = 1 To 20: customFogClear(x, y): Next
  EndIf
  
  If updateFog
    customFogClear(x, y)
  EndIf
  
  DisplaySprite(#map_spr, 0, 0)
  DisplayTransparentSprite(#fog_spr, 0, 0) ;The fog level can also be tinted and have its intensity set.
  DisplayTransparentSprite(#mouse_spr, x - #mouseWidth / 2, y - #mouseHeight / 2, 125)

  
  FlipBuffers()
  Delay(10)
Until quit = 1

End

;procedures
Procedure customFogClear(x, y)
  ;Manually change alpha settings by replacing each pixel with its corrected values.
  ;We need to manually change the alpha because the 2D Drawing commands reset the alpha for
  ;sprites in all modes of drawing including those specifically for changing the alpha levels.
  Shared fogClear()
  Protected i, j, ofx, ofy, tlx, tly, brx, bry, fogPoint
  Protected *bufferLine.Long, *bufferPixel.Long, pitch
  
  tlx = x - #fogClearWidth / 2 
  tly = y - #fogClearHeight / 2
  brx = tlx + #fogClearWidth
  bry = tly + #fogClearHeight
  If tlx < 0: ofx = -tlx: tlx = 0: EndIf
  If tly < 0: ofy = -tly: tly = 0: EndIf
  If brx > #mapWidth: brx = #mapWidth: EndIf
  If bry > #mapHeight: bry = #mapHeight: EndIf
  
  StartDrawing(SpriteOutput(#fog_spr))
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    *bufferLine.Long = DrawingBuffer()
    pitch = DrawingBufferPitch()
    
    *bufferLine + pitch * tly
    For j = ofy To ofy + bry - tly - 1
      *bufferPixel = *bufferLine + tlx * SizeOf(long)
      For i = ofx To ofx + brx - tlx - 1
        fogPoint = Alpha(*bufferPixel\l) - fogClear(i, j)
        If fogPoint < 0: fogPoint = 0: EndIf
        ;We assume the pixel format is #PB_PixelFormat_32Bits_BGR : 4 bytes per pixel (BBGGRR) + AA for alpha
        ;We could also provide separate routines to deal with other DrawingBufferPixelFormat() values.
        *bufferPixel\l = RGBA(0, 0, 0, fogPoint) ;Memory color values here are actually in reverse but alpha is still last
        *bufferPixel + SizeOf(Long)
      Next
      *bufferLine + pitch
    Next
  StopDrawing()
EndProcedure

Procedure customFogSet(tlx, tly, brx, bry) ;sets values within a a box 
  ;Called between StartDrawing(SpriteOutput(spriteID)) and StopDrawing().
  ;resets color and alpha settings manually by replacing each pixel with its correct values
  Protected i, j
  Protected pitch = DrawingBufferPitch(), *bufferPixel.Long
  Protected *bufferLine.Long = DrawingBuffer() + tly * pitch + tlx * SizeOf(Long)
    
  
  For j = tly To bry
    *bufferPixel = *bufferLine + tlx * SizeOf(Long)
    For i = tlx To brx
      ;We assume the pixel format is #PB_PixelFormat_32Bits_BGR : 4 bytes per pixel (BBGGRR) + AA for alpha
      *bufferPixel\l = RGBA(0, 0, 0, 255) ;memory color values here are actually in reverse but alpha is still last
      *bufferPixel + SizeOf(Long)
    Next
    *bufferLine + pitch
  Next
EndProcedure
There are a couple of special tricks to make everything possible.

The first is using an image to generate the alpha gradients that will be applied to the sprite.
The Second is applying the alpha changes to the 'fog' sprite manually because drawing them would destroy the sprite's alpha layer.


@Edit: made some minor corrections and documentation updates to code. Added the ability to reset the fog. Added the code to use a box shape instead of a circle area to clear the fog but it must be commented/uncommented in the code to work.
Last edited by Demivec on Fri May 20, 2016 1:10 am, edited 1 time in total.
Little John
Addict
Addict
Posts: 4519
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Fog of war

Post by Little John »

Hi Demivec,

that's a cool effect, thank you!
User avatar
electrochrisso
Addict
Addict
Posts: 980
Joined: Mon May 14, 2007 2:13 am
Location: Darling River

Re: Fog of war

Post by electrochrisso »

that's a cool effect, thank you!
I like too. :wink:
PureBasic! Purely one of the best 8)
User avatar
falsam
Enthusiast
Enthusiast
Posts: 630
Joined: Wed Sep 21, 2011 9:11 am
Location: France
Contact:

Re: Fog of war

Post by falsam »

Thank you Demivec. Cool effect +++

➽ Windows 11 64-bit - PB 6.0 x64 - AMD Ryzen 7 - NVIDIA GeForce GTX 1650 Ti

Sorry for my bad english and the Dunning–Kruger effect.
User avatar
Demivec
Addict
Addict
Posts: 4086
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: Fog of war

Post by Demivec »

Thanks for the positive comments regarding the Fog Test code. :)

I made some minor corrections and documentation updates to code.

Added the ability to reset the fog and I also added code to use a box shape instead of a circle area to clear the fog. Two lines have to be commented/uncommented in the code to make the change.
Post Reply