better and easier to understand asset loading

This commit is contained in:
Thomas von Dein 2024-04-04 18:24:44 +02:00
parent 95469ab3c7
commit cb3ccb323c

View File

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"image" "image"
_ "image/png" _ "image/png"
"io/fs"
"log" "log"
"log/slog" "log/slog"
"path" "path"
@ -17,6 +18,11 @@ import (
// Maps image name to image data // Maps image name to image data
type AssetRegistry map[string]*ebiten.Image type AssetRegistry map[string]*ebiten.Image
// A helper to pass the registry easier around
type assetData struct {
Registry AssetRegistry
}
type AnimationGeo struct { type AnimationGeo struct {
X int `json:"x"` X int `json:"x"`
Y int `json:"y"` Y int `json:"y"`
@ -52,13 +58,15 @@ type AnimationRegistry map[string]AnimationSet
//go:embed sprites/*.png fonts/*.ttf levels/*.ldtk shaders/*.kg sprites/*.json //go:embed sprites/*.png fonts/*.ttf levels/*.ldtk shaders/*.kg sprites/*.json
var assetfs embed.FS var assetfs embed.FS
// Called at build time, creates the global asset and animation registries
var Assets, Animations = LoadImages() var Assets, Animations = LoadImages()
// load pngs and json files
func LoadImages() (AssetRegistry, AnimationRegistry) { func LoadImages() (AssetRegistry, AnimationRegistry) {
dir := "sprites" dir := "sprites"
images := AssetRegistry{} imagedata := &assetData{}
imagedata.Registry = AssetRegistry{}
rawanimations := []AnimationJSON{} rawanimations := []AnimationJSON{}
animations := AnimationRegistry{}
// we use embed.FS to iterate over all files in ./assets/ // we use embed.FS to iterate over all files in ./assets/
entries, err := assetfs.ReadDir(dir) entries, err := assetfs.ReadDir(dir)
@ -69,53 +77,75 @@ func LoadImages() (AssetRegistry, AnimationRegistry) {
for _, imagefile := range entries { for _, imagefile := range entries {
path := path.Join(dir, imagefile.Name()) path := path.Join(dir, imagefile.Name())
fd, err := assetfs.Open(path)
if err != nil {
log.Fatalf("failed to open file %s: %s", imagefile.Name(), err)
}
defer fd.Close()
switch { switch {
case strings.HasSuffix(path, ".png"): case strings.HasSuffix(path, ".png"):
fd, err := assetfs.Open(path) name, image := ReadImage(imagefile, fd)
if err != nil { imagedata.Registry[name] = image
log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err)
}
defer fd.Close()
name := strings.TrimSuffix(imagefile.Name(), ".png")
img, _, err := image.Decode(fd)
if err != nil {
log.Fatalf("failed to decode image %s: %s", imagefile.Name(), err)
}
images[name] = ebiten.NewImageFromImage(img)
slog.Debug("loaded asset", "path", path)
case strings.HasSuffix(path, ".json"): case strings.HasSuffix(path, ".json"):
fd, err := assetfs.Open(path) animationjson := ReadJson(imagefile, fd)
if err != nil {
log.Fatalf("failed to open json file %s: %s", imagefile.Name(), err)
}
defer fd.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(fd)
animationjson := AnimationJSON{}
err = json.Unmarshal(buf.Bytes(), &animationjson)
if err != nil {
log.Fatalf("failed to parse JSON: %s", err)
}
rawanimations = append(rawanimations, animationjson) rawanimations = append(rawanimations, animationjson)
} }
slog.Debug("loaded asset", "path", path)
} }
// preprocess animation sprites // preprocess animation sprites
animations := ProcessAnimations(rawanimations, imagedata)
return imagedata.Registry, animations
}
func ReadImage(imagefile fs.DirEntry, fd fs.File) (string, *ebiten.Image) {
name := strings.TrimSuffix(imagefile.Name(), ".png")
img, _, err := image.Decode(fd)
if err != nil {
log.Fatalf("failed to decode image %s: %s", imagefile.Name(), err)
}
image := ebiten.NewImageFromImage(img)
return name, image
}
func ReadJson(imagefile fs.DirEntry, fd fs.File) AnimationJSON {
buf := new(bytes.Buffer)
buf.ReadFrom(fd)
animationjson := AnimationJSON{}
err := json.Unmarshal(buf.Bytes(), &animationjson)
if err != nil {
log.Fatalf("failed to parse JSON: %s", err)
}
return animationjson
}
// turn a raw JSON from Asesprite into something we can use better
// internally we also load all the sprites of an animation by using
// image.SubImage() on the spriteset matching the animation name.
// so, if the animation JSON is called "player-idle.json", then we
// expect to receive the spriteset in a file "player-idle.png", which
// has of course already been loaded at this stage. These spritesets
// must contain a row of sprites. We get the measurements from the JSON.
func ProcessAnimations(rawanimations []AnimationJSON, imagedata *assetData) AnimationRegistry {
animations := AnimationRegistry{}
for _, animation := range rawanimations { for _, animation := range rawanimations {
animationset := AnimationSet{} animationset := AnimationSet{}
animationset.File = strings.TrimSuffix(animation.Meta.Name, ".png") animationset.File = strings.TrimSuffix(animation.Meta.Name, ".png")
for _, frame := range animation.Frames { for _, frame := range animation.Frames {
sprite := images[animationset.File].SubImage( sprite := imagedata.Registry[animationset.File].SubImage(
image.Rect( image.Rect(
frame.Position.X, frame.Position.X,
frame.Position.Y, frame.Position.Y,
@ -132,5 +162,5 @@ func LoadImages() (AssetRegistry, AnimationRegistry) {
animations[animationset.File] = animationset animations[animationset.File] = animationset
} }
return images, animations return animations
} }