Refine opening prologue presentation

This commit is contained in:
2026-06-20 04:42:39 +09:00
parent f42229930a
commit b10eb520b9
4 changed files with 67 additions and 51 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 Cao Cao decision and Xiahou Dun opening prologue with wide story artwork plus a bottom caption panel, exposes the same audio/edge-scroll settings from the top in-game toolbar, 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, leads new campaigns through a generated four-panel Cao Cao decision and Xiahou Dun opening prologue with full-screen story artwork plus a bottom scroll caption panel, exposes the same audio/edge-scroll settings from the top in-game toolbar, 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

@@ -36,7 +36,7 @@
1. Load battle data. 1. Load battle data.
2. Hydrate deployments through `DataCatalog`. 2. Hydrate deployments through `DataCatalog`.
3. Dispatch battle-start scenario events. 3. Dispatch battle-start scenario events.
4. New campaigns first show a short title-to-briefing prologue using wide generated story artwork with a bottom caption panel, then continue into the first scenario briefing. 4. New campaigns first show a short title-to-briefing prologue using full-screen generated story artwork with a bottom scroll caption panel, then continue into the first scenario briefing.
5. Show scenario briefing with the campaign chapter header, battle header, objective summary, chapter overview, selectable camp Talk conversations, manual checkpoint menu, and optional pre-battle shop, Armory, Roster, and Formation setup. 5. Show scenario briefing with the campaign chapter header, battle header, objective summary, chapter overview, selectable camp Talk conversations, manual checkpoint menu, and optional pre-battle shop, Armory, Roster, and Formation setup.
6. Player selects a unit. 6. Player selects a unit.
7. Briefing completion dispatches battle-begin events, including opening dialogue. 7. Briefing completion dispatches battle-begin events, including opening dialogue.

View File

@@ -2894,103 +2894,100 @@ func _create_hud() -> void:
opening_prologue_background_texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE opening_prologue_background_texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
opening_prologue_background_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED opening_prologue_background_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
opening_prologue_background_texture_rect.texture = _load_art_texture(OPENING_PROLOGUE_REBELLION_PATH) opening_prologue_background_texture_rect.texture = _load_art_texture(OPENING_PROLOGUE_REBELLION_PATH)
opening_prologue_background_texture_rect.modulate = Color(0.78, 0.78, 0.74, 0.98) opening_prologue_background_texture_rect.modulate = Color(0.58, 0.58, 0.56, 1.0)
opening_prologue_root.add_child(opening_prologue_background_texture_rect) opening_prologue_root.add_child(opening_prologue_background_texture_rect)
var opening_dim := ColorRect.new()
opening_dim.name = "OpeningInkWash"
opening_dim.color = Color(0.030, 0.016, 0.008, 0.62)
opening_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE
opening_dim.set_anchors_preset(Control.PRESET_FULL_RECT)
opening_prologue_root.add_child(opening_dim)
var opening_panel := PanelContainer.new()
opening_panel.name = "OpeningProloguePanel"
opening_panel.position = Vector2(82, 44)
opening_panel.size = Vector2(1116, 632)
opening_panel.custom_minimum_size = Vector2(1116, 632)
_apply_panel_style(opening_panel, "lacquer")
opening_prologue_root.add_child(opening_panel)
var opening_shell := VBoxContainer.new()
opening_shell.custom_minimum_size = Vector2(1060, 576)
opening_shell.add_theme_constant_override("separation", 10)
opening_panel.add_child(opening_shell)
opening_shell.add_child(_make_scroll_rod(1060, 6))
var opening_image_frame := PanelContainer.new()
opening_image_frame.custom_minimum_size = Vector2(1060, 396)
_apply_panel_style(opening_image_frame, "command_seal")
opening_shell.add_child(opening_image_frame)
opening_prologue_scene_texture_rect = TextureRect.new() opening_prologue_scene_texture_rect = TextureRect.new()
opening_prologue_scene_texture_rect.name = "OpeningStoryImage" opening_prologue_scene_texture_rect.name = "OpeningStoryImage"
opening_prologue_scene_texture_rect.custom_minimum_size = Vector2(1032, 372) opening_prologue_scene_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
opening_prologue_scene_texture_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
opening_prologue_scene_texture_rect.custom_minimum_size = Vector2(1280, 720)
opening_prologue_scene_texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE opening_prologue_scene_texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
opening_prologue_scene_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED opening_prologue_scene_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
opening_prologue_scene_texture_rect.texture = _load_art_texture(OPENING_PROLOGUE_REBELLION_PATH) opening_prologue_scene_texture_rect.texture = _load_art_texture(OPENING_PROLOGUE_REBELLION_PATH)
opening_prologue_scene_texture_rect.modulate = Color(0.98, 0.96, 0.91, 0.98) opening_prologue_scene_texture_rect.modulate = Color(0.96, 0.95, 0.92, 1.0)
opening_prologue_scene_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE opening_prologue_root.add_child(opening_prologue_scene_texture_rect)
opening_image_frame.add_child(opening_prologue_scene_texture_rect)
var opening_top_dim := ColorRect.new()
opening_top_dim.name = "OpeningTopInkShade"
opening_top_dim.color = Color(0.020, 0.017, 0.014, 0.18)
opening_top_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE
opening_top_dim.position = Vector2(0, 0)
opening_top_dim.size = Vector2(1280, 122)
opening_prologue_root.add_child(opening_top_dim)
var opening_bottom_dim := ColorRect.new()
opening_bottom_dim.name = "OpeningBottomInkShade"
opening_bottom_dim.color = Color(0.020, 0.014, 0.010, 0.42)
opening_bottom_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE
opening_bottom_dim.position = Vector2(0, 456)
opening_bottom_dim.size = Vector2(1280, 264)
opening_prologue_root.add_child(opening_bottom_dim)
var opening_caption_panel := PanelContainer.new()
opening_caption_panel.name = "OpeningCaptionScroll"
opening_caption_panel.position = Vector2(56, 500)
opening_caption_panel.size = Vector2(1168, 170)
opening_caption_panel.custom_minimum_size = Vector2(1168, 170)
_apply_panel_style(opening_caption_panel, "paper")
opening_prologue_root.add_child(opening_caption_panel)
var opening_caption_row := HBoxContainer.new() var opening_caption_row := HBoxContainer.new()
opening_caption_row.custom_minimum_size = Vector2(1060, 142) opening_caption_row.custom_minimum_size = Vector2(1118, 122)
opening_caption_row.add_theme_constant_override("separation", 12) opening_caption_row.add_theme_constant_override("separation", 16)
opening_shell.add_child(opening_caption_row) opening_caption_panel.add_child(opening_caption_row)
var opening_title_column := VBoxContainer.new() var opening_title_column := VBoxContainer.new()
opening_title_column.custom_minimum_size = Vector2(236, 142) opening_title_column.custom_minimum_size = Vector2(248, 122)
opening_title_column.add_theme_constant_override("separation", 8) opening_title_column.add_theme_constant_override("separation", 8)
opening_caption_row.add_child(opening_title_column) opening_caption_row.add_child(opening_title_column)
opening_title_column.add_child(_make_seal_ribbon(236, 18)) opening_title_column.add_child(_make_seal_ribbon(248, 18))
opening_prologue_title_label = Label.new() opening_prologue_title_label = Label.new()
opening_prologue_title_label.custom_minimum_size = Vector2(236, 64) opening_prologue_title_label.custom_minimum_size = Vector2(248, 62)
opening_prologue_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
opening_prologue_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER opening_prologue_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(opening_prologue_title_label, UI_PARCHMENT_TEXT, 26) _apply_label_style(opening_prologue_title_label, UI_PARCHMENT_TEXT, 27)
opening_title_column.add_child(opening_prologue_title_label) opening_title_column.add_child(opening_prologue_title_label)
opening_prologue_step_label = Label.new() opening_prologue_step_label = Label.new()
opening_prologue_step_label.custom_minimum_size = Vector2(236, 30) opening_prologue_step_label.custom_minimum_size = Vector2(248, 30)
opening_prologue_step_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_step_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
opening_prologue_step_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER opening_prologue_step_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(opening_prologue_step_label, UI_OLD_BRONZE, 14) _apply_label_style(opening_prologue_step_label, UI_OLD_BRONZE, 14)
opening_title_column.add_child(opening_prologue_step_label) opening_title_column.add_child(opening_prologue_step_label)
var opening_body_panel := PanelContainer.new() var opening_body_panel := PanelContainer.new()
opening_body_panel.custom_minimum_size = Vector2(562, 142) opening_body_panel.custom_minimum_size = Vector2(640, 122)
_apply_panel_style(opening_body_panel, "edict") _apply_panel_style(opening_body_panel, "edict")
opening_caption_row.add_child(opening_body_panel) opening_caption_row.add_child(opening_body_panel)
opening_prologue_body_label = Label.new() opening_prologue_body_label = Label.new()
opening_prologue_body_label.custom_minimum_size = Vector2(526, 110) opening_prologue_body_label.custom_minimum_size = Vector2(602, 88)
opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
opening_prologue_body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER opening_prologue_body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(opening_prologue_body_label, UI_AGED_INK, 20) _apply_label_style(opening_prologue_body_label, UI_AGED_INK, 19)
opening_body_panel.add_child(opening_prologue_body_label) opening_body_panel.add_child(opening_prologue_body_label)
var opening_button_column := VBoxContainer.new() var opening_button_column := VBoxContainer.new()
opening_button_column.custom_minimum_size = Vector2(226, 142) opening_button_column.custom_minimum_size = Vector2(184, 122)
opening_button_column.add_theme_constant_override("separation", 8) opening_button_column.add_theme_constant_override("separation", 8)
opening_caption_row.add_child(opening_button_column) opening_caption_row.add_child(opening_button_column)
opening_prologue_skip_button = Button.new() opening_prologue_skip_button = Button.new()
opening_prologue_skip_button.text = "건너뛰기" opening_prologue_skip_button.text = "건너뛰기"
opening_prologue_skip_button.custom_minimum_size = Vector2(226, 42) opening_prologue_skip_button.custom_minimum_size = Vector2(184, 38)
opening_prologue_skip_button.tooltip_text = "오프닝을 닫고 군막으로 이동합니다." opening_prologue_skip_button.tooltip_text = "오프닝을 닫고 군막으로 이동합니다."
opening_prologue_skip_button.pressed.connect(_on_opening_prologue_skip_pressed) opening_prologue_skip_button.pressed.connect(_on_opening_prologue_skip_pressed)
opening_prologue_next_button = Button.new() opening_prologue_next_button = Button.new()
opening_prologue_next_button.text = "다음" opening_prologue_next_button.text = "다음"
opening_prologue_next_button.custom_minimum_size = Vector2(226, 48) opening_prologue_next_button.custom_minimum_size = Vector2(184, 50)
opening_prologue_next_button.tooltip_text = "다음 이야기로 넘깁니다." opening_prologue_next_button.tooltip_text = "다음 이야기로 넘깁니다."
opening_prologue_next_button.pressed.connect(_on_opening_prologue_next_pressed) opening_prologue_next_button.pressed.connect(_on_opening_prologue_next_pressed)
opening_button_column.add_child(opening_prologue_next_button) opening_button_column.add_child(opening_prologue_next_button)
opening_button_column.add_child(opening_prologue_skip_button) opening_button_column.add_child(opening_prologue_skip_button)
opening_shell.add_child(_make_scroll_rod(1060, 5))
briefing_panel = PanelContainer.new() briefing_panel = PanelContainer.new()
briefing_panel.visible = false briefing_panel.visible = false
briefing_panel.position = Vector2(212, 16) briefing_panel.position = Vector2(212, 16)

View File

@@ -45,8 +45,25 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void:
failures.append("title screen should start with an audible menu BGM stream") failures.append("title screen should start with an audible menu BGM stream")
elif scene.bgm_player.volume_db < -12.0: elif scene.bgm_player.volume_db < -12.0:
failures.append("title menu BGM should be loud enough to hear by default: %.1fdb" % scene.bgm_player.volume_db) failures.append("title menu BGM should be loud enough to hear by default: %.1fdb" % scene.bgm_player.volume_db)
if scene._sfx_stream_for("ui_click") == null or scene._sfx_stream_for("slash") == null or scene._sfx_stream_for("victory_sting") == null: if scene._bgm_stream_for("battle") == null:
failures.append("core UI, combat, and result SFX streams should load") failures.append("battle BGM stream should load before combat starts")
for sfx_key in [
"ui_click",
"menu_confirm",
"menu_cancel",
"footstep",
"slash",
"bow_release",
"hit_heavy",
"guard_block",
"skill_cast",
"fire_skill",
"item_pickup",
"victory_sting",
"defeat_sting"
]:
if scene._sfx_stream_for(sfx_key) == null:
failures.append("SFX stream should load: %s" % sfx_key)
if scene.title_background_texture_rect == null or scene.title_background_texture_rect.texture == null: if scene.title_background_texture_rect == null or scene.title_background_texture_rect.texture == null:
failures.append("title screen should show a high-resolution battlefield backdrop") failures.append("title screen should show a high-resolution battlefield backdrop")
elif BattleSceneScript.TITLE_BACKGROUND_PATH == BattleSceneScript.DEFAULT_BATTLE_BACKGROUND_PATH: elif BattleSceneScript.TITLE_BACKGROUND_PATH == BattleSceneScript.DEFAULT_BATTLE_BACKGROUND_PATH:
@@ -126,6 +143,8 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void:
failures.append("opening prologue should show generated story artwork") failures.append("opening prologue should show generated story artwork")
elif scene.opening_prologue_scene_texture_rect.custom_minimum_size.x <= scene.opening_prologue_scene_texture_rect.custom_minimum_size.y: elif scene.opening_prologue_scene_texture_rect.custom_minimum_size.x <= scene.opening_prologue_scene_texture_rect.custom_minimum_size.y:
failures.append("opening prologue story artwork should use a wide cinematic frame") failures.append("opening prologue story artwork should use a wide cinematic frame")
elif scene.opening_prologue_scene_texture_rect.custom_minimum_size.x < 1200 or scene.opening_prologue_scene_texture_rect.custom_minimum_size.y < 700:
failures.append("opening prologue story artwork should fill the first screen instead of sitting in a small frame")
if scene.opening_prologue_body_label != null and scene.opening_prologue_body_label.custom_minimum_size.x < 500: if scene.opening_prologue_body_label != null and scene.opening_prologue_body_label.custom_minimum_size.x < 500:
failures.append("opening prologue body text should have enough caption width for Korean copy") failures.append("opening prologue body text should have enough caption width for Korean copy")
var first_story_texture: Texture2D = null var first_story_texture: Texture2D = null