Improve battle unit immersion

This commit is contained in:
2026-06-28 23:15:45 +09:00
parent 66fc9984e5
commit 501a97706f
185 changed files with 196 additions and 65 deletions

View File

@@ -44,25 +44,25 @@ class TemplateVariant:
TEMPLATE_VARIANTS = (
TemplateVariant("unit-shu-archer", "unit-rebel-archer", (54, 132, 82), 0.2, 1.05),
TemplateVariant("unit-shu-cavalry", "unit-ma-chao", (58, 126, 78), 0.18, 1.02),
TemplateVariant("unit-shu-infantry", "unit-rebel", (56, 132, 78), 0.24, 1.06),
TemplateVariant("unit-shu-officer", "unit-guan-yu", (58, 132, 82), 0.16, 1.02),
TemplateVariant("unit-shu-spearman", "unit-zhang-fei", (70, 122, 86), 0.14, 1.02),
TemplateVariant("unit-shu-strategist", "unit-zhuge-liang", (74, 138, 102), 0.16, 1.03, 1.02),
TemplateVariant("unit-wei-archer", "unit-rebel-archer", (70, 84, 118), 0.24, 1.02, 0.98),
TemplateVariant("unit-wei-cavalry", "unit-ma-chao", (72, 78, 114), 0.24, 1.0, 0.98),
TemplateVariant("unit-wei-infantry", "unit-rebel", (68, 78, 112), 0.26, 1.0, 0.98),
TemplateVariant("unit-wei-officer", "unit-cao-cao", (76, 74, 116), 0.18, 1.0, 0.98),
TemplateVariant("unit-wei-strategist", "unit-zhuge-liang", (78, 92, 140), 0.22, 1.0, 0.98),
TemplateVariant("unit-wu-archer", "unit-rebel-archer", (42, 112, 120), 0.24, 1.05),
TemplateVariant("unit-wu-cavalry", "unit-ma-chao", (42, 110, 118), 0.22, 1.04),
TemplateVariant("unit-wu-infantry", "unit-rebel", (42, 116, 118), 0.26, 1.05),
TemplateVariant("unit-wu-officer", "unit-lu-meng", (46, 116, 128), 0.18, 1.02),
TemplateVariant("unit-wu-strategist", "unit-zhuge-liang", (42, 118, 132), 0.22, 1.04),
TemplateVariant("unit-nanman-infantry", "unit-rebel", (118, 112, 46), 0.28, 1.08),
TemplateVariant("unit-nanman-officer", "unit-meng-huo", (126, 100, 48), 0.2, 1.07),
TemplateVariant("unit-nanman-shaman", "unit-zhuge-liang", (118, 114, 48), 0.26, 1.08),
TemplateVariant("unit-shu-archer", "unit-rebel-archer", (52, 108, 74), 0.18, 0.88, 0.98),
TemplateVariant("unit-shu-cavalry", "unit-ma-chao", (54, 104, 72), 0.16, 0.86, 0.98),
TemplateVariant("unit-shu-infantry", "unit-rebel", (54, 108, 72), 0.2, 0.88, 0.98),
TemplateVariant("unit-shu-officer", "unit-guan-yu", (56, 108, 76), 0.14, 0.86, 0.98),
TemplateVariant("unit-shu-spearman", "unit-zhang-fei", (68, 104, 82), 0.13, 0.86, 0.98),
TemplateVariant("unit-shu-strategist", "unit-zhuge-liang", (70, 112, 92), 0.14, 0.86, 0.99),
TemplateVariant("unit-wei-archer", "unit-rebel-archer", (68, 76, 104), 0.2, 0.86, 0.96),
TemplateVariant("unit-wei-cavalry", "unit-ma-chao", (70, 72, 102), 0.2, 0.84, 0.96),
TemplateVariant("unit-wei-infantry", "unit-rebel", (66, 72, 100), 0.22, 0.84, 0.96),
TemplateVariant("unit-wei-officer", "unit-cao-cao", (74, 70, 104), 0.16, 0.84, 0.96),
TemplateVariant("unit-wei-strategist", "unit-zhuge-liang", (74, 84, 116), 0.18, 0.84, 0.96),
TemplateVariant("unit-wu-archer", "unit-rebel-archer", (42, 98, 104), 0.2, 0.88, 0.98),
TemplateVariant("unit-wu-cavalry", "unit-ma-chao", (42, 96, 104), 0.18, 0.86, 0.98),
TemplateVariant("unit-wu-infantry", "unit-rebel", (42, 100, 104), 0.22, 0.88, 0.98),
TemplateVariant("unit-wu-officer", "unit-lu-meng", (46, 100, 110), 0.16, 0.86, 0.98),
TemplateVariant("unit-wu-strategist", "unit-zhuge-liang", (44, 102, 112), 0.18, 0.86, 0.98),
TemplateVariant("unit-nanman-infantry", "unit-rebel", (104, 96, 48), 0.22, 0.9, 0.98),
TemplateVariant("unit-nanman-officer", "unit-meng-huo", (108, 88, 50), 0.17, 0.88, 0.98),
TemplateVariant("unit-nanman-shaman", "unit-zhuge-liang", (104, 98, 52), 0.2, 0.88, 0.98),
)
@@ -71,9 +71,10 @@ def main() -> None:
cleanup_only = "--cleanup-only" in sys.argv
optimize_only = "--optimize-only" in sys.argv
stabilize_motion = "--stabilize-motion" in sys.argv
selected_modes = sum(1 for enabled in (generic_only, cleanup_only, optimize_only, stabilize_motion) if enabled)
tone_only = "--tone-only" in sys.argv
selected_modes = sum(1 for enabled in (generic_only, cleanup_only, optimize_only, stabilize_motion, tone_only) if enabled)
if selected_modes > 1:
raise ValueError("Use only one of --generic-only, --cleanup-only, --optimize-only, or --stabilize-motion")
raise ValueError("Use only one of --generic-only, --cleanup-only, --optimize-only, --stabilize-motion, or --tone-only")
if generic_only:
sheets = load_current_base_sheets()
@@ -92,6 +93,10 @@ def main() -> None:
stabilize_current_motion_sheets()
return
if tone_only:
tone_current_unit_pngs()
return
reference = Image.open(REFERENCE_SHEET).convert("RGBA")
targets = reference_targets(reference)
generated = 0
@@ -152,6 +157,15 @@ def optimize_current_unit_pngs() -> None:
print(f"Optimized {optimized} unit PNGs with {PALETTE_COLORS}-color palettes")
def tone_current_unit_pngs() -> None:
toned = 0
for source in sorted(UNIT_DIR.glob("unit-*.png")):
image = Image.open(source).convert("RGBA")
save_unit_png(stabilize_palette_image(image), source)
toned += 1
print(f"Applied muted battlefield tone to {toned} unit PNGs")
def stabilize_current_motion_sheets() -> None:
reference = Image.open(REFERENCE_SHEET).convert("RGBA")
targets = stable_reference_targets(reference_targets(reference))
@@ -291,11 +305,26 @@ def write_template_variants(sheets: dict[str, Image.Image]) -> None:
def save_unit_png(image: Image.Image, path: Path) -> None:
image = image.convert("RGBA")
image = stabilize_palette_image(image.convert("RGBA"))
indexed = image.quantize(colors=PALETTE_COLORS, method=Image.Quantize.FASTOCTREE, dither=Image.Dither.NONE)
indexed.save(path, optimize=True)
def stabilize_palette_image(image: Image.Image) -> Image.Image:
image = image.convert("RGBA")
red, green, blue, alpha = image.split()
rgb = Image.merge("RGB", (red, green, blue))
rgb = ImageEnhance.Color(rgb).enhance(0.76)
rgb = ImageEnhance.Contrast(rgb).enhance(0.97)
rgb = ImageEnhance.Brightness(rgb).enhance(0.98)
luma = ImageOps.grayscale(rgb)
battlefield = ImageOps.colorize(luma, black=(22, 20, 22), white=(210, 194, 158), mid=(104, 91, 73))
tone_mask = alpha.point(lambda value: min(46, round(value * 0.16)))
rgb = Image.composite(battlefield, rgb, tone_mask)
return Image.merge("RGBA", (*rgb.split(), alpha))
def template_variant_for(stem: str) -> TemplateVariant | None:
for variant in TEMPLATE_VARIANTS:
if stem.startswith(variant.prefix) and stem != variant.template:
@@ -523,36 +552,36 @@ def colored_mask(alpha: Image.Image, color: tuple[int, int, int, int]) -> Image.
def style_for_unit(stem: str) -> UnitStyle:
lower = stem.lower()
scale = 0.98
saturation = 1.14
contrast = 1.08
brightness = 1.0
edge_warmth = 1.0
rim_color = (48, 12, 76)
saturation = 0.9
contrast = 1.04
brightness = 0.98
edge_warmth = 0.68
rim_color = (31, 25, 35)
if any(token in lower for token in ("archer", "crossbow", "longbow")):
scale = 0.94
saturation = 1.12
saturation = 0.88
elif any(token in lower for token in ("cavalry", "ma-chao", "ma-dai", "lu-bu")):
scale = 1.03
contrast = 1.1
contrast = 1.05
elif any(token in lower for token in ("strategist", "shaman", "zhuge", "sima", "fa-zheng", "ma-liang", "yi-ji")):
scale = 0.93
brightness = 1.03
rim_color = (38, 22, 86)
brightness = 0.99
rim_color = (29, 28, 41)
elif any(token in lower for token in ("leader", "officer", "meng-huo", "guan-yu", "zhang-fei", "jiang-wei")):
scale = 1.01
contrast = 1.1
edge_warmth = 1.12
contrast = 1.05
edge_warmth = 0.78
if "nanman" in lower or "meng-huo" in lower:
saturation = 1.18
rim_color = (58, 18, 63)
saturation = 0.92
rim_color = (38, 28, 34)
elif "wu-" in lower or "lu-meng" in lower:
rim_color = (28, 18, 72)
rim_color = (25, 31, 37)
elif "wei-" in lower or "cao-cao" in lower or "sima" in lower:
rim_color = (43, 10, 78)
rim_color = (30, 28, 40)
elif "shu-" in lower or any(token in lower for token in ("liu-bei", "guan-yu", "zhang-fei", "zhao-yun")):
rim_color = (34, 24, 72)
rim_color = (28, 30, 38)
return UnitStyle(scale, saturation, contrast, brightness, edge_warmth, rim_color)