threlte logo
@threlte/extras

<AnimatedSpriteMaterial>

Provides animation tools for spritesheets.

<script lang="ts">
  import { Canvas, T } from '@threlte/core'
  import { AnimatedSpriteMaterial } from '@threlte/extras'
  import Scene from './Scene.svelte'
  import { useTweakpane } from '$lib/useTweakpane'

  const { addInput, addButton, action } = useTweakpane()

  const fps = addInput({
    label: 'fps',
    value: 10,
    params: {
      min: 1,
      step: 1,
      max: 60
    }
  })

  const loop = addInput({
    label: 'loop',
    value: true
  })

  let play: () => void
  let pause: () => void

  addButton({
    title: 'play',
    onClick: () => play()
  })

  addButton({
    title: 'pause',
    onClick: () => pause()
  })
</script>

<Canvas>
  <Scene />

  <T.Sprite position.y={-2.3}>
    <AnimatedSpriteMaterial
      bind:play
      bind:pause
      textureUrl='/textures/sprites/fire.png'
      totalFrames={8}
      fps={$fps}
      loop={$loop}
    />
    <T.PointLight
      intensity={12}
      decay={0.5}
      position.y={-0.2}
      position.z={0.02}
    />
  </T.Sprite>
  

  <T.AmbientLight />
  <T.DirectionalLight />
</Canvas>

<div use:action />
<script lang='ts'>
  import * as THREE from 'three'
  import { T, useFrame } from '@threlte/core'
  import { AnimatedSpriteMaterial } from '@threlte/extras'

  const keyboard = { x: 0 }
  const pressed = new Set<string>()

  let animation = 'IdleLeft'

  const handleKey = (key: string, value: 0 | 1) => {
    switch (key.toLowerCase()) {
      case 'a':
      case 'arrowleft':
        return (keyboard.x = +value)
      case 'd':
      case 'arrowright':
        return (keyboard.x = -value)
    }
    return
  }

  const handleKeydown = (e: KeyboardEvent) => {
    pressed.add(e.key)
    pressed.forEach((key) => handleKey(key, 1))
  }

  const handleKeyup = (e: KeyboardEvent) => {
    pressed.delete(e.key)
    handleKey(e.key, 0)
    pressed.forEach((key) => handleKey(key, 1))

    if (e.key === 'q') play()
    if (e.key === 'e') pause()
  }

  let ref: THREE.Mesh

  useFrame(() => {
    ref.position.x += (-keyboard.x * 0.03)

    if (keyboard.x > 0) {
      animation = 'RunLeft'
    } else if (keyboard.x < 0) {
      animation = 'RunRight'
    } else {
      animation = animation.replace('Run', 'Idle')
    }
  })

  let play: () => void
  let pause: () => void
</script>

<svelte:window
  on:keydown={handleKeydown}
  on:keyup={handleKeyup}
/>

<T.Mesh
  bind:ref
  position.y={-2.75}
  position.x={0.5}
  position.z={0.01}
>
  <AnimatedSpriteMaterial
    is={new THREE.MeshStandardMaterial()}
    bind:play
    bind:pause
    {animation}
    textureUrl='/textures/sprites/player.png'
    dataUrl='/textures/sprites/player.json'
  />
  <T.PlaneGeometry args={[0.5, 0.5]} />
</T.Mesh>
<script lang='ts'>
  import { T } from '@threlte/core'
  import { AnimatedSpriteMaterial, useTexture } from '@threlte/extras'
  import Player from './Player.svelte'

  const texture = useTexture('/textures/sprites/bg.png')
</script>

{#each { length: 9 } as _, i}
  <T.Sprite
    scale={0.5}
    position.y={-1.99}
    position.x={i < 5
      ? (i / 2.4) + (Math.random() * 0.4) - 2.8
      : (i / 2.4) + (Math.random() * 0.4) - 1
    }
  >
    <AnimatedSpriteMaterial
      textureUrl='/textures/sprites/grass.png'
      totalFrames={6}
      fps={5}
      delay={i * 40}
    />
  </T.Sprite>
{/each}

<T.Sprite
  scale={7.5}
  position.z={-0.01}
  position.y={0.4}
>
  {#if $texture}
    <T.MeshBasicMaterial
      map={$texture}
      totalFrames={8}
    />
  {/if}
</T.Sprite>

<Player />

<T.PerspectiveCamera
  makeDefault
  position.z={7}
/>

This material is most easily used by passing it a spritesheet URL and a JSON metadata file URL.

Currently, JSON metadata using Aseprite’s hash export format is supported.

Animation names from tags can be used to transition to specific animations in the spritesheet.

<T.Sprite>
  <AnimatedSpriteMaterial
    animation='Idle'
    textureUrl='./player.png'
    dataUrl='./player.json'
  />
</T.Sprite>

If no metadata file is provided, additional props must be passed to run an animation:

  • totalFrames, if the spritesheet is only a single row.
  • totalFrames, rows, and columns, otherwise.
<T.Sprite>
  <AnimatedSpriteMaterial
    textureUrl='./fire.png'
    totalFrames={14}
    rows={4}
    columns={4}
  />
</T.Sprite>

Additionally, if a sheet with no JSON supplied has multiple animations, start and end frames must be passed to run an animation within the sheet.

<T.Sprite>
  <AnimatedSpriteMaterial
    textureUrl='./fire.png'
    totalFrames={14}
    rows={4}
    columns={4}
    startFrame={4}
    endFrame={8}
  />
</T.Sprite>

<AnimatedSpriteMaterial> can be attached to a <T.Sprite> as well as a <T.Mesh>.

<script lang="ts">
  import { Canvas, T } from '@threlte/core'
  import Scene from './Scene.svelte'
</script>

<Canvas>
  <Scene />

  <T.DirectionalLight intensity={2} castShadow position={[1, 1, 1]} />
</Canvas>
<script lang="ts">
  import { T } from '@threlte/core'
  import { Grid, OrbitControls, Sky, AnimatedSpriteMaterial } from '@threlte/extras'

</script>

<T.OrthographicCamera
  makeDefault
  near={-100}
  far={100}
  zoom={150}
  position={[5, 1.5, 3]}
  on:create={({ ref }) => ref.lookAt(0, 0, 0)}
>
  <OrbitControls
    enableDamping
    enablePan={false}
    enableZoom={false}
    maxPolarAngle={Math.PI / 2.5}
    minPolarAngle={Math.PI / 6}
  />
</T.OrthographicCamera>

<Sky />

<Grid position.y={0.001} type='polar' fadeDistance={10} infiniteGrid />

<T.Mesh position.y={1} position.x={-2} castShadow receiveShadow>
  <T.MeshStandardMaterial color="white" />
  <T.SphereGeometry />
</T.Mesh>

<T.Mesh receiveShadow rotation.x={-Math.PI / 2}>
  <T.PlaneGeometry args={[1000, 1000]} />
  <T.MeshStandardMaterial />
</T.Mesh>

<T.Mesh
  position.y={0.5}
  rotation.y={Math.PI / 2}
  castShadow
  receiveShadow
>
  <AnimatedSpriteMaterial
    animation='Idle_Left'
    textureUrl='/textures/sprites/punk.png'
    dataUrl='/textures/sprites/punk.json'
  />
  <T.PlaneGeometry />
</T.Mesh>

In the case of a Mesh parent a MeshBasicMaterial will be used by default, instead of a SpriteMaterial when attached to a Sprite. A custom depth material will be attached when parented to a mesh to support shadows.

Any other material type can be used as well.

<T.Mesh>
  <AnimatedSpriteMaterial
    is={THREE.MeshStandardMaterial}
    textureUrl='./fire.png'
    totalFrames={14}
  />
</T.Mesh>

Component Signature

Props

name
type
required
default
description

textureUrl
string
yes
The URL of the spritesheet texture image.

alphaTest
number
no
0.1
Sets the alpha value to be used when running an alpha test.

animation
string
no
The current playing animation name.

autoplay
boolean
no
true
Controls whether or not to automatically run an animation on load.

columns
number
no
The number of columns in the spritesheet.

dataUrl
string
no
The URL of the spritesheet JSON.

delay
number
no
0
Delay the start of the animation in ms.

endFrame
number
no
totalFrames
The end frame of the current animation.

filter
"nearest" | "linear"
no
"nearest"
The texture filtering applied to the spritesheet.

flipX
boolean
no
false
Whether or not the Sprite should flip sides on the x-axis.

fps
boolean
no
10
The desired frames per second of the animation.

loop
boolean
no
true
Whether or not the current animation should loop.

rows
number
no
1
The number of rows in the spritesheet.

startFrame
number
no
0
The start frame of the current animation.

totalFrames
number
no
rows * columns - 1
The total number of frames in the spritesheet.

transparent
boolean
no
true
Whether or not the material should be transparent.

Events

name
payload
description

load
void
Fires when all resources have loaded.

start
void
Fires when an animation starts.

end
void
Fires when an animation ends.

loop
void
Fires when an animation loop completes.

Bindings

name
type

play
() => void

pause
() => void