Improve tactical unit sprite detail

This commit is contained in:
2026-06-29 22:51:26 +09:00
parent 39256985a3
commit 84ab712c03
184 changed files with 168 additions and 16 deletions

View File

@@ -31,7 +31,7 @@ ACTION_COLUMN_OFFSETS = {
for index, action in enumerate(ACTION_ORDER)
}
ACTION_FRAMES_PER_DIRECTION = sum(ACTION_FRAME_COUNTS.values())
PROCESSING_VERSION = "readable-tactical-v1"
PROCESSING_VERSION = "readable-tactical-v2"
Color = tuple[int, int, int]
@@ -42,6 +42,7 @@ DARK_INK: Color = (4, 4, 4)
HIGHLIGHT: Color = (245, 220, 132)
METAL: Color = (207, 213, 202)
METAL_DARK: Color = (92, 98, 91)
LEATHER: Color = (95, 58, 34)
SKIN: Color = (201, 148, 94)
SKIN_DARK: Color = (126, 78, 49)
@@ -162,7 +163,7 @@ def unit_spec(stem: str) -> UnitSpec:
bulk += 0.14
if role in ("archer", "strategist", "shaman"):
bulk -= 0.06
scale = 1.0 + min(rank - 1, 2) * 0.04
scale = 1.06 + min(rank - 1, 2) * 0.04
hair = (44, 31, 23)
skin = SKIN
if faction == "nanman":
@@ -401,9 +402,19 @@ def draw_human(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: fl
(cx - shoulder * 0.62, torso_bottom + 52),
]
draw_poly(draw, cape_points, darken(spec.palette.primary, 0.22), INK, 8)
draw.line(
(cx - shoulder * 0.44, torso_top + 24, cx - shoulder * 0.28, torso_bottom + 42),
fill=darken(spec.palette.primary, 0.38),
width=max(3, round(4 * scale)),
)
draw.line(
(cx + shoulder * 0.38, torso_top + 26, cx + shoulder * 0.2, torso_bottom + 44),
fill=lighten(spec.palette.primary, 0.12),
width=max(2, round(3 * scale)),
)
leg_color = darken(spec.palette.primary, 0.15)
draw_leg_pair(draw, cx, bottom, waist, scale, leg_color, motion.step, side)
draw_leg_pair(draw, cx, bottom, waist, scale, leg_color, motion.step, side, spec)
torso = [
(cx - shoulder, torso_top),
@@ -414,6 +425,30 @@ def draw_human(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: fl
(cx - waist, torso_bottom),
]
draw_poly(draw, torso, spec.palette.primary, INK, 10)
draw_poly(
draw,
[
(cx - shoulder * 0.95, torso_top + 12),
(cx - shoulder * 0.2, torso_top + 10),
(cx - waist * 0.16, torso_bottom + 20),
(cx - waist * 0.76, torso_bottom + 20),
],
darken(spec.palette.primary, 0.17),
darken(spec.palette.primary, 0.32),
2,
)
draw_poly(
draw,
[
(cx + shoulder * 0.18, torso_top + 10),
(cx + shoulder * 0.86, torso_top + 13),
(cx + waist * 0.68, torso_bottom + 20),
(cx + waist * 0.18, torso_bottom + 16),
],
lighten(spec.palette.primary, 0.08),
lighten(spec.palette.primary, 0.16),
2,
)
draw_poly(
draw,
[
@@ -426,8 +461,25 @@ def draw_human(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: fl
INK,
5,
)
draw_poly(
draw,
[
(cx - shoulder * 0.38, torso_top + 10),
(cx, torso_top + 39 * scale),
(cx + shoulder * 0.38, torso_top + 10),
(cx + shoulder * 0.18, torso_top + 20),
(cx, torso_top + 60 * scale),
(cx - shoulder * 0.18, torso_top + 20),
],
spec.palette.cloth if spec.role != "strategist" else (226, 220, 176),
INK,
3,
)
draw_rect(draw, (cx - waist, torso_bottom - 8, cx + waist, torso_bottom + 8), spec.palette.accent, INK, 5)
draw.line((cx - waist * 0.78, torso_bottom + 6, cx + waist * 0.78, torso_bottom + 6), fill=lighten(spec.palette.accent, 0.24), width=3)
draw.line((cx, torso_top + 8, cx, torso_bottom - 8), fill=darken(spec.palette.primary, 0.24), width=5)
if spec.role in ("officer", "lord", "spearman"):
draw_armor_skirt(draw, cx, torso_bottom + 7 * scale, waist, scale, spec)
arm_y = torso_top + 30 * scale
left_hand = (cx - shoulder - 18 * scale, arm_y + 48 * scale + motion.arm * 8)
@@ -440,8 +492,14 @@ def draw_human(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: fl
right_hand = (right_hand[0], right_hand[1] - 42 * motion.raise_arm)
left_hand = (left_hand[0], left_hand[1] - 22 * motion.raise_arm)
draw_limb(draw, (cx - shoulder * 0.78, arm_y), left_hand, spec.palette.cloth, 16)
draw_limb(draw, (cx + shoulder * 0.78, arm_y), right_hand, spec.palette.cloth, 16)
left_shoulder = (cx - shoulder * 0.78, arm_y)
right_shoulder = (cx + shoulder * 0.78, arm_y)
draw_limb(draw, left_shoulder, left_hand, spec.palette.cloth, 16)
draw_limb(draw, right_shoulder, right_hand, spec.palette.cloth, 16)
draw_shoulder_guard(draw, left_shoulder[0], left_shoulder[1] - 1 * scale, scale, spec, -1)
draw_shoulder_guard(draw, right_shoulder[0], right_shoulder[1] - 1 * scale, scale, spec, 1)
draw_cuff(draw, left_hand, scale, spec)
draw_cuff(draw, right_hand, scale, spec)
draw_ellipse(draw, around(left_hand, 10), spec.skin, INK, 4)
draw_ellipse(draw, around(right_hand, 10), spec.skin, INK, 4)
@@ -449,6 +507,8 @@ def draw_human(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: fl
shield_x = left_hand[0] - 6 if side >= 0 else right_hand[0] + 6
shield_y = (left_hand[1] + right_hand[1]) / 2 + 4
draw_shield(draw, shield_x, shield_y, spec)
elif spec.role == "archer":
draw_quiver(draw, cx - shoulder * 0.64, torso_top + 46 * scale, scale, spec)
draw_weapon(draw, spec, direction, left_hand, right_hand, cx, bottom, motion, action)
draw_head(draw, spec, direction, cx + side * 8 * scale, head_y, scale, back)
@@ -462,9 +522,13 @@ def draw_cavalry(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx:
if side:
body = (cx - 82 * scale, bottom - 112 * scale, cx + 78 * scale, bottom - 44 * scale)
draw_round(draw, body, horse_color, INK, 10, 26)
draw_round(draw, (cx - 62 * scale, bottom - 105 * scale, cx + 44 * scale, bottom - 56 * scale), lighten(horse_color, 0.1), darken(horse_color, 0.3), 3, 18)
head_x = cx + side * 88 * scale
draw_ellipse(draw, (head_x - 28 * scale, bottom - 140 * scale, head_x + 28 * scale, bottom - 82 * scale), horse_color, INK, 9)
draw_poly(draw, [(head_x + side * 14 * scale, bottom - 138 * scale), (head_x + side * 40 * scale, bottom - 158 * scale), (head_x + side * 28 * scale, bottom - 118 * scale)], horse_color, INK, 7)
draw.line((head_x - side * 22 * scale, bottom - 118 * scale, head_x + side * 28 * scale, bottom - 102 * scale), fill=darken(horse_color, 0.42), width=max(4, round(5 * scale)))
draw.line((head_x - side * 20 * scale, bottom - 132 * scale, cx + side * 18 * scale, bottom - 102 * scale), fill=INK, width=max(3, round(4 * scale)))
draw_ellipse(draw, around((head_x + side * 10 * scale, bottom - 122 * scale), 4 * scale), DARK_INK, DARK_INK, 1)
draw_limb(draw, (cx - 52 * scale, bottom - 48 * scale), (cx - 68 * scale + motion.step * 12, bottom - 4 * scale), horse_dark, 15)
draw_limb(draw, (cx - 18 * scale, bottom - 46 * scale), (cx - 8 * scale - motion.step * 16, bottom - 2 * scale), horse_dark, 15)
draw_limb(draw, (cx + 26 * scale, bottom - 46 * scale), (cx + 42 * scale + motion.step * 16, bottom - 3 * scale), horse_dark, 15)
@@ -473,12 +537,16 @@ def draw_cavalry(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx:
else:
body = (cx - 62 * scale, bottom - 128 * scale, cx + 62 * scale, bottom - 40 * scale)
draw_round(draw, body, horse_color, INK, 10, 28)
draw_round(draw, (cx - 42 * scale, bottom - 118 * scale, cx + 42 * scale, bottom - 58 * scale), lighten(horse_color, 0.1), darken(horse_color, 0.3), 3, 18)
draw_ellipse(draw, (cx - 40 * scale, bottom - 164 * scale, cx + 40 * scale, bottom - 96 * scale), horse_color, INK, 9)
draw_ellipse(draw, around((cx - 16 * scale, bottom - 130 * scale), 4 * scale), DARK_INK, DARK_INK, 1)
draw_ellipse(draw, around((cx + 16 * scale, bottom - 130 * scale), 4 * scale), DARK_INK, DARK_INK, 1)
draw_limb(draw, (cx - 38 * scale, bottom - 48 * scale), (cx - 56 * scale, bottom - 2 * scale), horse_dark, 16)
draw_limb(draw, (cx + 38 * scale, bottom - 48 * scale), (cx + 56 * scale, bottom - 2 * scale), horse_dark, 16)
draw_limb(draw, (cx - 12 * scale, bottom - 46 * scale), (cx - 16 * scale, bottom - 2 * scale), horse_dark, 13)
draw_limb(draw, (cx + 12 * scale, bottom - 46 * scale), (cx + 16 * scale, bottom - 2 * scale), horse_dark, 13)
draw_saddle(draw, cx, bottom - 98 * scale, scale, spec, side)
rider_bottom = bottom - 88 * scale
rider_spec = UnitSpec(
**{
@@ -493,13 +561,24 @@ def draw_cavalry(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx:
draw_human(draw, rider_spec, direction, cx, rider_bottom, motion, action)
def draw_leg_pair(draw: ImageDraw.ImageDraw, cx: float, bottom: float, waist: float, scale: float, color: Color, step: float, side: int) -> None:
def draw_leg_pair(draw: ImageDraw.ImageDraw, cx: float, bottom: float, waist: float, scale: float, color: Color, step: float, side: int, spec: UnitSpec) -> None:
if side:
draw_limb(draw, (cx - 18 * scale, bottom - 60 * scale), (cx - 34 * scale - step * 6, bottom - 7 * scale), color, 18)
draw_limb(draw, (cx + 20 * scale, bottom - 60 * scale), (cx + 36 * scale + step * 7, bottom - 7 * scale), color, 18)
left_start = (cx - 18 * scale, bottom - 60 * scale)
left_end = (cx - 34 * scale - step * 6, bottom - 7 * scale)
right_start = (cx + 20 * scale, bottom - 60 * scale)
right_end = (cx + 36 * scale + step * 7, bottom - 7 * scale)
else:
draw_limb(draw, (cx - waist * 0.45, bottom - 62 * scale), (cx - 36 * scale - step * 7, bottom - 8 * scale), color, 18)
draw_limb(draw, (cx + waist * 0.45, bottom - 62 * scale), (cx + 36 * scale + step * 7, bottom - 8 * scale), color, 18)
left_start = (cx - waist * 0.45, bottom - 62 * scale)
left_end = (cx - 36 * scale - step * 7, bottom - 8 * scale)
right_start = (cx + waist * 0.45, bottom - 62 * scale)
right_end = (cx + 36 * scale + step * 7, bottom - 8 * scale)
draw_limb(draw, left_start, left_end, color, 18)
draw_limb(draw, right_start, right_end, color, 18)
draw_limb(draw, (left_start[0], left_start[1] + 25 * scale), (left_end[0], left_end[1] + 1 * scale), darken(color, 0.18), 6)
draw_limb(draw, (right_start[0], right_start[1] + 25 * scale), (right_end[0], right_end[1] + 1 * scale), lighten(color, 0.08), 5)
if spec.rank > 1:
draw_rect(draw, (cx - 50 * scale, bottom - 58 * scale, cx - 20 * scale, bottom - 37 * scale), spec.palette.armor, INK, 3)
draw_rect(draw, (cx + 20 * scale, bottom - 58 * scale, cx + 50 * scale, bottom - 37 * scale), spec.palette.armor, INK, 3)
draw_ellipse(draw, (cx - 58 * scale, bottom - 18 * scale, cx - 16 * scale, bottom + 2 * scale), darken(color, 0.2), INK, 4)
draw_ellipse(draw, (cx + 16 * scale, bottom - 18 * scale, cx + 58 * scale, bottom + 2 * scale), darken(color, 0.2), INK, 4)
@@ -507,22 +586,39 @@ def draw_leg_pair(draw: ImageDraw.ImageDraw, cx: float, bottom: float, waist: fl
def draw_head(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: float, cy: float, scale: float, back: bool) -> None:
r = 27 * scale
draw_ellipse(draw, (cx - r, cy - r, cx + r, cy + r), spec.skin, INK, 8)
if not back:
draw_ellipse(draw, (cx - r * 0.42, cy - r * 0.18, cx + r * 0.78, cy + r * 0.62), lighten(spec.skin, 0.16), spec.skin, 1)
draw.line((cx - r * 0.62, cy + r * 0.1, cx + r * 0.64, cy + r * 0.12), fill=SKIN_DARK, width=max(2, round(2 * scale)))
if spec.turban:
draw_round(draw, (cx - r * 1.15, cy - r * 0.98, cx + r * 1.15, cy - r * 0.33), spec.palette.accent, INK, 5, 10)
draw.line((cx - r * 0.9, cy - r * 0.36, cx + r * 0.9, cy - r * 0.36), fill=darken(spec.palette.accent, 0.25), width=5)
knot_x = cx + r * 0.82
draw_poly(draw, [(knot_x, cy - r * 0.72), (knot_x + r * 0.36, cy - r * 0.42), (knot_x, cy - r * 0.25)], spec.palette.accent, INK, 3)
elif spec.helmet:
draw_poly(draw, [(cx - r * 1.08, cy - r * 0.45), (cx, cy - r * 1.34), (cx + r * 1.08, cy - r * 0.45)], spec.palette.armor, INK, 6)
draw_rect(draw, (cx - r * 0.92, cy - r * 0.58, cx + r * 0.92, cy - r * 0.22), spec.palette.accent, INK, 4)
if spec.rank > 1:
draw.line((cx, cy - r * 1.32, cx, cy - r * 1.78), fill=INK, width=max(4, round(5 * scale)))
draw.line((cx, cy - r * 1.32, cx, cy - r * 1.72), fill=spec.palette.accent, width=max(2, round(3 * scale)))
else:
draw_ellipse(draw, (cx - r * 1.08, cy - r * 1.1, cx + r * 1.08, cy - r * 0.1), spec.hair, INK, 5)
draw_ellipse(draw, (cx - r * 0.35, cy - r * 1.48, cx + r * 0.35, cy - r * 0.92), spec.hair, INK, 4)
if not back:
eye_y = cy - r * 0.04
draw.line((cx - r * 0.52, eye_y, cx - r * 0.18, eye_y + 1), fill=DARK_INK, width=4)
draw.line((cx + r * 0.18, eye_y + 1, cx + r * 0.52, eye_y), fill=DARK_INK, width=4)
if spec.stem in ("unit-guan-yu", "unit-zhang-fei"):
draw.line((cx - r * 0.38, cy + r * 0.42, cx + r * 0.38, cy + r * 0.42), fill=spec.hair, width=8)
draw.line((cx, cy + r * 0.42, cx, cy + r * 1.05), fill=spec.hair, width=8)
brow = 5 if spec.role in ("lord", "officer", "spearman", "cavalry") else 4
draw.line((cx - r * 0.58, eye_y - r * 0.14, cx - r * 0.14, eye_y - r * 0.04), fill=DARK_INK, width=brow)
draw.line((cx + r * 0.14, eye_y - r * 0.04, cx + r * 0.58, eye_y - r * 0.14), fill=DARK_INK, width=brow)
draw.line((cx - r * 0.48, eye_y + r * 0.05, cx - r * 0.22, eye_y + r * 0.06), fill=DARK_INK, width=3)
draw.line((cx + r * 0.22, eye_y + r * 0.06, cx + r * 0.48, eye_y + r * 0.05), fill=DARK_INK, width=3)
draw.line((cx + r * 0.02, cy + r * 0.05, cx - r * 0.06, cy + r * 0.25), fill=SKIN_DARK, width=3)
if spec.stem in ("unit-guan-yu", "unit-zhang-fei", "unit-cao-cao", "unit-lu-bu") or spec.role == "officer":
beard_color = spec.hair
if spec.stem == "unit-guan-yu":
beard_color = (31, 24, 18)
draw.line((cx - r * 0.44, cy + r * 0.39, cx + r * 0.44, cy + r * 0.39), fill=beard_color, width=max(6, round(7 * scale)))
draw.line((cx, cy + r * 0.42, cx, cy + r * 1.12), fill=beard_color, width=max(6, round(8 * scale)))
draw.line((cx - r * 0.22, cy + r * 0.56, cx - r * 0.1, cy + r * 0.95), fill=lighten(beard_color, 0.15), width=2)
def draw_weapon(
@@ -549,6 +645,7 @@ def draw_weapon(
start = (cx + 45, bottom - 38)
end = (cx - 38, bottom - 238)
draw_limb(draw, start, end, (92, 59, 31), 11)
draw_limb(draw, (start[0], start[1] - 8), (end[0], end[1] + 8), (143, 91, 44), 4)
blade = 30 if spec.weapon != "glaive" else 42
draw_poly(
draw,
@@ -562,8 +659,16 @@ def draw_weapon(
INK,
5,
)
draw.line((end[0] - 6, end[1] - blade * 0.64, end[0] + 8, end[1] + 8), fill=(246, 249, 232), width=3)
if spec.weapon in ("spear", "glaive"):
tassel_color = (196, 38, 34) if spec.faction != "rebel" else spec.palette.accent
draw.line((end[0] - 17, end[1] + 22, end[0] - 32, end[1] + 48), fill=INK, width=7)
draw.line((end[0] - 17, end[1] + 22, end[0] - 32, end[1] + 48), fill=tassel_color, width=4)
draw.line((end[0] + 17, end[1] + 22, end[0] + 31, end[1] + 46), fill=INK, width=7)
draw.line((end[0] + 17, end[1] + 22, end[0] + 31, end[1] + 46), fill=lighten(tassel_color, 0.14), width=4)
if spec.weapon == "glaive":
draw_poly(draw, [(end[0] + 5, end[1] - 6), (end[0] + 40, end[1] + 22), (end[0] + 8, end[1] + 35)], METAL, INK, 5)
draw.line((end[0] + 11, end[1] + 1, end[0] + 31, end[1] + 20), fill=(246, 249, 232), width=3)
elif spec.weapon in ("bow", "crossbow"):
bow_cx = hand[0] + (side or 1) * 22
bow_cy = hand[1] - 22
@@ -575,6 +680,7 @@ def draw_weapon(
bbox = (bow_cx - 42, bow_cy - 72, bow_cx + 42, bow_cy + 72)
draw.arc(bbox, 255 if side < 0 else -75, 105 if side < 0 else 75, fill=INK, width=14)
draw.arc(bbox, 255 if side < 0 else -75, 105 if side < 0 else 75, fill=(118, 68, 34), width=7)
draw.arc((bbox[0] + 7, bbox[1] + 7, bbox[2] - 7, bbox[3] - 7), 255 if side < 0 else -75, 105 if side < 0 else 75, fill=(193, 122, 55), width=3)
draw.line((bow_cx, bow_cy - 68, bow_cx, bow_cy + 68), fill=METAL_DARK, width=3)
draw_limb(draw, (bow_cx - 48 * (side or -1), bow_cy), (bow_cx + 62 * (side or 1), bow_cy - 8), METAL, 5)
elif spec.weapon == "fan":
@@ -589,6 +695,7 @@ def draw_weapon(
if direction == "west":
fan_points = [(2 * fan_cx - x, y) for x, y in fan_points]
draw_poly(draw, fan_points, (230, 220, 172), INK, 6)
draw_poly(draw, [(fan_cx, fan_cy), fan_points[1], fan_points[2]], (246, 237, 190), (205, 184, 96), 2)
for angle in (-36, -15, 8, 28):
dx = math.cos(math.radians(angle)) * 66
dy = math.sin(math.radians(angle)) * 66
@@ -603,6 +710,7 @@ def draw_weapon(
else:
end = (hand[0] + 52, hand[1] - 74)
draw_limb(draw, hand, end, METAL, 10)
draw_limb(draw, hand, end, (246, 249, 232), 4)
draw_rect(draw, (hand[0] - 22, hand[1] - 8, hand[0] + 22, hand[1] + 8), spec.palette.accent, INK, 4)
@@ -610,6 +718,8 @@ def draw_shield(draw: ImageDraw.ImageDraw, cx: float, cy: float, spec: UnitSpec)
points = [(cx, cy - 42), (cx + 34, cy - 22), (cx + 24, cy + 38), (cx, cy + 54), (cx - 24, cy + 38), (cx - 34, cy - 22)]
draw_poly(draw, points, spec.palette.armor, INK, 7)
draw.line((cx, cy - 30, cx, cy + 38), fill=spec.palette.accent, width=5)
draw.line((cx - 20, cy - 14, cx + 20, cy + 2), fill=lighten(spec.palette.armor, 0.18), width=4)
draw_ellipse(draw, around((cx, cy + 6), 8), spec.palette.accent, INK, 3)
def draw_banner(draw: ImageDraw.ImageDraw, x: float, y: float, spec: UnitSpec) -> None:
@@ -617,6 +727,48 @@ def draw_banner(draw: ImageDraw.ImageDraw, x: float, y: float, spec: UnitSpec) -
flag = [(x, y - 34), (x + 58, y - 22), (x + 46, y + 28), (x, y + 18)]
draw_poly(draw, flag, spec.palette.accent, INK, 5)
draw.line((x + 11, y - 13, x + 42, y - 7), fill=darken(spec.palette.accent, 0.35), width=5)
draw.line((x + 8, y + 8, x + 39, y + 12), fill=lighten(spec.palette.accent, 0.2), width=4)
def draw_armor_skirt(draw: ImageDraw.ImageDraw, cx: float, y: float, waist: float, scale: float, spec: UnitSpec) -> None:
plate_w = 20 * scale
for index, offset in enumerate((-0.56, 0, 0.56)):
x = cx + waist * offset
fill = spec.palette.armor if index != 1 else lighten(spec.palette.armor, 0.08)
points = [(x - plate_w, y - 8 * scale), (x + plate_w, y - 8 * scale), (x + plate_w * 0.74, y + 34 * scale), (x - plate_w * 0.74, y + 34 * scale)]
draw_poly(draw, points, fill, INK, 3)
draw.line((x, y - 3 * scale, x, y + 26 * scale), fill=darken(fill, 0.25), width=max(2, round(2 * scale)))
def draw_shoulder_guard(draw: ImageDraw.ImageDraw, x: float, y: float, scale: float, spec: UnitSpec, side: int) -> None:
box = (x - 22 * scale, y - 17 * scale, x + 22 * scale, y + 15 * scale)
draw_ellipse(draw, box, spec.palette.armor, INK, 4)
draw.line((x - side * 14 * scale, y - 6 * scale, x + side * 12 * scale, y + 4 * scale), fill=lighten(spec.palette.armor, 0.18), width=max(2, round(3 * scale)))
def draw_cuff(draw: ImageDraw.ImageDraw, hand: tuple[float, float], scale: float, spec: UnitSpec) -> None:
draw_rect(
draw,
(hand[0] - 14 * scale, hand[1] - 15 * scale, hand[0] + 14 * scale, hand[1] - 2 * scale),
darken(spec.palette.cloth, 0.14),
INK,
2,
)
def draw_quiver(draw: ImageDraw.ImageDraw, x: float, y: float, scale: float, spec: UnitSpec) -> None:
draw_round(draw, (x - 13 * scale, y - 24 * scale, x + 17 * scale, y + 50 * scale), LEATHER, INK, 4, 8)
for offset in (-9, 0, 9):
draw_limb(draw, (x + offset * scale, y - 42 * scale), (x + offset * scale + 8 * scale, y - 12 * scale), METAL, 3)
draw.line((x - 6 * scale, y - 12 * scale, x + 10 * scale, y + 32 * scale), fill=lighten(LEATHER, 0.18), width=max(2, round(3 * scale)))
def draw_saddle(draw: ImageDraw.ImageDraw, cx: float, y: float, scale: float, spec: UnitSpec, side: int) -> None:
draw_round(draw, (cx - 46 * scale, y - 19 * scale, cx + 46 * scale, y + 18 * scale), darken(spec.palette.cloth, 0.18), INK, 5, 10)
draw_round(draw, (cx - 32 * scale, y - 12 * scale, cx + 32 * scale, y + 12 * scale), spec.palette.accent, darken(spec.palette.accent, 0.34), 2, 8)
if side:
draw.line((cx - side * 14 * scale, y + 13 * scale, cx - side * 22 * scale, y + 43 * scale), fill=INK, width=max(3, round(4 * scale)))
draw.line((cx - side * 14 * scale, y + 13 * scale, cx - side * 22 * scale, y + 43 * scale), fill=LEATHER, width=max(2, round(2 * scale)))
def draw_attack_effect(draw: ImageDraw.ImageDraw, spec: UnitSpec, direction: str, cx: float, bottom: float, frame_index: int, frame_count: int) -> None: