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: realistic prints are drawn procedurally — boot (treaded sole), shoe, bare foot, paw and hoof.
  • Each print has a crisp depression, a softened layer and a raised edge lit from the top-left. A print stays well marked for most of its life (erosionStart), then erodes: the crisp detail goes first and the soft imprint lingers.
  • Boot prints show a treaded sole and a separate heel, like real shoe prints.
  • Prints point their toe in the walking direction and alternate left / right.
  • Footprints are emitted from sprites tagged with footprintCaster.
  • You can tune rendering per surface profile (built-in sand, snow, mud, dirt, grass, or custom names).
  • No filter runs per print, so hundreds of prints stay cheap.

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, mud, dirt, grass)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.45Softness of a fresh print
blurEndnumber1.9How soft an old print gets (higher = the crisp detail fades faster)
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
jitternumber6Random rotation variation in degrees
print'boot' | 'shoe' | 'bare' | 'paw' | 'hoof''boot'Print shape

Prints point their toe in the walking direction. If your art needs the opposite, 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.