Skip to content

Footprints

Install

Please install the @canvasengine/presets package first.

bash
npm install @canvasengine/presets

Then, you can use the presets in your project.

Overview

Footprints adds fading footprints behind moving sprites.

  • No footprint image is required: the shape is generated procedurally.
  • Footprints are emitted from sprites tagged with footprintCaster.
  • You can tune rendering per surface profile (sand, snow, custom names).

Basic Usage

html
<Canvas>
  <Viewport worldWidth={2048} worldHeight={1280} sortableChildren={true}>
    <Footprints
      profiles={surfaceProfiles}
      defaultSurface="sand"
      updateHz={45}
      maxFootprints={320}
    />

    <Sprite
      x={heroX}
      y={heroY}
      controls={heroControls}
      sheet={heroSheet}
      footprintCaster={heroFootprintCaster}
    />
  </Viewport>
</Canvas>

<script>
  import { signal } from 'canvasengine'
  import { Footprints } from '@canvasengine/presets'

  const heroY = signal(840)
  const snowLine = 560

  const heroFootprintCaster = {
    minStepDistance: 16,
    stepIntervalMs: 70,
    leftOffset: { x: -10, y: 2 },
    rightOffset: { x: 10, y: 2 },
    surface: () => (heroY() <= snowLine ? 'snow' : 'sand'),
  }

  const surfaceProfiles = {
    sand: {
      tint: '#7f5d34',
      lifetimeMs: 1800,
      startAlpha: 0.34,
      blurStart: 0.35,
      blurEnd: 1.6,
      erosionStart: 0.62,
      blendMode: 'multiply',
    },
    snow: {
      tint: '#889db6',
      lifetimeMs: 2600,
      startAlpha: 0.26,
      blurStart: 0.62,
      blurEnd: 2.35,
      erosionStart: 0.43,
      blendMode: 'multiply',
    },
  }
</script>

Preset Props

PropTypeDefaultDescription
profilesRecord<string, SurfaceProfile> | Signal<Record> | () => RecordBuilt-in (default, sand, snow)Surface rendering profiles
defaultSurfacestring | Signal<string> | () => string'default'Fallback profile name
maxFootprintsnumber | Signal<number> | () => number260Maximum active footprints before recycling oldest
updateHznumber | Signal<number> | () => number30Recompute/update frequency

SurfaceProfile

FieldTypeDefaultDescription
lifetimeMsnumber1800Lifetime before disappearance
startAlphanumber0.32Initial opacity
endAlphanumber0Final opacity
tintstring | number0x2d2a26Footprint tint color
blendModestring | number'multiply'Blend mode used by footprint sprites
scalenumber1Base scale multiplier
blurStartnumber0.45Initial blur strength
blurEndnumber1.9Final blur strength
erosionStartnumber0.54Normalized time (0..1) where erosion starts
depthnumber0.58Depression intensity (0..1) for center darkening
rimStrengthnumber0.14Strength of the soft raised edge highlight

footprintCaster Options

Attach on each sprite that should leave footprints:

html
<Sprite
  image="hero.png"
  footprintCaster={{
    minStepDistance: 16,
    minSpeed: 44,
    stepIntervalMs: 70,
    leftOffset: { x: -10, y: 2 },
    rightOffset: { x: 10, y: 2 },
    surface: 'sand',
    size: 0.94,
    alpha: 1,
    blur: 0.1,
    angleOffset: 0,
    jitter: 11,
  }}
/>
FieldTypeDefaultDescription
enabledbooleantrueEnable/disable emitter on this sprite
footAnchor{ x, y }{ x: 0.5, y: 1 }Ground anchor inside sprite bounds
leftOffset{ x, y }{ x: -10, y: 1 }Offset for left foot (local walk basis)
rightOffset{ x, y }{ x: 10, y: 1 }Offset for right foot (local walk basis)
minStepDistancenumber18Distance needed before emitting a new footprint
minSpeednumber36Minimum movement speed in px/s required to emit
stepIntervalMsnumber85Minimum delay between two emissions
sizenumber1Additional footprint size multiplier
alphanumber1Opacity multiplier
blurnumber0Extra blur added to profile blur
surfacestringdefaultSurfaceProfile name for this caster
angleOffsetnumber0Extra rotation offset in degrees
jitternumber8Random rotation variation in degrees

If the footprint appears visually reversed in your art direction, set angleOffset: 180.

Conditional Rules (Terrain / Zones / X-Y)

You can make all important footprintCaster fields reactive (functions/signals), so emission can change by zone, terrain, or position ranges.

html
<script>
  const zones = [
    { x1: 860, y1: 760, x2: 1260, y2: 980, surface: 'mud', enabled: true, step: 13 },
    { x1: 1460, y1: 840, x2: 1880, y2: 1100, surface: 'rock', enabled: false, step: 20 },
    { x1: 0, y1: 0, x2: 2048, y2: 560, surface: 'snow', enabled: true, step: 14 },
    { x1: 0, y1: 560, x2: 2048, y2: 1280, surface: 'sand', enabled: true, step: 18 },
  ]

  const inZone = (x, y, z) => x >= z.x1 && x <= z.x2 && y >= z.y1 && y <= z.y2
  const zoneAt = (x, y) => zones.find((z) => inZone(x, y, z)) ?? zones[zones.length - 1]

  const heroFootprintCaster = {
    enabled: () => zoneAt(heroX(), heroY()).enabled,
    surface: () => zoneAt(heroX(), heroY()).surface,
    minStepDistance: () => zoneAt(heroX(), heroY()).step,
  }
</script>

Tuning Tips

  • Start with blendMode: 'multiply' for both sand and snow.
  • Keep minStepDistance between 14 and 22 for readable spacing.
  • In snow, use longer lifetimeMs and larger blurEnd.
  • In sand, increase erosionStart so traces break up sooner.