@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
, 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>
<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>