Add title story preview strip

This commit is contained in:
2026-06-20 05:07:27 +09:00
parent 6ee0b9403e
commit 112f8f7d62
3 changed files with 68 additions and 4 deletions

View File

@@ -126,7 +126,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
- Campaign completion screen. - Campaign completion screen.
- New campaign save reset. - New campaign save reset.
- Manual campaign checkpoint save/load. - Manual campaign checkpoint save/load.
- Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop and Cao Cao portrait art, offers start/load/settings, leads new campaigns through a generated four-panel Yellow Turban rebellion, Cao Cao resolve, Xiahou Dun meeting, and Yingchuan contact prologue with full-screen story artwork, a chapter seal, and a bottom scroll caption panel, exposes the same audio/edge-scroll settings from the top in-game toolbar, includes a one-click audio recovery control, and persists BGM/SFX volume, sound toggles, and edge-scroll preference in `user://heros_settings.json`. - Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop and Cao Cao portrait art, offers start/load/settings, previews the four generated opening story panels on the title menu, leads new campaigns through a generated four-panel Yellow Turban rebellion, Cao Cao resolve, Xiahou Dun meeting, and Yingchuan contact prologue with full-screen story artwork, a chapter seal, and a bottom scroll caption panel, exposes the same audio/edge-scroll settings from the top in-game toolbar, includes a one-click audio recovery control, and persists BGM/SFX volume, sound toggles, and edge-scroll preference in `user://heros_settings.json`.
- Victory and defeat result overlay. - Victory and defeat result overlay.
- Dialogue portrait slot stays fixed across speaker and narration lines, with officer default image paths, optional per-line overrides, and speaker-initial or record fallback. - Dialogue portrait slot stays fixed across speaker and narration lines, with officer default image paths, optional per-line overrides, and speaker-initial or record 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.

View File

@@ -1954,6 +1954,52 @@ func _make_seal_cluster(height: float) -> HBoxContainer:
return cluster return cluster
func _make_title_story_preview_strip() -> PanelContainer:
var panel := PanelContainer.new()
panel.name = "TitleStoryPreviewStrip"
panel.custom_minimum_size = Vector2(616, 88)
_apply_panel_style(panel, "hud_info")
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(584, 68)
row.add_theme_constant_override("separation", 8)
panel.add_child(row)
for index in range(OPENING_PROLOGUE_PAGES.size()):
var page: Dictionary = OPENING_PROLOGUE_PAGES[index]
row.add_child(_make_title_story_preview_card(str(page.get("title", "")), str(page.get("image", "")), index + 1))
return panel
func _make_title_story_preview_card(title: String, image_path: String, index: int) -> VBoxContainer:
var card := VBoxContainer.new()
card.name = "TitleStoryPreviewCard%d" % index
card.custom_minimum_size = Vector2(140, 68)
card.add_theme_constant_override("separation", 3)
card.tooltip_text = "%d. %s" % [index, title]
var texture_rect := TextureRect.new()
texture_rect.name = "TitleStoryPreviewImage"
texture_rect.custom_minimum_size = Vector2(140, 48)
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
texture_rect.texture = _load_art_texture(image_path)
texture_rect.modulate = Color(0.96, 0.94, 0.88, 0.94)
texture_rect.tooltip_text = card.tooltip_text
card.add_child(texture_rect)
var label := Label.new()
label.name = "TitleStoryPreviewLabel"
label.text = title
label.custom_minimum_size = Vector2(140, 17)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.tooltip_text = card.tooltip_text
_apply_label_style(label, UI_OLD_BRONZE, 10)
card.add_child(label)
return card
func _make_map_lattice_overlay(size: Vector2) -> Control: func _make_map_lattice_overlay(size: Vector2) -> Control:
var overlay := Control.new() var overlay := Control.new()
overlay.name = "MapLatticeOverlay" overlay.name = "MapLatticeOverlay"
@@ -3605,9 +3651,9 @@ func _create_hud() -> void:
title_panel = PanelContainer.new() title_panel = PanelContainer.new()
title_panel.name = "TitleMenuPanel" title_panel.name = "TitleMenuPanel"
title_panel.position = Vector2(58, 76) title_panel.position = Vector2(58, 46)
title_panel.size = Vector2(708, 568) title_panel.size = Vector2(708, 628)
title_panel.custom_minimum_size = Vector2(708, 568) title_panel.custom_minimum_size = Vector2(708, 628)
_apply_panel_style(title_panel, "paper") _apply_panel_style(title_panel, "paper")
title_screen_root.add_child(title_panel) title_screen_root.add_child(title_panel)
@@ -3634,6 +3680,8 @@ func _create_hud() -> void:
_apply_label_style(title_subtitle_label, UI_OLD_BRONZE, 17) _apply_label_style(title_subtitle_label, UI_OLD_BRONZE, 17)
title_column.add_child(title_subtitle_label) title_column.add_child(title_subtitle_label)
title_column.add_child(_make_title_story_preview_strip())
var title_body_panel := PanelContainer.new() var title_body_panel := PanelContainer.new()
title_body_panel.custom_minimum_size = Vector2(616, 314) title_body_panel.custom_minimum_size = Vector2(616, 314)
_apply_panel_style(title_body_panel, "edict") _apply_panel_style(title_body_panel, "edict")

View File

@@ -74,6 +74,22 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void:
failures.append("title menu panel should sit on the left side of the cinematic title screen") failures.append("title menu panel should sit on the left side of the cinematic title screen")
if scene.title_caption_label == null or scene.title_caption_label.text != "조조전": if scene.title_caption_label == null or scene.title_caption_label.text != "조조전":
failures.append("title screen should present the campaign title in Korean") failures.append("title screen should present the campaign title in Korean")
var title_story_strip: Node = null
if scene.title_screen_root != null:
title_story_strip = scene.title_screen_root.find_child("TitleStoryPreviewStrip", true, false)
if title_story_strip == null:
failures.append("title screen should preview the four generated opening story panels")
else:
var story_cards: Array = title_story_strip.find_children("TitleStoryPreviewCard*", "", true, false)
if story_cards.size() != 4:
failures.append("title story preview should expose four story beats: %d" % story_cards.size())
for story_card in story_cards:
var story_image := (story_card as Node).find_child("TitleStoryPreviewImage", true, false) as TextureRect
var story_label := (story_card as Node).find_child("TitleStoryPreviewLabel", true, false) as Label
if story_image == null or story_image.texture == null:
failures.append("title story preview card should load generated artwork")
if story_label == null or story_label.text.strip_edges().is_empty():
failures.append("title story preview card should name its story beat")
if scene.briefing_panel == null or scene.briefing_panel.visible: if scene.briefing_panel == null or scene.briefing_panel.visible:
failures.append("briefing should stay hidden behind the title screen") failures.append("briefing should stay hidden behind the title screen")
if scene.title_start_button == null or scene.title_start_button.text != "처음부터 시작": if scene.title_start_button == null or scene.title_start_button.text != "처음부터 시작":