Skip to content

Fog Of War

Install

Please install the @canvasengine/presets package first.

bash
npm install @canvasengine/presets

Then, you can use the presets in your project.

Overview

FogOfWar provides an Age-of-Empires style fog system with 3 states:

  • visible: fully transparent (currently seen)
  • explored: darkened memory
  • unknown: opaque black

It works well inside Viewport, supports smooth rendering, and can expose visibility queries for gameplay objects.

Basic Usage

html
<Canvas>
  <Viewport worldWidth={2048} worldHeight={2048} sortableChildren={true}>
    <FogOfWar
      mapWidth={2048}
      mapHeight={2048}
      tileSize={32}
      smooth={true}
      renderScale={2}
      edgeSoftness={22}
      visionSources={visionSources}
      updateHz={15}
      zIndex={1000}
    />
  </Viewport>
</Canvas>

<script>
  import { computed, signal } from 'canvasengine'
  import { FogOfWar } from '@canvasengine/presets'

  const playerX = signal(300)
  const playerY = signal(260)

  const visionSources = computed(() => [
    { x: playerX(), y: playerY(), radius: 220, enabled: true },
  ])
</script>

Props

PropTypeDefaultDescription
mapWidthnumber | Signal<number>requiredWorld/map width in pixels
mapHeightnumber | Signal<number>requiredWorld/map height in pixels
tileSizenumber | Signal<number>32Fog grid cell size in pixels
smoothboolean | Signal<boolean>trueEnables smooth fog interpolation
renderScalenumber | Signal<number>2Internal fog resolution multiplier (higher = smoother, heavier CPU)
edgeSoftnessnumber | Signal<number>22Soft transition width (pixels) around vision circles
visionSourcesArray<VisionSource> | Signal<Array<VisionSource>>[]Vision emitters
updateHznumber | Signal<number>15Fog recomputation frequency
colorsobject | Signal<object>{ unknown: [0,0,0,1], explored: [0,0,0,0.55] }Fog colors/alpha
initialExploredboolean | Signal<boolean>falseReveal all as explored on start
controllerFogOfWarControllernoneExposes clarityAt/isVisibleAt/stateAt for object visibility logic
zIndexnumber | Signal<number>inheritedRender order (forwarded to Sprite)

VisionSource

ts
type VisionSource = {
  x: number | (() => number)
  y: number | (() => number)
  radius: number | (() => number)
  enabled?: boolean | (() => boolean)
}

FogOfWarController

ts
import { createFogOfWarController } from '@canvasengine/presets'

const fog = createFogOfWarController()

fog.clarityAt(x, y) // 0..1
fog.isVisibleAt(x, y, 0.72) // true only in "very clear" zone
fog.isExploredAt(x, y) // true if already discovered
fog.stateAt(x, y) // "visible" | "explored" | "unknown"

Gameplay Visibility Example

Use this when some objects should stay normal (current behavior), while others are visible only in clear vision.

html
<Rect x={relicX} y={relicY} width={34} height={34} color="#ffd166" />
<Rect x={enemyX} y={enemyY} width={34} height={34} color="#5ee7ff" visible={enemyVisible} />

<FogOfWar
  mapWidth={worldWidth}
  mapHeight={worldHeight}
  visionSources={visionSources}
  controller={fog}
  smooth={true}
  renderScale={2}
  edgeSoftness={22}
/>

<script>
  import { computed } from 'canvasengine'
  import { createFogOfWarController } from '@canvasengine/presets'

  const fog = createFogOfWarController()
  const enemyVisible = computed(() => fog.isVisibleAt(enemyX() + 17, enemyY() + 17, 0.72))
</script>

fogVisibility Directive

You can also attach visibility logic directly on an element.

html
<Sprite
  image="enemy.png"
  x={enemyX}
  y={enemyY}
  fogVisibility={{
    controller: fog,
    mode: 'visible',
    threshold: 0.72,
    point: {
      x: computed(() => enemyX() + 16),
      y: computed(() => enemyY() + 16),
    },
    hideAs: 'visible'
  }}
/>

Directive options:

  • controller: FogOfWar controller created with createFogOfWarController()
  • mode: 'visible' | 'explored' ('visible' by default)
  • threshold: clear zone threshold (0..1, default 0.65)
  • point: optional sample point { x, y } in world coordinates
  • hideAs: 'visible' (hide/show) or 'alpha' (fade only)
  • hiddenAlpha: alpha used when hidden with hideAs: 'alpha'
  • sampleHz: recomputation frequency (default 30)

Notes

  • Use computed() for visionSources so updates track unit movement naturally.
  • For draw order over gameplay, set Viewport sortableChildren={true} and a high zIndex on FogOfWar.
  • Current version uses circle vision per source (no LOS occlusion blocking yet).