import type { AsepriteDirection , AsepriteFrameJSON , AsepriteFrameTagJSON , AsepriteJSON , AsepriteLayerJSON } from "*.aseprite.json" ; import { loadImage } from "./graphics" ; import { now } from "./util" ; /**
 * Sprite implementation which uses the Aseprite JSON format. Use the static asynchronous [[load]] method to load the
 * sprite and then use [[draw]] or [[drawTag]] to draw the sprite animation.
 */ export class Aseprite { private readonly frames : AsepriteFrameJSON [ ] ; private readonly frameTags : Record < string , AsepriteFrameTagJSON > = { } ; private readonly frameTagDurations : Record < string , number > = { } ; private readonly duration : number ; private readonly fallbackTag = "idle" ; private constructor ( private readonly json , private readonly image ) { this . frames = Object . values ( json . frames ) ; this . duration = this . frames . reduce ( ( duration , frame ) => duration + frame . duration , 0 ) ; for ( const frameTag of json . meta . frameTags ?? [ ] ) { let duration = 0 ; for ( let i = frameTag . from ; i <= frameTag . to ; i ++ ) { duration += this . frames [ i ] . duration ; } this . frameTags [ frameTag . name ] = frameTag ; this . frameTagDurations [ frameTag . name ] = duration ; } } param source return public static async load ( source ) { const json = await ( await fetch ( source ) ) . json ( ) as AsepriteJSON ; const baseURL = new URL ( source , location . href ) ; const image = await loadImage ( new URL ( json . meta . image , baseURL ) ) ; return new Aseprite ( json , image ) ; } return public get width ( ) : number { return this . frames [ 0 ] . sourceSize . w ; } return public get height ( ) : number { return this . frames [ 0 ] . sourceSize . h ; } private calculateFrameIndex ( time = now ( ) , duration = this . duration , from = 0 , to = this . frames . length - 1 , direction = "forward" ) { let delta = direction === "reverse" ? - 1 : 1 ; if ( direction === "pingpong" ) { duration = duration * 2 - this . frames [ from ] . duration - this . frames [ to ] . duration ; } let frameTime = time % duration ; let frameIndex = direction === "reverse" ? to : from ; while ( ( ( delta > 0 && frameIndex < to ) || ( delta < 0 && frameIndex > from ) ) && frameTime >= this . frames [ frameIndex ] . duration ) { frameTime -= this . frames [ frameIndex ] . duration ; frameIndex += delta ; if ( frameIndex === to ) { delta = - delta ; } } return frameIndex ; } param time return public getFrameIndex ( time = now ( ) ) { return this . calculateFrameIndex ( time ) ; } param ctx param index param x param y public drawFrame ( ctx , index , x , y ) { const frame = this . frames [ index ] ; if ( frame == null ) { throw new Error ( "Frame index not found: " + index ) ; } ctx . drawImage ( this . image , frame . frame . x , frame . frame . y , frame . frame . w , frame . frame . h , Math . round ( x ) + frame . spriteSourceSize . x , Math . round ( y ) + frame . spriteSourceSize . y , frame . spriteSourceSize . w , frame . spriteSourceSize . h ) ; } param tag param time return public getTaggedFrameIndex ( tag , time = now ( ) ) { const frameTag = this . frameTags [ tag ] || this . frameTags [ this . fallbackTag ] ; if ( frameTag == null ) { throw new Error ( `Frame tag not found and fallback is not available as well. Tag: '${ tag }' | FallbackTag: '${ this . fallbackTag }'` ) ; } return this . calculateFrameIndex ( time , this . frameTagDurations [ tag ] , frameTag . from , frameTag . to , frameTag . direction ) ; } param tag return public getAnimationDurationByTag ( tag ) { const duration = this . frameTagDurations [ tag ] || this . frameTagDurations [ this . fallbackTag ] ; if ( duration == null ) { throw new Error ( `Frame tag not found and fallback is not available as well. Tag: '${ tag }' | FallbackTag: '${ this . fallbackTag }'` ) ; } return duration ; } param ctx param tag param x param y param time public drawTag ( ctx , tag , x , y , time = now ( ) ) { this . drawFrame ( ctx , this . getTaggedFrameIndex ( tag , time ) , x , y ) ; } param ctx param x param y param time public draw ( ctx , x , y , time = now ( ) ) { const frameIndex = this . calculateFrameIndex ( time ) ; this . drawFrame ( ctx , frameIndex , x , y ) ; } param name return public getLayer ( name ) { return this . json . meta . layers ?. find ( layer => layer . name === name ) ?? null ; } }