parse actual Aseprite anims in converters
|
@ -40,18 +40,10 @@ set(ASSETS
|
|||
assets-cg/hud_wave2.png
|
||||
assets-cg/skillicons.png
|
||||
# Player animations
|
||||
assets-cg/player/player_idle_up.png
|
||||
assets-cg/player/player_idle_right.png
|
||||
assets-cg/player/player_idle_down.png
|
||||
assets-cg/player/player_idle_left.png
|
||||
assets-cg/player/player_attack_up.png
|
||||
assets-cg/player/player_attack_right.png
|
||||
assets-cg/player/player_attack_down.png
|
||||
assets-cg/player/player_attack_left.png
|
||||
assets-cg/player/player_damage_up.png
|
||||
assets-cg/player/player_damage_right.png
|
||||
assets-cg/player/player_damage_down.png
|
||||
assets-cg/player/player_damage_left.png
|
||||
assets-cg/player/player_up.aseprite
|
||||
assets-cg/player/player_right.aseprite
|
||||
assets-cg/player/player_down.aseprite
|
||||
assets-cg/player/player_left.aseprite
|
||||
# Skill animations
|
||||
assets-cg/skills/swing_up.png
|
||||
assets-cg/skills/swing_right.png
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
A fun game about smashing monsters in a dungeon.
|
||||
|
||||
Compiling requires py\_aseprite by Eiyeron: [https://github.com/Eiyeron/py\_aseprite](https://github.com/Eiyeron/py_aseprite)
|
|
@ -1,5 +1,6 @@
|
|||
import fxconv
|
||||
import re
|
||||
import os.path
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
|
@ -10,6 +11,8 @@ def convert(input, output, params, target):
|
|||
o = convert_level_map(input, params)
|
||||
elif params["custom-type"] == "animation":
|
||||
o = convert_animation(input, params)
|
||||
elif params["custom-type"] == "aseprite-anim":
|
||||
o = convert_aseprite_anim(input, output, params)
|
||||
else:
|
||||
recognized = False
|
||||
|
||||
|
@ -164,3 +167,151 @@ def convert_animation(input, params):
|
|||
})
|
||||
|
||||
return o
|
||||
|
||||
def convert_aseprite_anim(input, output, params):
|
||||
from aseprite import AsepriteFile
|
||||
|
||||
#---
|
||||
# Perform checks for support on the Aseprite file
|
||||
#---
|
||||
|
||||
with open(input, "rb") as fp:
|
||||
ase = AsepriteFile(fp.read())
|
||||
|
||||
if ase.header.color_depth != 32:
|
||||
raise fxconv.FxconvError("Only RGBA supported yet, sorry x_x")
|
||||
|
||||
# Find tags that give names to animations
|
||||
frame_tags = None
|
||||
for c in ase.frames[0].chunks:
|
||||
if c.chunk_type == 0x2018: # Tags
|
||||
frame_tags = c
|
||||
|
||||
if frame_tags is None:
|
||||
raise fxconv.FxconvError("Found no frame tags")
|
||||
|
||||
if len(ase.layers) != 1:
|
||||
raise fxconv.FxconvError("Only one layer supported yet, sorry x_x")
|
||||
|
||||
# Find cel chunks of suitable types in each frame
|
||||
cel_chunks = []
|
||||
for i, f in enumerate(ase.frames):
|
||||
for c in f.chunks:
|
||||
if c.chunk_type == 0x2005: # Cel
|
||||
cel_chunks.append(c)
|
||||
break
|
||||
else:
|
||||
raise fxconv.FxconvError(f"Found no cels for frame #{i+1}")
|
||||
|
||||
if cel_chunks[-1].cel_type not in [0,2]:
|
||||
raise fxconv.FxconvError(f"Cel chunk for frame #{i+1} is linked " +
|
||||
"or tilemap")
|
||||
|
||||
#---
|
||||
# Print summary of animations
|
||||
#---
|
||||
|
||||
print(f"{os.path.basename(input)} ({ase.header.width}x{ase.header.height})",
|
||||
f"has {len(frame_tags.tags)} animations:")
|
||||
for t in frame_tags.tags:
|
||||
name, from_, to = t['name'], t['from'], t['to']
|
||||
print(f" '{name}': Frames {from_} to {to}", end="")
|
||||
durations = [ase.frames[i].frame_duration for i in range(from_, to+1)]
|
||||
print(" (" + ", ".join(f"{d} ms" for d in durations) + ")")
|
||||
|
||||
#---
|
||||
# Generate PIL images for each frame
|
||||
#---
|
||||
|
||||
pil_frames = []
|
||||
|
||||
for i, f in enumerate(ase.frames):
|
||||
img = Image.new("RGBA", (ase.header.width, ase.header.height))
|
||||
c = cel_chunks[i]
|
||||
pixels = []
|
||||
offset = 0
|
||||
|
||||
for y in range(c.data['height']):
|
||||
for x in range(c.data['width']):
|
||||
r, g, b, a = c.data['data'][offset:offset+4]
|
||||
pixels.append((r, g, b, a))
|
||||
offset += 4
|
||||
|
||||
img.putdata(pixels)
|
||||
pil_frames.append(img)
|
||||
|
||||
#---
|
||||
# Parse parameters
|
||||
#---
|
||||
|
||||
if "center" in params:
|
||||
center = tuple(map(int, params["center"].split(",")))
|
||||
else:
|
||||
center = (ase.header.width // 2, ase.header.height // 2)
|
||||
|
||||
if "next" in params:
|
||||
next_anim = [s.strip() for s in params["next"].split(",")]
|
||||
next_anim = dict([s.split("=", 1) for s in next_anim])
|
||||
else:
|
||||
next_anim = dict()
|
||||
|
||||
#---
|
||||
# Generate compact sheets and object data for each animation
|
||||
#---
|
||||
|
||||
bg = Image.new("RGBA", (ase.header.width, ase.header.height), (0, 0, 0, 0))
|
||||
|
||||
# This will old all animations in sequence
|
||||
o = fxconv.ObjectData()
|
||||
sizeof_frame = 16
|
||||
|
||||
for t in frame_tags.tags:
|
||||
name, from_, to = t['name'], t['from'], t['to']
|
||||
total_width = 0
|
||||
total_height = 0
|
||||
|
||||
for i in range(from_, to+1):
|
||||
bbox = ImageChops.difference(pil_frames[i], bg).getbbox()
|
||||
if bbox:
|
||||
pil_frames[i] = (bbox[0], bbox[1], pil_frames[i].crop(bbox))
|
||||
else:
|
||||
pil_frames[i] = (0, 0, pil_frames[i])
|
||||
|
||||
total_width += pil_frames[i][2].width
|
||||
total_height = max(total_height, pil_frames[i][2].height)
|
||||
|
||||
|
||||
# Define a new symbol for each animation
|
||||
symname = params["name"] + "_" + name
|
||||
s = fxconv.Structure()
|
||||
|
||||
sheet = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
|
||||
x = 0
|
||||
|
||||
for i in range(from_, to+1):
|
||||
s += fxconv.ref(f"{symname}_sheet")
|
||||
s += fxconv.u8(x) + fxconv.u8(0)
|
||||
s += fxconv.u8(pil_frames[i][2].width)
|
||||
s += fxconv.u8(pil_frames[i][2].height)
|
||||
s += fxconv.u8(center[0] - pil_frames[i][0])
|
||||
s += fxconv.u8(center[1] - pil_frames[i][1])
|
||||
s += fxconv.u16(ase.frames[i].frame_duration)
|
||||
|
||||
if i < to:
|
||||
s += fxconv.ref(symname, (i-from_+1) * sizeof_frame)
|
||||
elif name in next_anim:
|
||||
s += fxconv.ref(params["name"] + "_" + next_anim[name])
|
||||
else:
|
||||
s += fxconv.u32(0)
|
||||
|
||||
sheet.paste(pil_frames[i][2], (x, 0))
|
||||
x += pil_frames[i][2].width
|
||||
|
||||
o += fxconv.sym(symname)
|
||||
o += s
|
||||
o += fxconv.sym(f"{symname}_sheet")
|
||||
o += fxconv.convert_bopti_cg(sheet, {
|
||||
"profile": params.get("profile", "p8"),
|
||||
})
|
||||
|
||||
return o
|
||||
|
|
|
@ -1,52 +1,6 @@
|
|||
*.png:
|
||||
custom-type: animation
|
||||
name_regex: (.*)\.png anim_\1
|
||||
player_*.aseprite:
|
||||
custom-type: aseprite-anim
|
||||
name_regex: (.*)\.aseprite anims_\1
|
||||
center: 12, 17
|
||||
next: Idle=Idle, Walking=Walking, Hit=Idle, Attack=Idle
|
||||
profile: p8
|
||||
|
||||
player_idle_*.png:
|
||||
frame_duration: 500, 500
|
||||
center: 12, 17
|
||||
|
||||
player_idle_up.png:
|
||||
next: anim_player_idle_up
|
||||
|
||||
player_idle_right.png:
|
||||
next: anim_player_idle_right
|
||||
|
||||
player_idle_down.png:
|
||||
next: anim_player_idle_down
|
||||
|
||||
player_idle_left.png:
|
||||
next: anim_player_idle_left
|
||||
|
||||
player_attack_*.png:
|
||||
frame_duration: 330
|
||||
center: 12, 17
|
||||
|
||||
player_attack_down.png:
|
||||
next: anim_player_idle_down
|
||||
|
||||
player_attack_up.png:
|
||||
next: anim_player_idle_up
|
||||
|
||||
player_attack_right.png:
|
||||
next: anim_player_idle_right
|
||||
|
||||
player_attack_left.png:
|
||||
next: anim_player_idle_left
|
||||
|
||||
player_damage_*.png:
|
||||
frame_duration: 90
|
||||
center: 12, 17
|
||||
|
||||
player_damage_down.png:
|
||||
next: anim_player_idle_down
|
||||
|
||||
player_damage_up.png:
|
||||
next: anim_player_idle_up
|
||||
|
||||
player_damage_right.png:
|
||||
next: anim_player_idle_right
|
||||
|
||||
player_damage_left.png:
|
||||
next: anim_player_idle_left
|
||||
|
|
Before Width: | Height: | Size: 401 B |
Before Width: | Height: | Size: 372 B |
Before Width: | Height: | Size: 347 B |
Before Width: | Height: | Size: 368 B |
Before Width: | Height: | Size: 309 B |
Before Width: | Height: | Size: 301 B |
Before Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 293 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 587 B |
Before Width: | Height: | Size: 580 B |
Before Width: | Height: | Size: 566 B |
Before Width: | Height: | Size: 503 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
20
src/anim.c
|
@ -12,12 +12,26 @@
|
|||
anim_frame_t *name[4] = { \
|
||||
name ## _up, name ## _right, name ## _down, name ## _left };
|
||||
|
||||
DIRECTIONAL_ANIM(anim_player_idle);
|
||||
DIRECTIONAL_ANIM(anim_player_attack);
|
||||
DIRECTIONAL_ANIM(anim_player_damage);
|
||||
#define ANIM_4DIRECTIONAL(prefix, suffix) \
|
||||
extern anim_frame_t prefix ## _up_ ## suffix[]; \
|
||||
extern anim_frame_t prefix ## _right_ ## suffix[]; \
|
||||
extern anim_frame_t prefix ## _down_ ## suffix[]; \
|
||||
extern anim_frame_t prefix ## _left_ ## suffix[]; \
|
||||
anim_frame_t *prefix ## _ ## suffix[4] = { \
|
||||
prefix ## _up_ ## suffix, \
|
||||
prefix ## _right_ ## suffix, \
|
||||
prefix ## _down_ ## suffix, \
|
||||
prefix ## _left_ ## suffix, \
|
||||
};
|
||||
|
||||
DIRECTIONAL_ANIM(anim_swing);
|
||||
DIRECTIONAL_ANIM(anim_impale);
|
||||
|
||||
ANIM_4DIRECTIONAL(anims_player, Idle);
|
||||
ANIM_4DIRECTIONAL(anims_player, Walking);
|
||||
ANIM_4DIRECTIONAL(anims_player, Attack);
|
||||
ANIM_4DIRECTIONAL(anims_player, Hit);
|
||||
|
||||
/* Animation functions. */
|
||||
|
||||
fixed_t (anim_duration)(anim_frame_t const *first_frame)
|
||||
|
|
|
@ -68,8 +68,9 @@ extern anim_frame_t anim_bat_damage_left[];
|
|||
extern anim_frame_t anim_bat_damage_right[];
|
||||
|
||||
/* Quadri-directional animations. */
|
||||
extern anim_frame_t *anim_player_idle[4];
|
||||
extern anim_frame_t *anim_player_attack[4];
|
||||
extern anim_frame_t *anim_player_damage[4];
|
||||
extern anim_frame_t *anims_player_Idle[4];
|
||||
extern anim_frame_t *anims_player_Walking[4];
|
||||
extern anim_frame_t *anims_player_Attack[4];
|
||||
extern anim_frame_t *anims_player_Hit[4];
|
||||
extern anim_frame_t *anim_swing[4];
|
||||
extern anim_frame_t *anim_impale[4];
|
||||
|
|
|
@ -155,7 +155,7 @@ int entity_damage(entity_t *e, int base_damage)
|
|||
entity_set_anim(e, enemies[e->identity]->anim_damage[index]);
|
||||
}
|
||||
else {
|
||||
entity_set_anim(e, anim_player_damage[e->movement.facing]);
|
||||
entity_set_anim(e, anims_player_Hit[e->movement.facing]);
|
||||
}
|
||||
|
||||
return damage;
|
||||
|
|
|
@ -104,7 +104,7 @@ int main(void)
|
|||
player->sprite = (frect_t){
|
||||
-fix(6)/16, fix(5)/16, -fix(12)/16, fix(4)/16 };
|
||||
|
||||
entity_set_anim(player, anim_player_idle);
|
||||
entity_set_anim(player, anims_player_Idle);
|
||||
|
||||
//---
|
||||
// Main loop
|
||||
|
@ -359,7 +359,7 @@ int main(void)
|
|||
|
||||
bool set_anim = (player->movement.facing != next.facing);
|
||||
game_try_move_entity(&game, player, &next);
|
||||
if(set_anim) entity_set_anim(player, anim_player_idle);
|
||||
if(set_anim) entity_set_anim(player, anims_player_Walking);
|
||||
}
|
||||
|
||||
/* Directions to reach the player from anywhere on the grid */
|
||||
|
@ -434,7 +434,7 @@ int main(void)
|
|||
player->movement.facing);
|
||||
game_add_effect_area(&game, area);
|
||||
|
||||
entity_set_anim(player, anim_player_attack);
|
||||
entity_set_anim(player, anims_player_Attack);
|
||||
player->current_attack = area;
|
||||
player->attack_follows_movement = true;
|
||||
playerd->combo_delay = area->lifetime;
|
||||
|
|