Page 1 of 1

Fog of war

Posted: Sat May 14, 2016 9:09 am
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.

Re: Fog of war

Posted: Sat May 14, 2016 12:19 pm
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.

Re: Fog of war

Posted: Sat May 14, 2016 12:27 pm
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.

Re: Fog of war

Posted: Sat May 14, 2016 1:35 pm
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 ;)

Re: Fog of war

Posted: Sat May 14, 2016 3:53 pm
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 :)

Re: Fog of war

Posted: Sat May 14, 2016 7:56 pm
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.

Re: Fog of war

Posted: Tue May 17, 2016 11:54 pm
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.

Re: Fog of war

Posted: Wed May 18, 2016 10:24 am
by Little John
Hi Demivec,

that's a cool effect, thank you!

Re: Fog of war

Posted: Thu May 19, 2016 3:25 am
by electrochrisso
that's a cool effect, thank you!
I like too. :wink:

Re: Fog of war

Posted: Thu May 19, 2016 1:33 pm
by falsam
Thank you Demivec. Cool effect +++

Re: Fog of war

Posted: Fri May 20, 2016 1:12 am
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.