<AnimatedSpriteMaterial>
Provides animation tools for spritesheets.
<script lang="ts"> import { Canvas } from '@threlte/core' import Scene from './Scene.svelte'</script>
<div> <Canvas> <Scene /> </Canvas></div>
<style> div { background-color: black; height: 100%; }</style><script lang="ts"> import { T, useTask } from '@threlte/core' import { AnimatedSpriteMaterial } from '@threlte/extras' import { PointLight } from 'three'
const light = new PointLight('#FF893D', 100)
let rate = 1 / 10 let fixedStepTimeAccumulator = 0 useTask((delta) => { fixedStepTimeAccumulator += delta while (fixedStepTimeAccumulator >= rate) { fixedStepTimeAccumulator -= rate // random light intensity between 22 and 44 light.intensity = Math.random() * 24 + 22 } })</script>
<T.Sprite position.y={-2.3}> <AnimatedSpriteMaterial textureUrl="/textures/sprites/fire.png" totalFrames={8} fps={10} /> <T is={light} distance={1.8} decay={0.5} position.y={-0.2} position.z={0.02} /></T.Sprite><script lang="ts"> import { T, useTask } from '@threlte/core' import { AnimatedSpriteMaterial, Suspense } from '@threlte/extras' import { Mesh, MeshStandardMaterial } from 'three'
type Props = { position: [number, number, number] } let { position = $bindable() }: Props = $props()
const keyboard = { x: 0 } const pressed = new Set<string>()
let sprite = $state.raw<AnimatedSpriteMaterial>() let animation = $state('IdleRight')
const mesh = new Mesh() mesh.position.set(...position)
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') sprite?.play() if (e.key === 'e') sprite?.pause() }
useTask((delta) => { if (keyboard.x > 0) { animation = 'RunLeft' } else if (keyboard.x < 0) { animation = 'RunRight' } else { animation = animation.replace('Run', 'Idle') }
if (keyboard.x === 0) return position[0] += -keyboard.x * (delta * 2) mesh.position.set(...position) })</script>
<svelte:window onkeydown={handleKeydown} onkeyup={handleKeyup}/>
<Suspense> <T is={mesh}> <AnimatedSpriteMaterial is={new MeshStandardMaterial()} {animation} textureUrl="/textures/sprites/player.png" dataUrl="/textures/sprites/player.json" bind:this={sprite} /> <T.PlaneGeometry args={[0.5, 0.5]} /> </T></Suspense><script lang="ts"> import { T } from '@threlte/core' import { AnimatedSpriteMaterial, Suspense, useTexture } from '@threlte/extras' import Fire from './Fire.svelte' import Player from './Player.svelte' import ThrelteLogo from './ThrelteLogo.svelte' import { Tween } from 'svelte/motion' import { cubicOut } from 'svelte/easing'
const texture = useTexture('/textures/sprites/bg.png')
let playerPosition: [number, number, number] = $state([-2.0, -2.75, 0.01]) let playerAtFire = $derived(playerPosition && Math.abs(playerPosition[0]) < 0.7)
const fov = new Tween(50, { easing: cubicOut, duration: 900 }) const cameraPosY = new Tween(-0.2, { easing: cubicOut, duration: 900 })
$effect(() => { fov.set(playerAtFire ? 45 : 50) }) $effect(() => { cameraPosY.set(playerAtFire ? -0.9 : -0.2) })</script>
<Suspense> <Fire /></Suspense>
<T.AmbientLight color="#6697C7" intensity={0.3}/>
<Suspense> {#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}</Suspense>
{#await texture then map} <T.Sprite scale={7.5} position.z={-0.01} position.y={0.4} > <T.MeshBasicMaterial {map} /> </T.Sprite>{/await}
<Suspense> <ThrelteLogo show={playerAtFire} /></Suspense>
<Player bind:position={playerPosition} />
<T.PerspectiveCamera makeDefault position.z={7} position.y={cameraPosY.current} fov={fov.current}/><script lang="ts"> import { T } from '@threlte/core' import { AnimatedSpriteMaterial } from '@threlte/extras'
interface Props { show: boolean }
let { show }: Props = $props()
let animation = $state<'Hidden' | 'In' | 'Out'>('Hidden') let mounted = false
$effect(() => { if (mounted && show) { animation = 'In' } else if (!show && animation === 'In') { animation = 'Out' } else { mounted = true } })</script>
<T.Sprite scale={[3.5, 1.75, 3.5]} position.y={0.2}> <AnimatedSpriteMaterial textureUrl="/textures/sprites/Threlte_7.png" dataUrl="/textures/sprites/Threlte_7.json" {animation} autoplay loop={false} /></T.Sprite>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, andcolumns, 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>
<div> <Canvas> <Scene />
<T.DirectionalLight intensity={2} castShadow position={[1, 1, 1]} /> </Canvas></div>
<style> div { height: 100%; }</style><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]} oncreate={(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>