Skip to content

Instantly share code, notes, and snippets.

@abradley2
Created December 30, 2024 16:37
Show Gist options
  • Save abradley2/e7d889533bbff3461ca4bc6094490fb0 to your computer and use it in GitHub Desktop.
Save abradley2/e7d889533bbff3461ca4bc6094490fb0 to your computer and use it in GitHub Desktop.
AsepriteLoader
import { TaskEither } from "fp-ts/lib/TaskEither"
import { Assets, Spritesheet, SpritesheetData, SpritesheetFrameData } from "pixi.js"
import * as io from "io-ts"
import { HandledError } from "./errors"
import * as taskEither from "fp-ts/lib/TaskEither"
import { pipe } from "fp-ts/lib/function"
import * as errors from "./errors"
const frameDecoder = io.type({
filename: io.string,
frame: io.type({
x: io.number,
y: io.number,
w: io.number,
h: io.number,
}),
sourceSize: io.type({
w: io.number,
h: io.number,
}),
duration: io.number,
})
export type Frame = io.TypeOf<typeof frameDecoder>
const frameTagDecoder = io.type({
name: io.string,
from: io.number,
to: io.number,
})
export type FrameTag = io.TypeOf<typeof frameTagDecoder>
const metaDecoder = io.type({
image: io.string,
size: io.type({
w: io.number,
h: io.number,
}),
frameTags: io.array(frameTagDecoder),
})
const asepriteDecoder = io.type({
frames: io.array(frameDecoder),
meta: metaDecoder,
})
export type Aseprite = io.TypeOf<typeof asepriteDecoder>
const cache : Record<string, Spritesheet> = {}
const requestCache : Record<string, Promise<unknown> | undefined> = {}
export function loadSprite (spriteJsonFilePath: string): TaskEither<HandledError, Spritesheet> {
if (cache[spriteJsonFilePath]) {
return taskEither.right(cache[spriteJsonFilePath])
}
const siblingDir = spriteJsonFilePath.
split("/").
slice(0, -1).
join("/")
return pipe(
taskEither.Do,
taskEither.bind("json", () => {
return taskEither.tryCatch(
() => {
if (requestCache[spriteJsonFilePath]) {
return requestCache[spriteJsonFilePath]
}
const response = fetch(spriteJsonFilePath, { cache: "no-store" }).then((res) => res.json())
requestCache[spriteJsonFilePath] = response
return response
},
errors.handleError(new Error(`Failed to fetch sprite file for ${spriteJsonFilePath}`)),
)
}),
taskEither.bind("parsedJson", ({ json }) => {
const result = asepriteDecoder.decode(json)
return pipe(
result,
taskEither.fromEither,
taskEither.mapLeft(errors.handleError(new Error(`Failed to parse sprite file for ${spriteJsonFilePath}`))),
)
}),
taskEither.bind("texture", ({ parsedJson }) => {
return taskEither.tryCatch(
() => Assets.load(`${siblingDir}/${parsedJson.meta.image}`),
errors.handleError(new Error(`Failed to load texture for sprite file ${spriteJsonFilePath}`)),
)
}),
taskEither.bind("spriteSheet", ({ texture, parsedJson }) => {
const atlasData: SpritesheetData = {
frames: parsedJson.frames.reduce((acc: Record<string, SpritesheetFrameData>, frame, idx) => {
acc[`${idx}`] = frame
return acc
}, {}),
meta: {
size: parsedJson.meta.size,
scale: 1,
},
}
parsedJson.meta.frameTags.forEach((frameTag) => {
const frames = []
for (let i = frameTag.from; i <= frameTag.to; i++) {
frames.push(`${i}`)
}
if (typeof atlasData.animations === "undefined") {
atlasData.animations = {}
}
atlasData.animations[frameTag.name] = frames
})
return taskEither.right(new Spritesheet(texture, atlasData))
}),
taskEither.bind("parsedSpriteSheet", ({ spriteSheet }) => {
return taskEither.tryCatch(
() => spriteSheet.parse(),
errors.handleError(new Error(`Failed to parse sprite sheet for sprite file ${spriteJsonFilePath}`)),
)
}),
taskEither.map(({ spriteSheet }) => {
cache[spriteJsonFilePath] = spriteSheet
return spriteSheet
}),
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment