add support for palette variations of sprites
This commit is contained in:
parent
9fc160e36c
commit
1b2f0fbc15
|
@ -79,6 +79,9 @@ set(ASSETS
|
|||
# Enemies: Gunslinger
|
||||
assets-cg/enemies/gunslinger_left.aseprite
|
||||
assets-cg/enemies/gunslinger_right.aseprite
|
||||
# Enemies: Fire Slime
|
||||
assets-cg/enemies/fire_slime_left.txt
|
||||
assets-cg/enemies/fire_slime_right.txt
|
||||
# Misc
|
||||
assets-cg/font_damage_red.png
|
||||
assets-cg/font_damage_white.png
|
||||
|
|
|
@ -14,6 +14,8 @@ def convert(input, output, params, target):
|
|||
o = convert_animation(input, params)
|
||||
elif params["custom-type"] == "aseprite-anim":
|
||||
o = convert_aseprite_anim(input, output, params)
|
||||
elif params["custom-type"] == "aseprite-anim-variation":
|
||||
o = convert_aseprite_anim_variation(input, output, params)
|
||||
elif params["custom-type"] == "tiled-tileset":
|
||||
o = convert_tiled_tileset(input, output, params)
|
||||
elif params["custom-type"] == "tiled-map":
|
||||
|
@ -32,7 +34,7 @@ def convert_level(input, params):
|
|||
"map": re.compile(r'[a-zA-Z_][a-zA-Z0-9-]*'),
|
||||
"spawner": re.compile(r'\d+,\d+'),
|
||||
"player_spawn": re.compile(r'\d+,\d+'),
|
||||
"wave": re.compile(r'\d+s(\s+\d+\*[a-z]+/\d+)+'),
|
||||
"wave": re.compile(r'\d+s(\s+\d+\*[a-z_]+/\d+)+'),
|
||||
"delay": re.compile(r'\d+s')
|
||||
}
|
||||
with open(input, "r") as fp:
|
||||
|
@ -95,7 +97,7 @@ def convert_level(input, params):
|
|||
identity = 1
|
||||
elif identity == "bat/2":
|
||||
identity = 2
|
||||
elif identity == "slime/4":
|
||||
elif identity == "fire_slime/4":
|
||||
identity = 3
|
||||
elif identity == "gunslinger/8":
|
||||
identity = 4
|
||||
|
@ -231,26 +233,49 @@ def convert_animation(input, params):
|
|||
|
||||
return o
|
||||
|
||||
def _aseprite_render_cel(c):
|
||||
def _aseprite_render_cel(c, indexed=False, palette=None, newpalette=None):
|
||||
if c.chunk_type != 0x2005: # Cel
|
||||
raise fxconv.FxconvError("Rendering a Cel without a Cel chunk")
|
||||
raise fxconv.FxconvError("Rendering a Cel but it's not a Cel chunk")
|
||||
if c.cel_type not in [0,2]:
|
||||
raise fxconv.FxconvError("Rendering a Cel that is linked or tilemap")
|
||||
|
||||
colors = None
|
||||
|
||||
if indexed:
|
||||
assert palette is not None and palette.chunk_type == 0x2019 # Palette
|
||||
# py_aseprite swaps blue and green!!
|
||||
colors = [(c["red"], c["blue"], c["green"], c["alpha"])
|
||||
for c in palette.colors]
|
||||
if newpalette is not None:
|
||||
assert indexed and newpalette.chunk_type == 0x2019 # Palette
|
||||
newcolors = [(c["red"], c["blue"], c["green"], c["alpha"])
|
||||
for c in newpalette.colors]
|
||||
|
||||
img = Image.new("RGBA", (c.data['width'], c.data['height']))
|
||||
pixels = []
|
||||
offset = 0
|
||||
|
||||
for y in range(img.height):
|
||||
for x in range(img.width):
|
||||
r, g, b, a = c.data['data'][offset:offset+4]
|
||||
if indexed:
|
||||
i = c.data['data'][offset]
|
||||
offset += 1
|
||||
else:
|
||||
r, g, b, a = c.data['data'][offset:offset+4]
|
||||
offset += 4
|
||||
|
||||
# Remap palette if requested
|
||||
if newpalette is not None:
|
||||
r, g, b, a = newcolors[i]
|
||||
elif indexed:
|
||||
r, g, b, a = colors[i]
|
||||
|
||||
pixels.append((r, g, b, a))
|
||||
offset += 4
|
||||
|
||||
img.putdata(pixels)
|
||||
return img
|
||||
|
||||
def convert_aseprite_anim(input, output, params):
|
||||
def convert_aseprite_anim(input, output, params, newpalette=None):
|
||||
from aseprite import AsepriteFile
|
||||
|
||||
#---
|
||||
|
@ -260,8 +285,12 @@ def convert_aseprite_anim(input, output, params):
|
|||
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")
|
||||
if ase.header.color_depth == 8:
|
||||
indexed = True
|
||||
elif ase.header.color_depth == 32:
|
||||
indexed = False
|
||||
else:
|
||||
raise fxconv.FxconvError("Only indexed/RGBA supported yet, sorry x_x")
|
||||
|
||||
# Find tags that give names to animations
|
||||
tags = None
|
||||
|
@ -283,6 +312,16 @@ def convert_aseprite_anim(input, output, params):
|
|||
durations = [ase.frames[i].frame_duration for i in range(from_, to+1)]
|
||||
print(" (" + ", ".join(f"{d} ms" for d in durations) + ")")
|
||||
|
||||
#---
|
||||
# Find palette
|
||||
#---
|
||||
|
||||
palette_chunk = None
|
||||
|
||||
for c in ase.frames[0].chunks:
|
||||
if c.chunk_type == 0x2019: # Palette
|
||||
palette_chunk = c
|
||||
|
||||
#---
|
||||
# Generate PIL images for each frame
|
||||
#---
|
||||
|
@ -302,7 +341,8 @@ def convert_aseprite_anim(input, output, params):
|
|||
x = [c for c in x if c.chunk_type == 0x2005]
|
||||
chunk = x[chunk.layer_index]
|
||||
|
||||
cel = _aseprite_render_cel(chunk)
|
||||
cel = _aseprite_render_cel(chunk, indexed, palette_chunk,
|
||||
newpalette)
|
||||
img.paste(cel, (chunk.x_pos, chunk.y_pos))
|
||||
|
||||
pil_frames.append(img)
|
||||
|
@ -382,6 +422,27 @@ def convert_aseprite_anim(input, output, params):
|
|||
|
||||
return o
|
||||
|
||||
def convert_aseprite_anim_variation(input, output, params):
|
||||
if "base" not in params or "palette" not in params:
|
||||
raise fxconv.FxconvError("aseprite anim variation needs base/palette")
|
||||
|
||||
from aseprite import AsepriteFile
|
||||
|
||||
base = os.path.join(os.path.dirname(input), params["base"])
|
||||
palette = os.path.join(os.path.dirname(input), params["palette"])
|
||||
|
||||
with open(palette, "rb") as fp:
|
||||
ase = AsepriteFile(fp.read())
|
||||
|
||||
newpalette = None
|
||||
for c in ase.frames[0].chunks:
|
||||
if c.chunk_type == 0x2019: # Palette
|
||||
newpalette = c
|
||||
if newpalette is None:
|
||||
raise fxconv.FxconvError("f{input} has no palette chunk")
|
||||
|
||||
return convert_aseprite_anim(base, output, params, newpalette)
|
||||
|
||||
def print_xml_tree(node, indent=0):
|
||||
print(indent*" " + f"<{node.tag}> {node.attrib}")
|
||||
for child in node:
|
||||
|
|
Binary file not shown.
|
@ -20,3 +20,17 @@ gunslinger_left.aseprite:
|
|||
gunslinger_right.aseprite:
|
||||
center: 8, 22
|
||||
next: Idle=Idle, Walking=Walking, Hit=Idle, Fire=Reloading
|
||||
|
||||
*.txt:
|
||||
custom-type: aseprite-anim-variation
|
||||
name_regex: (.*)\.txt frames_\1
|
||||
profile: p8
|
||||
next: Idle=Idle, Walking=Walking, Hit=Idle
|
||||
|
||||
fire_slime_left.txt:
|
||||
base: slime_left.aseprite
|
||||
palette: fire_slime_palette.aseprite
|
||||
|
||||
fire_slime_right.txt:
|
||||
base: slime_right.aseprite
|
||||
palette: fire_slime_palette.aseprite
|
||||
|
|
|
@ -13,4 +13,6 @@ wave: 4s 8*bat/2
|
|||
delay: 2s
|
||||
wave: 8s 16*slime/1 8*bat/2
|
||||
delay: 6s
|
||||
wave: 2s 8*slime/1 8*bat/2 2*slime/4
|
||||
wave: 2s 8*slime/1 8*bat/2 2*fire_slime/4
|
||||
delay: 2s
|
||||
wave: 8s 12*bat/2 4*fire_slime/4 1*gunslinger/8
|
||||
|
|
|
@ -57,6 +57,10 @@ ANIM2(gunslinger, Reloading);
|
|||
ANIM2(gunslinger, Fire);
|
||||
ANIM2(gunslinger, Hit);
|
||||
ANIM2(gunslinger, Death);
|
||||
ANIM2(fire_slime, Idle);
|
||||
ANIM2(fire_slime, Walking);
|
||||
ANIM2(fire_slime, Hit);
|
||||
ANIM2(fire_slime, Death);
|
||||
|
||||
/* Player */
|
||||
ANIM4(player, Idle);
|
||||
|
|
|
@ -86,6 +86,10 @@ extern anim_t anims_gunslinger_Reloading;
|
|||
extern anim_t anims_gunslinger_Fire;
|
||||
extern anim_t anims_gunslinger_Hit;
|
||||
extern anim_t anims_gunslinger_Death;
|
||||
extern anim_t anims_fire_slime_Idle;
|
||||
extern anim_t anims_fire_slime_Walking;
|
||||
extern anim_t anims_fire_slime_Hit;
|
||||
extern anim_t anims_fire_slime_Death;
|
||||
|
||||
/* Player */
|
||||
extern anim_t anims_player_Idle;
|
||||
|
|
|
@ -52,6 +52,25 @@ static enemy_t const bat_2 = {
|
|||
.z = fix(0.75),
|
||||
};
|
||||
|
||||
static enemy_t const fire_slime_4 = {
|
||||
.name = "Fire slime",
|
||||
.level = 4,
|
||||
ANIMS(fire_slime, Idle, Walking, Idle, Hit, Death),
|
||||
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
|
||||
.limits = {
|
||||
.max_speed = fix(1),
|
||||
.friction = fix(0.6),
|
||||
},
|
||||
.stats = {
|
||||
.HP = fix(1.4),
|
||||
.ATK = fix(1.2),
|
||||
.MAG = fix(1.4),
|
||||
.DEF = fix(1.4),
|
||||
},
|
||||
.shadow_size = 4,
|
||||
.z = 0,
|
||||
};
|
||||
|
||||
static enemy_t const gunslinger_8 = {
|
||||
.name = "Gunslinger",
|
||||
.level = 8,
|
||||
|
@ -74,6 +93,7 @@ static enemy_t const gunslinger_8 = {
|
|||
static enemy_t const * const enemies[] = {
|
||||
[ENEMY_SLIME_1] = &slime_1,
|
||||
[ENEMY_BAT_2] = &bat_2,
|
||||
[ENEMY_FIRE_SLIME_4] = &fire_slime_4,
|
||||
[ENEMY_GUNSLINGER_8] = &gunslinger_8,
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,8 @@ enum {
|
|||
/* ID 0 is used for the player! */
|
||||
ENEMY_SLIME_1 = 1,
|
||||
ENEMY_BAT_2 = 2,
|
||||
ENEMY_GUNSLINGER_8 = 3,
|
||||
ENEMY_FIRE_SLIME_4 = 3,
|
||||
ENEMY_GUNSLINGER_8 = 4,
|
||||
};
|
||||
|
||||
/* Get enemy data by ID. */
|
||||
|
|
|
@ -525,6 +525,7 @@ int main(void)
|
|||
game_update_animations(&game, dt);
|
||||
game_update_aoes(&game, dt);
|
||||
game_update_particles(&game, dt);
|
||||
// TODO: Kill out-of-bounds entities
|
||||
game_remove_dead_entities(&game);
|
||||
player_f->combo_delay -= dt;
|
||||
|
||||
|
|
Loading…
Reference in New Issue