Make base unit art transparent

This commit is contained in:
2026-06-18 19:08:38 +09:00
parent e748db1598
commit b1e32d6fc7
9 changed files with 49 additions and 3 deletions

View File

@@ -114,7 +114,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
- Victory and defeat result overlay.
- Dialogue portrait slot with officer default image paths, optional per-line overrides, and speaker-initial fallback.
- Initial AI-generated photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He.
- Initial AI-generated battlefield, class unit, transparent generic-enemy unit, and item icon assets live under `art/backgrounds`, `art/units`, and `art/items`.
- Initial AI-generated battlefield, transparent class unit, transparent generic-enemy unit, and item icon assets live under `art/backgrounds`, `art/units`, and `art/items`.
- Battle HUD portrait thumbnails show the selected unit, or a hovered unit when nothing is selected.
- Selected and hovered unit focus text shows combat role, movement type, current terrain move cost, terrain bonuses, core stats, and status effects.
- Dialogue lines can place the portrait on the left or right side of an expanded visual-novel-style dialogue panel with progress and previous-line controls.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -16,7 +16,7 @@
- `scripts/core/battle_state.gd`: Battle rules, unit state, turns, AI, and victory checks.
- `scripts/scenes/battle_scene.gd`: Rendering, input handling, and HUD wiring.
- `audio/bgm/*.wav` and `audio/sfx/*.wav`: Placeholder music loops and interface/action stingers.
- `art/portraits/*.png`, `art/units/*.png`, `art/units/enemies/*.png`, `art/items/*.png`, and `art/backgrounds/*.png`: Generated visual assets used by dialogue, battle HUD, board tokens, generic-enemy transparent cutouts, item buttons, and map backdrops.
- `art/portraits/*.png`, `art/units/*.png`, `art/units/enemies/*.png`, `art/items/*.png`, and `art/backgrounds/*.png`: Generated visual assets used by dialogue, battle HUD, transparent board-token cutouts, generic-enemy transparent cutouts, item buttons, and map backdrops.
- `data/campaign/campaign.json`: Campaign chapter ranges, scenario order, and scenario paths.
- `data/defs/*.json`: Officers, classes, terrain, items, and skills.
- `data/scenarios/*.json`: Scenario definitions.

View File

@@ -219,7 +219,7 @@ Classes provide movement, min/max attack range, skill access, growth grades, cla
```
`attack_range` is `[min, max]` in Manhattan distance. A range of `[2, 2]` means the unit cannot attack adjacent targets.
`sprite` is an optional class-level board token image used by hydrated units unless an officer or deployment overrides it. `enemy_sprite` is an optional class-level image for generic enemy units with `team: "enemy"` and no `officer_id`; runtime sprite precedence is deployment `sprite`, officer `sprite`, generic enemy class `enemy_sprite`, then class `sprite`. The current enemy class art is stored as transparent PNG cutouts under `res://art/units/enemies/` so named officers and player units can keep their cleaner class or officer presentation while ordinary troops read as their own force.
`sprite` is an optional class-level board token image used by hydrated units unless an officer or deployment overrides it. Base class sprites are transparent PNG cutouts under `res://art/units/` so board tokens can show only the character over the terrain and token backplate. `enemy_sprite` is an optional class-level image for generic enemy units with `team: "enemy"` and no `officer_id`; runtime sprite precedence is deployment `sprite`, officer `sprite`, generic enemy class `enemy_sprite`, then class `sprite`. The current enemy class art is stored as transparent PNG cutouts under `res://art/units/enemies/` so named officers and player units can keep their cleaner class or officer presentation while ordinary troops read as their own force.
`promotion.level` and `promotion.to` define an automatic class promotion route. When a unit reaches the threshold during battle, the runtime updates class name, movement, movement type, growth, class skills, attack range, and class base-bonus deltas. The promoted `class_id` and derived roster fields are captured in the campaign roster snapshot, and the next battle rehydrates class-derived fields from the saved `class_id`. Promotion targets must be higher-tier classes and must keep all source equipment slot permissions unless runtime auto-unequip logic is added later. The current core routes cover hero, cavalry, infantry, archer, warrior, and strategist first-tier classes.

View File

@@ -55,6 +55,19 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
_check_unit_sprite_resolution(failures, state, "yellow_turban_1", "res://art/units/enemies/enemy_warrior.png", true)
_check_unit_sprite_resolution(failures, state, "yellow_turban_2", "res://art/units/enemies/enemy_infantry.png", true)
_check_unit_sprite_resolution(failures, state, "yellow_turban_3", "res://art/units/enemies/enemy_archer.png", true)
for path in [
"res://art/units/archer.png",
"res://art/units/cavalry.png",
"res://art/units/infantry.png",
"res://art/units/strategist.png",
"res://art/units/warrior.png",
"res://art/units/enemies/enemy_archer.png",
"res://art/units/enemies/enemy_cavalry.png",
"res://art/units/enemies/enemy_infantry.png",
"res://art/units/enemies/enemy_strategist.png",
"res://art/units/enemies/enemy_warrior.png"
]:
_check_alpha_cutout_path(failures, path, "unit cutout %s" % path)
var gate_state = BattleStateScript.new()
if not gate_state.load_battle("res://data/scenarios/002_sishui_gate.json"):
@@ -348,6 +361,39 @@ func _check_unit_sprite_resolution(failures: Array[String], state, unit_id: Stri
_check_image_path(failures, sprite, "unit %s resolved sprite" % unit_id)
func _check_alpha_cutout_path(failures: Array[String], path: String, context: String) -> void:
if path.is_empty():
failures.append("%s has an empty image path" % context)
return
if not path.begins_with("res://"):
failures.append("%s must use a res:// path: %s" % [context, path])
return
if not FileAccess.file_exists(path):
failures.append("%s references missing image: %s" % [context, path])
return
var image := Image.new()
var err := image.load(path)
if err != OK:
failures.append("%s references unreadable image: %s" % [context, path])
return
if image.get_width() < 512 or image.get_height() < 512:
failures.append("%s image should be at least 512x512: %s (%dx%d)" % [context, path, image.get_width(), image.get_height()])
return
for corner in [Vector2i(0, 0), Vector2i(image.get_width() - 1, 0), Vector2i(0, image.get_height() - 1), Vector2i(image.get_width() - 1, image.get_height() - 1)]:
if image.get_pixelv(corner).a > 0.01:
failures.append("%s should have transparent corners: %s" % [context, path])
return
var transparent_samples := 0
var total_samples := 0
for y in range(0, image.get_height(), 4):
for x in range(0, image.get_width(), 4):
total_samples += 1
if image.get_pixel(x, y).a <= 0.01:
transparent_samples += 1
if transparent_samples < int(total_samples * 0.20):
failures.append("%s should have substantial transparent background: %s (%d/%d sampled pixels)" % [context, path, transparent_samples, total_samples])
func _check_image_path(failures: Array[String], path: String, context: String) -> void:
if path.is_empty():
failures.append("%s has an empty image path" % context)