From 19af1359d1d2eb18a8287fd1ec23d1dbafa9795d Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 19 Jun 2026 16:01:01 +0900 Subject: [PATCH] Improve pixel map unit silhouettes --- scripts/scenes/battle_scene.gd | 243 +++++++++++++++++++++++------- tools/smoke_chapter_one_polish.gd | 54 +++++++ 2 files changed, 246 insertions(+), 51 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index c4fbe62..f499c4a 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -3199,11 +3199,12 @@ func _unit_sprite_modulate(unit: Dictionary, acted: bool) -> Color: func _draw_pixel_unit_sprite(rect: Rect2, unit: Dictionary, team_color: Color, modulate: Color) -> void: - var family := _unit_class_family(unit) + var profile := _pixel_unit_sprite_profile(unit) + var family := str(profile.get("family", _unit_class_family(unit))) var phase := _unit_idle_phase(unit) - var bob := roundf(sin(phase) * 1.25) - var step := 1.0 if sin(phase * 1.65) >= 0.0 else -1.0 - var pixel := 3.0 + var bob := _unit_idle_bob(family, phase) + var step := _unit_idle_step(family, phase) + var pixel := float(profile.get("pixel", 3.0)) var origin := Vector2(roundf(rect.position.x + 12.0), roundf(rect.position.y + 5.0 + bob)) var outline := _unit_pixel_color(Color(0.035, 0.026, 0.018, 1.0), modulate) var skin := _unit_pixel_color(Color(0.86, 0.62, 0.38, 1.0), modulate) @@ -3233,6 +3234,84 @@ func _unit_idle_phase(unit: Dictionary) -> float: return float(Time.get_ticks_msec()) * 0.0024 + float(seed % 360) * 0.01745 +func _unit_idle_bob(family: String, phase: float) -> float: + if family == "cavalry": + return roundf(sin(phase * 1.55) * 0.75) + if family == "ranged": + return roundf(sin(phase * 1.10) * 0.75) + if family == "tactic": + return roundf(sin(phase * 0.82) * 0.65) + if family == "heavy": + return roundf(sin(phase * 0.72) * 0.45) + return roundf(sin(phase) * 1.15) + + +func _unit_idle_step(family: String, phase: float) -> float: + var rate := 1.65 + if family == "cavalry": + rate = 2.15 + elif family == "ranged": + rate = 1.25 + elif family == "tactic": + rate = 0.95 + elif family == "heavy": + rate = 0.78 + return 1.0 if sin(phase * rate) >= 0.0 else -1.0 + + +func _pixel_unit_sprite_profile(unit: Dictionary) -> Dictionary: + var family := _unit_class_family(unit) + if family == "cavalry": + return { + "family": family, + "pixel": 3.0, + "min_x": 0.0, + "max_x": 15.0, + "max_y": 14.0, + "weapon": "banner", + "idle": "hoof_cycle" + } + if family == "ranged": + return { + "family": family, + "pixel": 3.0, + "min_x": 1.0, + "max_x": 14.0, + "max_y": 13.0, + "weapon": "bow", + "idle": "bow_sway" + } + if family == "tactic": + return { + "family": family, + "pixel": 3.0, + "min_x": 1.0, + "max_x": 13.0, + "max_y": 14.0, + "weapon": "staff", + "idle": "robe_pulse" + } + if family == "heavy": + return { + "family": family, + "pixel": 3.0, + "min_x": 1.0, + "max_x": 14.0, + "max_y": 14.0, + "weapon": "heavy_blade", + "idle": "weight_shift" + } + return { + "family": family, + "pixel": 3.0, + "min_x": 1.0, + "max_x": 12.0, + "max_y": 13.0, + "weapon": "shield_spear", + "idle": "march_step" + } + + func _unit_pixel_color(color: Color, modulate: Color) -> Color: return Color( color.r * modulate.r, @@ -3246,74 +3325,136 @@ func _draw_pixel_rect(origin: Vector2, x: float, y: float, width: float, height: draw_rect(Rect2(origin + Vector2(x, y) * pixel, Vector2(width, height) * pixel), color) +func _draw_pixel_shield(origin: Vector2, x: float, y: float, pixel: float, outline: Color, face: Color, trim: Color) -> void: + _draw_pixel_rect(origin, x + 1, y, 2, 1, pixel, outline) + _draw_pixel_rect(origin, x, y + 1, 4, 3, pixel, outline) + _draw_pixel_rect(origin, x + 1, y + 4, 2, 1, pixel, outline) + _draw_pixel_rect(origin, x + 1, y + 1, 2, 3, pixel, face) + _draw_pixel_rect(origin, x + 2, y + 1, 1, 3, pixel, trim) + + +func _draw_pixel_spear(origin: Vector2, x: float, y: float, pixel: float, shaft: Color, blade: Color) -> void: + _draw_pixel_rect(origin, x, y, 1, 9, pixel, shaft) + _draw_pixel_rect(origin, x - 1, y - 1, 3, 1, pixel, blade) + _draw_pixel_rect(origin, x, y - 2, 1, 1, pixel, blade.lightened(0.20)) + + +func _draw_pixel_bow(origin: Vector2, x: float, y: float, pixel: float, wood: Color, string_color: Color, arrow: Color) -> void: + _draw_pixel_rect(origin, x + 1, y, 2, 1, pixel, wood) + _draw_pixel_rect(origin, x + 3, y + 1, 1, 2, pixel, wood) + _draw_pixel_rect(origin, x + 4, y + 3, 1, 3, pixel, wood) + _draw_pixel_rect(origin, x + 3, y + 6, 1, 2, pixel, wood) + _draw_pixel_rect(origin, x + 1, y + 8, 2, 1, pixel, wood) + _draw_pixel_rect(origin, x + 1, y + 1, 1, 7, pixel, string_color) + _draw_pixel_rect(origin, x - 3, y + 4, 6, 1, pixel, arrow) + _draw_pixel_rect(origin, x + 3, y + 3, 1, 3, pixel, arrow.lightened(0.10)) + + +func _draw_pixel_horse_tack(origin: Vector2, pixel: float, cloth: Color, trim: Color) -> void: + _draw_pixel_rect(origin, 4, 6, 4, 1, pixel, trim) + _draw_pixel_rect(origin, 4, 7, 4, 2, pixel, cloth) + _draw_pixel_rect(origin, 8, 7, 1, 2, pixel, trim) + + +func _draw_pixel_banner(origin: Vector2, x: float, y: float, pixel: float, pole: Color, cloth: Color, trim: Color) -> void: + _draw_pixel_rect(origin, x, y, 1, 9, pixel, pole) + _draw_pixel_rect(origin, x + 1, y, 3, 2, pixel, cloth) + _draw_pixel_rect(origin, x + 1, y + 2, 2, 1, pixel, cloth.darkened(0.12)) + _draw_pixel_rect(origin, x + 3, y + 2, 1, 1, pixel, trim) + + +func _draw_pixel_staff(origin: Vector2, x: float, y: float, pixel: float, staff: Color, tassel: Color) -> void: + _draw_pixel_rect(origin, x, y, 1, 9, pixel, staff) + _draw_pixel_rect(origin, x - 1, y, 3, 1, pixel, tassel) + _draw_pixel_rect(origin, x, y - 1, 1, 1, pixel, tassel.lightened(0.18)) + _draw_pixel_rect(origin, x + 1, y + 2, 1, 2, pixel, tassel.darkened(0.18)) + + +func _draw_pixel_heavy_blade(origin: Vector2, x: float, y: float, pixel: float, handle: Color, blade: Color) -> void: + _draw_pixel_rect(origin, x, y + 1, 1, 7, pixel, handle) + _draw_pixel_rect(origin, x - 1, y, 3, 1, pixel, handle.darkened(0.10)) + _draw_pixel_rect(origin, x - 2, y - 1, 3, 2, pixel, blade) + _draw_pixel_rect(origin, x - 3, y, 1, 2, pixel, blade.darkened(0.10)) + _draw_pixel_rect(origin, x - 1, y + 1, 2, 1, pixel, blade.lightened(0.16)) + + func _draw_pixel_infantry(origin: Vector2, pixel: float, outline: Color, skin: Color, cloth: Color, cloth_dark: Color, class_color: Color, accent: Color, step: float) -> void: - _draw_pixel_rect(origin, 4, 1, 4, 2, pixel, outline) - _draw_pixel_rect(origin, 5, 2, 2, 2, pixel, skin) - _draw_pixel_rect(origin, 3, 4, 6, 5, pixel, outline) - _draw_pixel_rect(origin, 4, 4, 4, 4, pixel, cloth) - _draw_pixel_rect(origin, 3, 6, 2, 3, pixel, class_color) - _draw_pixel_rect(origin, 7, 6, 2, 3, pixel, cloth_dark) - _draw_pixel_rect(origin, 4 + step, 9, 2, 3, pixel, outline) - _draw_pixel_rect(origin, 7 - step, 9, 2, 3, pixel, outline) - _draw_pixel_rect(origin, 1, 4, 1, 8, pixel, accent.lightened(0.15)) - _draw_pixel_rect(origin, 0, 3, 3, 1, pixel, accent) + _draw_pixel_rect(origin, 4, 1, 4, 1, pixel, accent) + _draw_pixel_rect(origin, 4, 2, 4, 2, pixel, outline) + _draw_pixel_rect(origin, 5, 3, 2, 2, pixel, skin) + _draw_pixel_rect(origin, 3, 5, 6, 5, pixel, outline) + _draw_pixel_rect(origin, 4, 5, 4, 4, pixel, cloth) + _draw_pixel_rect(origin, 5, 6, 2, 3, pixel, class_color) + _draw_pixel_shield(origin, 1, 5, pixel, outline, class_color.darkened(0.12), accent) + _draw_pixel_spear(origin, 10 + step, 2, pixel, accent.darkened(0.12), accent.lightened(0.18)) + _draw_pixel_rect(origin, 4 + step, 10, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 7 - step, 10, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 4 + step, 10, 2, 1, pixel, cloth_dark) + _draw_pixel_rect(origin, 7 - step, 10, 2, 1, pixel, cloth_dark) func _draw_pixel_archer(origin: Vector2, pixel: float, outline: Color, skin: Color, cloth: Color, cloth_dark: Color, class_color: Color, accent: Color, step: float) -> void: _draw_pixel_rect(origin, 4, 1, 4, 2, pixel, outline) - _draw_pixel_rect(origin, 5, 2, 2, 2, pixel, skin) - _draw_pixel_rect(origin, 3, 4, 6, 5, pixel, outline) - _draw_pixel_rect(origin, 4, 4, 4, 4, pixel, cloth) - _draw_pixel_rect(origin, 5, 5, 2, 4, pixel, class_color) - _draw_pixel_rect(origin, 4 + step, 9, 2, 3, pixel, outline) - _draw_pixel_rect(origin, 7 - step, 9, 2, 3, pixel, outline) - draw_line(origin + Vector2(9, 3) * pixel, origin + Vector2(11, 7) * pixel, accent, 2.0) - draw_line(origin + Vector2(11, 7) * pixel, origin + Vector2(9, 11) * pixel, accent, 2.0) - draw_line(origin + Vector2(4, 6) * pixel, origin + Vector2(11, 7) * pixel, accent.lightened(0.15), 1.3) - _draw_pixel_rect(origin, 2, 5, 2, 1, pixel, cloth_dark) + _draw_pixel_rect(origin, 4, 2, 4, 1, pixel, cloth_dark) + _draw_pixel_rect(origin, 5, 3, 2, 2, pixel, skin) + _draw_pixel_rect(origin, 3, 5, 6, 5, pixel, outline) + _draw_pixel_rect(origin, 4, 5, 4, 4, pixel, cloth) + _draw_pixel_rect(origin, 2, 6, 2, 2, pixel, cloth_dark) + _draw_pixel_rect(origin, 5, 6, 2, 3, pixel, class_color) + _draw_pixel_rect(origin, 4 + step, 10, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 7 - step, 10, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 1, 4, 1, 5, pixel, accent.darkened(0.22)) + _draw_pixel_rect(origin, 1, 5, 2, 1, pixel, accent) + _draw_pixel_bow(origin, 8 + step, 2, pixel, accent, outline.lightened(0.28), accent.lightened(0.16)) func _draw_pixel_cavalry(origin: Vector2, pixel: float, outline: Color, skin: Color, cloth: Color, cloth_dark: Color, class_color: Color, accent: Color, step: float) -> void: var horse := _unit_pixel_color(Color(0.33, 0.20, 0.11, 1.0), Color(1.0, 1.0, 1.0, outline.a)) var mane := _unit_pixel_color(Color(0.12, 0.075, 0.04, 1.0), Color(1.0, 1.0, 1.0, outline.a)) - _draw_pixel_rect(origin, 1, 7, 9, 3, pixel, outline) - _draw_pixel_rect(origin, 2, 7, 7, 2, pixel, horse) - _draw_pixel_rect(origin, 8, 5, 3, 3, pixel, outline) - _draw_pixel_rect(origin, 8, 6, 2, 2, pixel, horse) - _draw_pixel_rect(origin, 7, 5, 1, 3, pixel, mane) - _draw_pixel_rect(origin, 2 + step, 10, 2, 3, pixel, outline) - _draw_pixel_rect(origin, 7 - step, 10, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 0, 8, 10, 3, pixel, outline) + _draw_pixel_rect(origin, 1, 8, 8, 2, pixel, horse) + _draw_pixel_rect(origin, 8, 6, 4, 3, pixel, outline) + _draw_pixel_rect(origin, 8, 7, 3, 2, pixel, horse) + _draw_pixel_rect(origin, 7, 6, 1, 3, pixel, mane) + _draw_pixel_rect(origin, 0, 7, 2, 1, pixel, mane) + _draw_pixel_rect(origin, 1 + step, 11, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 4 - step, 11, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 7 + step, 11, 2, 3, pixel, outline) + _draw_pixel_horse_tack(origin, pixel, class_color, accent) _draw_pixel_rect(origin, 4, 2, 4, 2, pixel, outline) _draw_pixel_rect(origin, 5, 3, 2, 2, pixel, skin) _draw_pixel_rect(origin, 4, 5, 4, 3, pixel, cloth) _draw_pixel_rect(origin, 5, 5, 2, 2, pixel, class_color) - draw_line(origin + Vector2(4, 4) * pixel, origin + Vector2(2, 1) * pixel, accent, 2.0) + _draw_pixel_banner(origin, 11, 0, pixel, accent.darkened(0.22), class_color, accent) func _draw_pixel_strategist(origin: Vector2, pixel: float, outline: Color, skin: Color, cloth: Color, cloth_dark: Color, class_color: Color, accent: Color, step: float) -> void: - _draw_pixel_rect(origin, 4, 1, 4, 2, pixel, outline) - _draw_pixel_rect(origin, 5, 2, 2, 2, pixel, skin) - _draw_pixel_rect(origin, 3, 4, 6, 7, pixel, outline) - _draw_pixel_rect(origin, 4, 4, 4, 6, pixel, cloth) - _draw_pixel_rect(origin, 3, 6, 2, 3, pixel, class_color) - _draw_pixel_rect(origin, 7, 6, 2, 3, pixel, class_color) - _draw_pixel_rect(origin, 4 + step, 11, 2, 2, pixel, outline) - _draw_pixel_rect(origin, 7 - step, 11, 2, 2, pixel, outline) - _draw_pixel_rect(origin, 8, 3, 3, 1, pixel, accent) - _draw_pixel_rect(origin, 9, 4, 2, 1, pixel, accent) - _draw_pixel_rect(origin, 2, 5, 1, 5, pixel, cloth_dark) + _draw_pixel_rect(origin, 4, 0, 4, 1, pixel, class_color.lightened(0.20)) + _draw_pixel_rect(origin, 3, 1, 6, 2, pixel, outline) + _draw_pixel_rect(origin, 5, 3, 2, 2, pixel, skin) + _draw_pixel_rect(origin, 3, 5, 6, 7, pixel, outline) + _draw_pixel_rect(origin, 4, 5, 4, 6, pixel, cloth) + _draw_pixel_rect(origin, 3, 7, 2, 3, pixel, class_color) + _draw_pixel_rect(origin, 7, 7, 2, 3, pixel, class_color.darkened(0.08)) + _draw_pixel_rect(origin, 4 + step, 12, 2, 2, pixel, outline) + _draw_pixel_rect(origin, 7 - step, 12, 2, 2, pixel, outline) + _draw_pixel_rect(origin, 1, 6, 2, 4, pixel, cloth_dark) + _draw_pixel_rect(origin, 2, 10, 3, 1, pixel, accent.lightened(0.10)) + _draw_pixel_staff(origin, 10 + step, 3, pixel, accent.darkened(0.22), accent) func _draw_pixel_heavy(origin: Vector2, pixel: float, outline: Color, skin: Color, cloth: Color, cloth_dark: Color, class_color: Color, accent: Color, step: float) -> void: _draw_pixel_rect(origin, 4, 1, 4, 2, pixel, outline) - _draw_pixel_rect(origin, 5, 2, 2, 2, pixel, skin) - _draw_pixel_rect(origin, 2, 4, 8, 6, pixel, outline) - _draw_pixel_rect(origin, 3, 4, 6, 5, pixel, cloth) - _draw_pixel_rect(origin, 4, 5, 4, 4, pixel, class_color) - _draw_pixel_rect(origin, 3 + step, 10, 2, 3, pixel, outline) - _draw_pixel_rect(origin, 8 - step, 10, 2, 3, pixel, outline) - draw_line(origin + Vector2(9, 2) * pixel, origin + Vector2(2, 10) * pixel, accent, 3.0) - _draw_pixel_rect(origin, 8, 1, 3, 2, pixel, accent.lightened(0.20)) + _draw_pixel_rect(origin, 3, 2, 6, 1, pixel, accent.darkened(0.08)) + _draw_pixel_rect(origin, 5, 3, 2, 2, pixel, skin) + _draw_pixel_rect(origin, 2, 5, 8, 6, pixel, outline) + _draw_pixel_rect(origin, 3, 5, 6, 5, pixel, cloth) + _draw_pixel_rect(origin, 4, 6, 4, 4, pixel, class_color) + _draw_pixel_rect(origin, 1, 6, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 9, 6, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 3 + step, 11, 2, 3, pixel, outline) + _draw_pixel_rect(origin, 8 - step, 11, 2, 3, pixel, outline) + _draw_pixel_heavy_blade(origin, 11 + step, 2, pixel, accent.darkened(0.22), accent.lightened(0.12)) func _draw_unit_identity_marks(rect: Rect2, center: Vector2, unit: Dictionary, team_color: Color) -> void: diff --git a/tools/smoke_chapter_one_polish.gd b/tools/smoke_chapter_one_polish.gd index c2bef0d..7345254 100644 --- a/tools/smoke_chapter_one_polish.gd +++ b/tools/smoke_chapter_one_polish.gd @@ -381,6 +381,15 @@ func _check_pixel_unit_helpers(failures: Array[String]) -> void: var scene = BattleSceneScript.new() for method_name in [ "_draw_pixel_unit_sprite", + "_pixel_unit_sprite_profile", + "_unit_idle_bob", + "_unit_idle_step", + "_draw_pixel_shield", + "_draw_pixel_spear", + "_draw_pixel_bow", + "_draw_pixel_banner", + "_draw_pixel_staff", + "_draw_pixel_heavy_blade", "_draw_pixel_infantry", "_draw_pixel_archer", "_draw_pixel_cavalry", @@ -389,4 +398,49 @@ func _check_pixel_unit_helpers(failures: Array[String]) -> void: ]: if not scene.has_method(method_name): failures.append("missing pixel-map unit helper: %s" % method_name) + _check_pixel_unit_profiles(scene, failures) scene.free() + + +func _check_pixel_unit_profiles(scene, failures: Array[String]) -> void: + var profiles := { + "infantry": scene._pixel_unit_sprite_profile({"class_id": "infantry"}), + "ranged": scene._pixel_unit_sprite_profile({"class_id": "archer"}), + "cavalry": scene._pixel_unit_sprite_profile({"class_id": "cavalry"}), + "tactic": scene._pixel_unit_sprite_profile({"class_id": "strategist"}), + "heavy": scene._pixel_unit_sprite_profile({"class_id": "bandit"}) + } + for family in profiles.keys(): + var profile: Dictionary = profiles[family] + if str(profile.get("family", "")) != str(family): + failures.append("pixel unit profile should map %s class to %s family: %s" % [family, family, str(profile)]) + if absf(float(profile.get("pixel", 0.0)) - 3.0) > 0.001: + failures.append("pixel unit profile should use a stable 3px grid for %s" % family) + if float(profile.get("min_x", -1.0)) < 0.0 or float(profile.get("max_x", 99.0)) > 16.0: + failures.append("pixel unit profile should stay inside tile width for %s: %s" % [family, str(profile)]) + var bottom_px := 5.0 + 1.0 + float(profile.get("max_y", 99.0)) * float(profile.get("pixel", 3.0)) + if bottom_px >= 56.0: + failures.append("pixel unit profile should stay above HP bar for %s: %.1f" % [family, bottom_px]) + if float((profiles["cavalry"] as Dictionary).get("max_x", 0.0)) <= float((profiles["infantry"] as Dictionary).get("max_x", 0.0)): + failures.append("cavalry pixel profile should read wider than infantry") + if float((profiles["heavy"] as Dictionary).get("max_x", 0.0)) <= float((profiles["infantry"] as Dictionary).get("max_x", 0.0)): + failures.append("heavy pixel profile should read wider than infantry") + if float((profiles["tactic"] as Dictionary).get("max_y", 0.0)) <= float((profiles["infantry"] as Dictionary).get("max_y", 0.0)): + failures.append("tactic pixel profile should read taller or more robed than infantry") + + var seen_weapons := {} + var seen_idles := {} + for family in profiles.keys(): + var profile: Dictionary = profiles[family] + var weapon := str(profile.get("weapon", "")) + var idle := str(profile.get("idle", "")) + if weapon.is_empty() or seen_weapons.has(weapon): + failures.append("pixel unit profile should expose a distinct weapon silhouette for %s" % family) + seen_weapons[weapon] = true + if idle.is_empty() or seen_idles.has(idle): + failures.append("pixel unit profile should expose a distinct idle profile for %s" % family) + seen_idles[idle] = true + if str((profiles["ranged"] as Dictionary).get("weapon", "")) != "bow": + failures.append("ranged pixel profile should keep an explicit bow silhouette") + if str((profiles["infantry"] as Dictionary).get("weapon", "")) != "shield_spear": + failures.append("infantry pixel profile should keep shield and spear silhouette")