commit 404e1d86df02bf752dfab657225d3cc57aaff50f Author: codegirl-007 Date: Mon Jul 27 21:32:28 2026 -0700 asset pipeline for packing sprites diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d94a80c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.bin +tutorials/ diff --git a/assetbake/assetbake b/assetbake/assetbake new file mode 100755 index 0000000..07a4fac Binary files /dev/null and b/assetbake/assetbake differ diff --git a/assetbake/main.odin b/assetbake/main.odin new file mode 100644 index 0000000..445e9e0 --- /dev/null +++ b/assetbake/main.odin @@ -0,0 +1,485 @@ +package main + +import "core:bytes" +import "core:c" +import "core:encoding/json" +import "core:fmt" +import "core:image" +import "core:image/png" +import "core:math" +import "core:os" +import "core:path/filepath" +import "core:slice" +import "core:strconv" +import "core:strings" +import stbi "vendor:stb/image" +import stbrp "vendor:stb/rect_pack" + +// Authoring-side clip block from manifest.json +Clip_Manifest :: struct { + folder: string, + fps: f32, + loop: bool, +} + +// Authoring-side character manifest +Manifest :: struct { + id: string, + pixels_per_unit: f32, + pivot: [2]f32, + clips: map[string]Clip_Manifest, +} + +// Runtime frame description written to *.char.json +Frame_Def :: struct { + rect: [4]int, // x, y, w, h in atlas pixels + source_size: [2]int, + trim_offset: [2]int, +} + +// Runtime clip description +Clip_Def :: struct { + loop: bool, + fps: f32, + frames: []Frame_Def, +} + +// Runtime character package metadata +Char_Def :: struct { + version: int, + id: string, + atlas: string, // filename relative to this .char.json + pixels_per_unit: f32, + pivot: [2]f32, + clips: map[string]Clip_Def, +} + +// Packed atlas pixels + per-frame rects (indexed like the combined frames list) +Atlas :: struct { + pixels: []u8, + width: int, + height: int, + rects: [][4]int, // [x, y, w, h] per frame; sizes may differ across clips +} + +// Where one clip's frames sit inside the packed atlas frame list +Clip_Range :: struct { + name: string, + clip: Clip_Manifest, + start: int, // index into the combined frames / atlas rects + count: int, +} + +main :: proc() { + if len(os.args) < 3 { + fmt.eprintfln("usage: assetbake ") + os.exit(1) + } + bake_character(os.args[1], os.args[2]) +} + +// bake_character is the full pipeline for one character folder. +bake_character :: proc(in_dir, out_dir: string) { + manifest := load_manifest(in_dir) + if len(manifest.clips) == 0 { + fmt.eprintfln("manifest has no clips") + os.exit(1) + } + + // Load every clip from the manifest into one frame list (deterministic clip order) + frames, ranges := load_all_clips(in_dir, manifest) + defer destroy_frames(frames) + defer delete(ranges) + + // Clips may differ in size (idle vs walk); pack with a real rectangle packer + atlas := pack_atlas(frames) + defer delete(atlas.pixels) + defer delete(atlas.rects) + + ensure_dir(out_dir) + + atlas_name := fmt.tprintf("%s.atlas.png", manifest.id) + write_atlas_png(out_dir, atlas_name, atlas) + + char_def := build_char_def(manifest, ranges, atlas_name, atlas) + defer destroy_char_def(char_def) + + write_char_json(out_dir, fmt.tprintf("%s.char.json", manifest.id), char_def) + fmt.printfln("baked %s (%d clips, %d frames)", manifest.id, len(ranges), len(frames)) +} + +// load_manifest reads content/.../manifest.json into a Manifest. +load_manifest :: proc(character_dir: string) -> Manifest { + path, join_err := filepath.join({character_dir, "manifest.json"}) + if join_err != nil { + fmt.eprintfln("join failed: %v", join_err) + os.exit(1) + } + defer delete(path) + + data, read_err := os.read_entire_file(path, context.allocator) + if read_err != nil { + fmt.eprintfln("failed to read %s: %v", path, read_err) + os.exit(1) + } + defer delete(data) + + manifest: Manifest + if err := json.unmarshal(data, &manifest); err != nil { + fmt.eprintfln("json unmarshal failed: %v", err) + os.exit(1) + } + return manifest +} + +// load_all_clips walks every manifest clip (names sorted), loads PNGs, and records atlas ranges. +// Within a clip, all frames must share one size. Different clips may use different sizes. +// Caller owns frames (destroy_frames) and ranges (delete). +load_all_clips :: proc(character_dir: string, manifest: Manifest) -> ([]^image.Image, []Clip_Range) { + clip_names := sorted_clip_names(manifest) + defer delete(clip_names) + + loaded: [dynamic]^image.Image + ranges := make([]Clip_Range, len(clip_names)) + + for name, i in clip_names { + clip := manifest.clips[name] + paths := list_sorted_frame_paths(character_dir, clip.folder) + defer delete_paths(paths) + + start := len(loaded) + for path in paths { + append(&loaded, load_frame(path)) + } + + // Enforce uniform size inside this clip only + validate_clip_frame_sizes(name, loaded[start:]) + + ranges[i] = Clip_Range { + name = name, + clip = clip, + start = start, + count = len(paths), + } + fw := loaded[start].width + fh := loaded[start].height + fmt.printfln( + "clip %s: %d frames %dx%d (atlas slots %d..%d)", + name, + len(paths), + fw, + fh, + start, + start + len(paths) - 1, + ) + } + + if len(loaded) == 0 { + fmt.eprintfln("no frames loaded") + os.exit(1) + } + + frames := make([]^image.Image, len(loaded)) + copy(frames, loaded[:]) + delete(loaded) + + fmt.printfln("loaded %d frames total across %d clips", len(frames), len(ranges)) + return frames, ranges +} + +// validate_clip_frame_sizes requires every frame in one clip to match the first frame's size. +validate_clip_frame_sizes :: proc(clip_name: string, frames: []^image.Image) { + if len(frames) == 0 { + return + } + fw := frames[0].width + fh := frames[0].height + for frame, i in frames[1:] { + if frame.width != fw || frame.height != fh { + fmt.eprintfln( + "clip %s: frame size mismatch at %d: got %dx%d, expected %dx%d", + clip_name, + i + 1, + frame.width, + frame.height, + fw, + fh, + ) + os.exit(1) + } + } +} + +// sorted_clip_names returns manifest clip keys in alphabetical order for stable bakes. +sorted_clip_names :: proc(manifest: Manifest) -> []string { + names := make([]string, len(manifest.clips)) + i := 0 + for name in manifest.clips { + names[i] = name // keys owned by the map; do not delete individually + i += 1 + } + slice.sort(names) + return names +} + +// list_sorted_frame_paths globs *.png under folder and sorts by trailing frame number. +// Caller owns the returned paths (use delete_paths). +list_sorted_frame_paths :: proc(character_dir, folder: string) -> []string { + clip_dir, clip_join_err := filepath.join({character_dir, folder}) + if clip_join_err != nil { + fmt.eprintfln("join failed: %v", clip_join_err) + os.exit(1) + } + defer delete(clip_dir) + + pattern, pattern_err := filepath.join({clip_dir, "*.png"}) + if pattern_err != nil { + fmt.eprintfln("pattern join failed: %v", pattern_err) + os.exit(1) + } + defer delete(pattern) + + matches, glob_err := filepath.glob(pattern) + if glob_err != nil { + fmt.eprintfln("glob failed: %v", glob_err) + os.exit(1) + } + if len(matches) == 0 { + fmt.eprintfln("no pngs in %s", clip_dir) + os.exit(1) + } + + // Sort by trailing frame number so _10 comes after _9, not after _1 + slice.sort_by(matches, proc(a, b: string) -> bool { + return frame_index(a) < frame_index(b) + }) + return matches +} + +delete_paths :: proc(paths: []string) { + for p in paths { + delete(p) + } + delete(paths) +} + +load_frame :: proc(path: string) -> ^image.Image { + img, err := png.load_from_file(path) + if err != nil { + fmt.eprintfln("image load failed (%s): %v", path, err) + os.exit(1) + } + if img.channels != 4 { + fmt.eprintfln("expected RGBA for %s, got %d channels", path, img.channels) + os.exit(1) + } + return img +} + +destroy_frames :: proc(frames: []^image.Image) { + for f in frames { + image.destroy(f) + } + delete(frames) +} + +// pack_atlas packs variable-size frames with stb_rect_pack and blits them into one sheet. +// Caller owns atlas.pixels and atlas.rects. +pack_atlas :: proc(frames: []^image.Image) -> Atlas { + PADDING :: 1 // 1px gap to reduce bleeding if filtered later + + pack_rects := make([]stbrp.Rect, len(frames)) + defer delete(pack_rects) + + total_area := 0 + max_w := 0 + max_h := 0 + for frame, i in frames { + w := frame.width + PADDING + h := frame.height + PADDING + pack_rects[i] = stbrp.Rect { + id = c.int(i), // pack_rects may reorder; id maps back to frame index + w = stbrp.Coord(w), + h = stbrp.Coord(h), + } + total_area += w * h + max_w = max(max_w, w) + max_h = max(max_h, h) + } + + // Grow a square-ish atlas until everything fits + side := max(max_w, max_h, int(math.ceil(math.sqrt(f64(total_area))))) + atlas_w, atlas_h := 0, 0 + packed := false + for attempt in 0 ..< 16 { + atlas_w = side + atlas_h = side + if try_pack_rects(atlas_w, atlas_h, pack_rects) { + packed = true + break + } + side = max(side + 64, int(math.ceil(f64(side) * 1.25))) + _ = attempt + } + if !packed { + fmt.eprintfln("failed to pack %d frames into an atlas", len(frames)) + os.exit(1) + } + + pixels := make([]u8, atlas_w * atlas_h * 4) // transparent + rects := make([][4]int, len(frames)) + + // Blit using each rect's id (array order may have changed during packing) + for pr in pack_rects { + if !pr.was_packed { + fmt.eprintfln("frame %d was not packed", pr.id) + os.exit(1) + } + idx := int(pr.id) + frame := frames[idx] + fw := frame.width + fh := frame.height + dst_x := int(pr.x) + dst_y := int(pr.y) + + src := bytes.buffer_to_bytes(&frame.pixels) + for y in 0 ..< fh { + src_row := src[y * fw * 4:(y + 1) * fw * 4] + dst_i := ((dst_y + y) * atlas_w + dst_x) * 4 + copy(pixels[dst_i:], src_row) + } + + rects[idx] = {dst_x, dst_y, fw, fh} + } + + fmt.printfln("packed atlas %dx%d", atlas_w, atlas_h) + return Atlas { + pixels = pixels, + width = atlas_w, + height = atlas_h, + rects = rects, + } +} + +// try_pack_rects attempts to place all rects into a width×height bin. +try_pack_rects :: proc(width, height: int, rects: []stbrp.Rect) -> bool { + nodes := make([]stbrp.Node, width) + defer delete(nodes) + + ctx: stbrp.Context + stbrp.init_target(&ctx, c.int(width), c.int(height), raw_data(nodes), c.int(len(nodes))) + stbrp.setup_allow_out_of_mem(&ctx, true) + + return stbrp.pack_rects(&ctx, raw_data(rects), c.int(len(rects))) != 0 +} + +ensure_dir :: proc(path: string) { + if err := os.make_directory_all(path); err != nil && err != .Exist { + fmt.eprintfln("failed to create output dir %s: %v", path, err) + os.exit(1) + } +} + +// write_atlas_png writes atlas pixels to out_dir/atlas_name via stb_image_write. +write_atlas_png :: proc(out_dir, atlas_name: string, atlas: Atlas) { + atlas_path, atlas_join_err := filepath.join({out_dir, atlas_name}) + if atlas_join_err != nil { + fmt.eprintfln("atlas path join failed: %v", atlas_join_err) + os.exit(1) + } + defer delete(atlas_path) + + atlas_cpath := strings.clone_to_cstring(atlas_path) + defer delete(atlas_cpath) + + if stbi.write_png( + atlas_cpath, + i32(atlas.width), + i32(atlas.height), + 4, + raw_data(atlas.pixels), + i32(atlas.width * 4), + ) == 0 { + fmt.eprintfln("failed to write atlas: %s", atlas_path) + os.exit(1) + } + fmt.printfln("wrote %s (%dx%d)", atlas_path, atlas.width, atlas.height) +} + +// build_char_def builds runtime metadata for every baked clip using packed rects. +build_char_def :: proc( + manifest: Manifest, + ranges: []Clip_Range, + atlas_name: string, + atlas: Atlas, +) -> Char_Def { + char_clips := make(map[string]Clip_Def) + + for range in ranges { + frame_defs := make([]Frame_Def, range.count) + for i in 0 ..< range.count { + slot := range.start + i + r := atlas.rects[slot] + frame_defs[i] = Frame_Def { + rect = r, + source_size = {r[2], r[3]}, + trim_offset = {0, 0}, // no trimming in v1 + } + } + + char_clips[range.name] = Clip_Def { + loop = range.clip.loop, + fps = range.clip.fps, + frames = frame_defs, + } + } + + return Char_Def { + version = 1, + id = manifest.id, + atlas = atlas_name, + pixels_per_unit = manifest.pixels_per_unit, + pivot = manifest.pivot, + clips = char_clips, + } +} + +destroy_char_def :: proc(char_def: Char_Def) { + for _, clip in char_def.clips { + delete(clip.frames) + } + delete(char_def.clips) +} + +write_char_json :: proc(out_dir, char_name: string, char_def: Char_Def) { + char_json, marshal_err := json.marshal(char_def, {pretty = true, use_spaces = true}) + if marshal_err != nil { + fmt.eprintfln("json marshal failed: %v", marshal_err) + os.exit(1) + } + defer delete(char_json) + + char_path, char_join_err := filepath.join({out_dir, char_name}) + if char_join_err != nil { + fmt.eprintfln("char path join failed: %v", char_join_err) + os.exit(1) + } + defer delete(char_path) + + if err := os.write_entire_file(char_path, char_json); err != nil { + fmt.eprintfln("failed to write %s: %v", char_path, err) + os.exit(1) + } + fmt.printfln("wrote %s", char_path) +} + +// frame_index pulls the trailing number from names like skeleton-Idle_17.png +frame_index :: proc(path: string) -> int { + name := filepath.stem(path) + idx := strings.last_index_byte(name, '_') + if idx < 0 { + return 0 + } + n, _ := strconv.parse_int(name[idx + 1:]) + return n +} diff --git a/assets_baked/characters/toad/toad.atlas.png b/assets_baked/characters/toad/toad.atlas.png new file mode 100644 index 0000000..f007cf2 Binary files /dev/null and b/assets_baked/characters/toad/toad.atlas.png differ diff --git a/assets_baked/characters/toad/toad.char.json b/assets_baked/characters/toad/toad.char.json new file mode 100644 index 0000000..754f1f1 --- /dev/null +++ b/assets_baked/characters/toad/toad.char.json @@ -0,0 +1,600 @@ +{ + "version": 1, + "id": "toad", + "atlas": "toad.atlas.png", + "pixels_per_unit": 160.00000000, + "pivot": [ + 0.50000000, + 1.00000000 + ], + "clips": { + "walk": { + "loop": true, + "fps": 10.00000000, + "frames": [ + { + "rect": [ + 0, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 269, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 538, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 807, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1076, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1345, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1614, + 0, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 0, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 269, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 538, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 807, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1076, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1345, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1614, + 327, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 0, + 654, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 269, + 654, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 538, + 654, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 807, + 654, + 268, + 326 + ], + "source_size": [ + 268, + 326 + ], + "trim_offset": [ + 0, + 0 + ] + } + ] + }, + "idle": { + "loop": true, + "fps": 10.00000000, + "frames": [ + { + "rect": [ + 1883, + 0, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1883, + 320, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1883, + 640, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1076, + 654, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1290, + 654, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1504, + 654, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1718, + 960, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1076, + 974, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1290, + 974, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1504, + 974, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 0, + 981, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 214, + 981, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 428, + 981, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 642, + 981, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 856, + 981, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1718, + 1280, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1070, + 1294, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + }, + { + "rect": [ + 1284, + 1294, + 213, + 319 + ], + "source_size": [ + 213, + 319 + ], + "trim_offset": [ + 0, + 0 + ] + } + ] + } + } +} \ No newline at end of file diff --git a/content/characters/toad/Idle/skeleton-Idle_0.png b/content/characters/toad/Idle/skeleton-Idle_0.png new file mode 100644 index 0000000..6e33175 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_0.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_1.png b/content/characters/toad/Idle/skeleton-Idle_1.png new file mode 100644 index 0000000..8e8c499 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_1.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_10.png b/content/characters/toad/Idle/skeleton-Idle_10.png new file mode 100644 index 0000000..ac973e4 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_10.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_11.png b/content/characters/toad/Idle/skeleton-Idle_11.png new file mode 100644 index 0000000..02b0549 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_11.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_12.png b/content/characters/toad/Idle/skeleton-Idle_12.png new file mode 100644 index 0000000..bb17e65 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_12.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_13.png b/content/characters/toad/Idle/skeleton-Idle_13.png new file mode 100644 index 0000000..9210d88 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_13.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_14.png b/content/characters/toad/Idle/skeleton-Idle_14.png new file mode 100644 index 0000000..9e8587e Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_14.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_15.png b/content/characters/toad/Idle/skeleton-Idle_15.png new file mode 100644 index 0000000..6625e42 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_15.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_16.png b/content/characters/toad/Idle/skeleton-Idle_16.png new file mode 100644 index 0000000..e72d163 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_16.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_17.png b/content/characters/toad/Idle/skeleton-Idle_17.png new file mode 100644 index 0000000..84041f6 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_17.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_2.png b/content/characters/toad/Idle/skeleton-Idle_2.png new file mode 100644 index 0000000..8c6f502 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_2.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_3.png b/content/characters/toad/Idle/skeleton-Idle_3.png new file mode 100644 index 0000000..7117e87 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_3.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_4.png b/content/characters/toad/Idle/skeleton-Idle_4.png new file mode 100644 index 0000000..7fd550e Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_4.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_5.png b/content/characters/toad/Idle/skeleton-Idle_5.png new file mode 100644 index 0000000..27d994c Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_5.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_6.png b/content/characters/toad/Idle/skeleton-Idle_6.png new file mode 100644 index 0000000..958a576 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_6.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_7.png b/content/characters/toad/Idle/skeleton-Idle_7.png new file mode 100644 index 0000000..6487556 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_7.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_8.png b/content/characters/toad/Idle/skeleton-Idle_8.png new file mode 100644 index 0000000..ab46ef2 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_8.png differ diff --git a/content/characters/toad/Idle/skeleton-Idle_9.png b/content/characters/toad/Idle/skeleton-Idle_9.png new file mode 100644 index 0000000..4218da6 Binary files /dev/null and b/content/characters/toad/Idle/skeleton-Idle_9.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_0.png b/content/characters/toad/Walk/skeleton-Walk_0.png new file mode 100644 index 0000000..95c0393 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_0.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_1.png b/content/characters/toad/Walk/skeleton-Walk_1.png new file mode 100644 index 0000000..dee2ab6 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_1.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_10.png b/content/characters/toad/Walk/skeleton-Walk_10.png new file mode 100644 index 0000000..5a9b19c Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_10.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_11.png b/content/characters/toad/Walk/skeleton-Walk_11.png new file mode 100644 index 0000000..e4ba079 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_11.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_12.png b/content/characters/toad/Walk/skeleton-Walk_12.png new file mode 100644 index 0000000..09c69af Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_12.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_13.png b/content/characters/toad/Walk/skeleton-Walk_13.png new file mode 100644 index 0000000..9b859ff Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_13.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_14.png b/content/characters/toad/Walk/skeleton-Walk_14.png new file mode 100644 index 0000000..c1c3da3 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_14.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_15.png b/content/characters/toad/Walk/skeleton-Walk_15.png new file mode 100644 index 0000000..a5ef633 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_15.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_16.png b/content/characters/toad/Walk/skeleton-Walk_16.png new file mode 100644 index 0000000..ba56323 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_16.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_17.png b/content/characters/toad/Walk/skeleton-Walk_17.png new file mode 100644 index 0000000..ce4eeb8 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_17.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_2.png b/content/characters/toad/Walk/skeleton-Walk_2.png new file mode 100644 index 0000000..aff4923 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_2.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_3.png b/content/characters/toad/Walk/skeleton-Walk_3.png new file mode 100644 index 0000000..703c7c4 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_3.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_4.png b/content/characters/toad/Walk/skeleton-Walk_4.png new file mode 100644 index 0000000..e497812 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_4.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_5.png b/content/characters/toad/Walk/skeleton-Walk_5.png new file mode 100644 index 0000000..c7cb870 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_5.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_6.png b/content/characters/toad/Walk/skeleton-Walk_6.png new file mode 100644 index 0000000..49bc0af Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_6.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_7.png b/content/characters/toad/Walk/skeleton-Walk_7.png new file mode 100644 index 0000000..677a87b Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_7.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_8.png b/content/characters/toad/Walk/skeleton-Walk_8.png new file mode 100644 index 0000000..a11573c Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_8.png differ diff --git a/content/characters/toad/Walk/skeleton-Walk_9.png b/content/characters/toad/Walk/skeleton-Walk_9.png new file mode 100644 index 0000000..d6fa326 Binary files /dev/null and b/content/characters/toad/Walk/skeleton-Walk_9.png differ diff --git a/content/characters/toad/manifest.json b/content/characters/toad/manifest.json new file mode 100644 index 0000000..27f26cd --- /dev/null +++ b/content/characters/toad/manifest.json @@ -0,0 +1,17 @@ +{ + "id": "toad", + "pixels_per_unit": 160.0, + "pivot": [0.5, 1.0], + "clips": { + "idle": { + "folder": "Idle", + "fps": 10, + "loop": true + }, + "walk": { + "folder": "Walk", + "fps": 10, + "loop": true + } + } +}