Add early campaign battlefield backgrounds

This commit is contained in:
2026-06-18 20:50:04 +09:00
parent c5d47021b4
commit 1ee3859636
11 changed files with 78 additions and 24 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. - Victory and defeat result overlay.
- Dialogue portrait slot with officer default image paths, optional per-line overrides, and speaker-initial fallback. - 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 photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He.
- Initial AI-generated battlefield, transparent class unit, transparent generic-enemy unit, and transparent item icon assets live under `art/backgrounds`, `art/units`, and `art/items`. - AI-generated scenario battlefield backgrounds, transparent class units, transparent generic-enemy units, and transparent 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. - 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. - 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. - 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.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -46,6 +46,7 @@
"map": { "map": {
"width": 12, "width": 12,
"height": 9, "height": 9,
"background": "res://art/backgrounds/sishui_gate_pass.png",
"terrain": [ "terrain": [
"GGGGGRRGGGGG", "GGGGGRRGGGGG",
"GGGFFRRGHHGG", "GGGFFRRGHHGG",

View File

@@ -74,6 +74,7 @@
"map": { "map": {
"width": 15, "width": 15,
"height": 10, "height": 10,
"background": "res://art/backgrounds/wan_castle_escape.png",
"terrain": [ "terrain": [
"GGGGGHHGGGFFFFG", "GGGGGHHGGGFFFFG",
"GGFFGHHGGGFFFFG", "GGFFGHHGGGFFFFG",

View File

@@ -67,6 +67,7 @@
"map": { "map": {
"width": 15, "width": 15,
"height": 10, "height": 10,
"background": "res://art/backgrounds/xiapi_flooded_siege.png",
"terrain": [ "terrain": [
"GGGGGHHGGGWWGGG", "GGGGGHHGGGWWGGG",
"GGGFFHHGGGWWGGG", "GGGFFHHGGGWWGGG",

View File

@@ -67,6 +67,7 @@
"map": { "map": {
"width": 17, "width": 17,
"height": 11, "height": 11,
"background": "res://art/backgrounds/guandu_defensive_camps.png",
"terrain": [ "terrain": [
"GGGGGHHGGGGHHGGGG", "GGGGGHHGGGGHHGGGG",
"GGFFGHHGGFFHHGFFG", "GGFFGHHGGFFHHGFFG",

View File

@@ -134,7 +134,7 @@
## Milestone 5: Presentation ## Milestone 5: Presentation
- Portrait and art pipeline. Officer definitions can provide default portrait paths, classes can provide board `sprite` paths, items can provide transparent `icon` cutout paths, scenarios can provide map `background` paths, and the first generated officer portraits, unit sprites, item icons, and battlefield background are in `art/`. - Portrait and art pipeline. Officer definitions can provide default portrait paths, classes can provide board `sprite` paths, items can provide transparent `icon` cutout paths, scenarios can provide map `background` paths, and generated officer portraits, unit sprites, item icons, plus scenario battlefield backgrounds are in `art/`.
- Unit sprites and animations. Board movement now has a short slide animation, attacks request class-specific motion profiles for infantry, archers, cavalry, command units, and heavy warrior/bandit units, and board units show class sprite tokens, team rings, class badges, low-HP warning rings, and small status pips for active support, debuff, poison, seal, snare, and disarm effects, with HUD summaries showing remaining phases. - Unit sprites and animations. Board movement now has a short slide animation, attacks request class-specific motion profiles for infantry, archers, cavalry, command units, and heavy warrior/bandit units, and board units show class sprite tokens, team rings, class badges, low-HP warning rings, and small status pips for active support, debuff, poison, seal, snare, and disarm effects, with HUD summaries showing remaining phases.
- Battlefield presentation. Maps can draw a high-resolution background under translucent terrain overlays, with terrain-specific detail patterns, connected road segments, shoreline/castle/forest/hill edge blends, and generic-enemy token marks. - Battlefield presentation. Maps can draw a high-resolution background under translucent terrain overlays, with terrain-specific detail patterns, connected road segments, shoreline/castle/forest/hill edge blends, and generic-enemy token marks.
- Battle HUD presentation. Selected and hovered units now summarize battlefield role, movement type, current terrain move cost, defense/avoid bonuses, core stats, status effects, and gear so class identity reads alongside the visual tokens. - Battle HUD presentation. Selected and hovered units now summarize battlefield role, movement type, current terrain move cost, defense/avoid bonuses, core stats, status effects, and gear so class identity reads alongside the visual tokens.

View File

@@ -3,7 +3,39 @@ extends SceneTree
const BattleStateScript := preload("res://scripts/core/battle_state.gd") const BattleStateScript := preload("res://scripts/core/battle_state.gd")
const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd") const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd")
const WUCHAO_BACKGROUND_PATH := "res://art/backgrounds/wuchao_night_granary.png" const SCENARIO_BACKGROUNDS := [
{
"scenario_path": "res://data/scenarios/001_yellow_turbans.json",
"background_path": "res://art/backgrounds/battlefield_frontier.png",
"label": "001 opening field background",
"allow_default": true
},
{
"scenario_path": "res://data/scenarios/002_sishui_gate.json",
"background_path": "res://art/backgrounds/sishui_gate_pass.png",
"label": "002 Sishui Gate background"
},
{
"scenario_path": "res://data/scenarios/008_wan_castle_escape.json",
"background_path": "res://art/backgrounds/wan_castle_escape.png",
"label": "008 Wan Castle background"
},
{
"scenario_path": "res://data/scenarios/009_xiapi_siege.json",
"background_path": "res://art/backgrounds/xiapi_flooded_siege.png",
"label": "009 Xiapi flooded siege background"
},
{
"scenario_path": "res://data/scenarios/012_guandu_showdown.json",
"background_path": "res://art/backgrounds/guandu_defensive_camps.png",
"label": "012 Guandu defensive camps background"
},
{
"scenario_path": "res://data/scenarios/013_wuchao_raid.json",
"background_path": "res://art/backgrounds/wuchao_night_granary.png",
"label": "013 Wuchao night granary background"
}
]
func _init() -> void: func _init() -> void:
@@ -47,7 +79,7 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
failures.append("001 shop should expose merchant lines") failures.append("001 shop should expose merchant lines")
_check_image_path(failures, state.get_map_background_path(), "001 map background") _check_image_path(failures, state.get_map_background_path(), "001 map background")
_check_wuchao_background_data(failures) _check_scenario_backgrounds_data(failures)
for unit_id in ["cao_cao", "xiahou_dun", "yellow_turban_1", "yellow_turban_2", "yellow_turban_3"]: for unit_id in ["cao_cao", "xiahou_dun", "yellow_turban_1", "yellow_turban_2", "yellow_turban_3"]:
var unit := state.get_unit(unit_id) var unit := state.get_unit(unit_id)
if unit.is_empty(): if unit.is_empty():
@@ -88,16 +120,21 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
_check_alpha_cutout_path(failures, icon_path, "item %s icon cutout" % item_id) _check_alpha_cutout_path(failures, icon_path, "item %s icon cutout" % item_id)
func _check_wuchao_background_data(failures: Array[String]) -> void: func _check_scenario_backgrounds_data(failures: Array[String]) -> void:
var state = BattleStateScript.new() for expectation in SCENARIO_BACKGROUNDS:
if not state.load_battle("res://data/scenarios/013_wuchao_raid.json"): var entry: Dictionary = expectation
failures.append("could not load Wuchao raid for background") var scenario_path := str(entry.get("scenario_path", ""))
return var expected_path := str(entry.get("background_path", ""))
var background_path := state.get_map_background_path() var label := str(entry.get("label", scenario_path))
if background_path != WUCHAO_BACKGROUND_PATH: var state = BattleStateScript.new()
failures.append("013 Wuchao should use its night granary background: %s" % background_path) if not state.load_battle(scenario_path):
return failures.append("could not load scenario for background: %s" % label)
_check_image_path(failures, background_path, "013 Wuchao map background") continue
var background_path: String = state.get_map_background_path()
if background_path != expected_path:
failures.append("%s should use %s, got %s" % [label, expected_path, background_path])
continue
_check_image_path(failures, background_path, label)
func _check_scene_texture_loading(failures: Array[String]) -> void: func _check_scene_texture_loading(failures: Array[String]) -> void:
@@ -136,16 +173,29 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
failures.append("camp conversation button text should preview supply effect") failures.append("camp conversation button text should preview supply effect")
scene.free() scene.free()
var wuchao_scene = BattleSceneScript.new() _check_scenario_background_texture_loading(failures)
if not wuchao_scene.state.load_battle("res://data/scenarios/013_wuchao_raid.json"):
failures.append("could not load Wuchao scene state")
wuchao_scene.free() func _check_scenario_background_texture_loading(failures: Array[String]) -> void:
return for expectation in SCENARIO_BACKGROUNDS:
if wuchao_scene.state.get_map_background_path() == BattleSceneScript.DEFAULT_BATTLE_BACKGROUND_PATH: var entry: Dictionary = expectation
failures.append("Wuchao scene should not rely on the default battlefield background") var scenario_path := str(entry.get("scenario_path", ""))
if wuchao_scene._current_battle_background_texture() == null: var expected_path := str(entry.get("background_path", ""))
failures.append("battle scene should load Wuchao night granary background texture") var label := str(entry.get("label", scenario_path))
wuchao_scene.free() var scene = BattleSceneScript.new()
if not scene.state.load_battle(scenario_path):
failures.append("could not load scene state for background: %s" % label)
scene.free()
continue
var actual_path: String = scene.state.get_map_background_path()
if actual_path != expected_path:
failures.append("%s scene loaded %s instead of %s" % [label, actual_path, expected_path])
var allow_default := bool(entry.get("allow_default", false))
if not allow_default and actual_path == BattleSceneScript.DEFAULT_BATTLE_BACKGROUND_PATH:
failures.append("%s should not rely on the default battlefield background" % label)
if scene._current_battle_background_texture() == null:
failures.append("battle scene should load texture for %s" % label)
scene.free()
func _check_hud_focus_text(failures: Array[String]) -> void: func _check_hud_focus_text(failures: Array[String]) -> void: