Refine opening prologue layout

This commit is contained in:
2026-06-20 04:10:43 +09:00
parent cd8f3b6926
commit 0e59451ed4
4 changed files with 55 additions and 50 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, 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 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`.
- 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 the battlefield backdrop and Cao Cao portrait, then continue into the first scenario briefing. 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.
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

@@ -2905,89 +2905,90 @@ func _create_hud() -> void:
var opening_panel := PanelContainer.new() var opening_panel := PanelContainer.new()
opening_panel.name = "OpeningProloguePanel" opening_panel.name = "OpeningProloguePanel"
opening_panel.position = Vector2(104, 58) opening_panel.position = Vector2(82, 44)
opening_panel.size = Vector2(1072, 604) opening_panel.size = Vector2(1116, 632)
opening_panel.custom_minimum_size = Vector2(1072, 604) opening_panel.custom_minimum_size = Vector2(1116, 632)
_apply_panel_style(opening_panel, "paper") _apply_panel_style(opening_panel, "lacquer")
opening_prologue_root.add_child(opening_panel) opening_prologue_root.add_child(opening_panel)
var opening_shell := HBoxContainer.new() var opening_shell := VBoxContainer.new()
opening_shell.custom_minimum_size = Vector2(1016, 548) opening_shell.custom_minimum_size = Vector2(1060, 576)
opening_shell.add_theme_constant_override("separation", 18) opening_shell.add_theme_constant_override("separation", 10)
opening_panel.add_child(opening_shell) opening_panel.add_child(opening_shell)
opening_shell.add_child(_make_bamboo_gutter(22, 548)) 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(520, 548) opening_prologue_scene_texture_rect.custom_minimum_size = Vector2(1032, 372)
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.98, 0.96, 0.91, 0.98)
opening_prologue_scene_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE opening_prologue_scene_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
opening_shell.add_child(opening_prologue_scene_texture_rect) opening_image_frame.add_child(opening_prologue_scene_texture_rect)
var opening_story_column := VBoxContainer.new() var opening_caption_row := HBoxContainer.new()
opening_story_column.custom_minimum_size = Vector2(422, 548) opening_caption_row.custom_minimum_size = Vector2(1060, 142)
opening_story_column.add_theme_constant_override("separation", 14) opening_caption_row.add_theme_constant_override("separation", 12)
opening_shell.add_child(opening_story_column) opening_shell.add_child(opening_caption_row)
opening_shell.add_child(_make_bamboo_gutter(22, 548))
opening_story_column.add_child(_make_scroll_rod(422, 6)) var opening_title_column := VBoxContainer.new()
opening_story_column.add_child(_make_seal_ribbon(422, 18)) opening_title_column.custom_minimum_size = Vector2(236, 142)
opening_title_column.add_theme_constant_override("separation", 8)
opening_caption_row.add_child(opening_title_column)
opening_title_column.add_child(_make_seal_ribbon(236, 18))
opening_prologue_title_label = Label.new() opening_prologue_title_label = Label.new()
opening_prologue_title_label.custom_minimum_size = Vector2(422, 52) opening_prologue_title_label.custom_minimum_size = Vector2(236, 64)
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, 26)
opening_story_column.add_child(opening_prologue_title_label) opening_title_column.add_child(opening_prologue_title_label)
var opening_body_panel := PanelContainer.new()
opening_body_panel.custom_minimum_size = Vector2(422, 250)
_apply_panel_style(opening_body_panel, "edict")
opening_story_column.add_child(opening_body_panel)
var opening_body_row := HBoxContainer.new()
opening_body_row.add_theme_constant_override("separation", 10)
opening_body_panel.add_child(opening_body_row)
opening_body_row.add_child(_make_edict_marker_stack(["", "", ""], 206))
opening_prologue_body_label = Label.new()
opening_prologue_body_label.custom_minimum_size = Vector2(322, 206)
opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
opening_prologue_body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(opening_prologue_body_label, UI_AGED_INK, 20)
opening_body_row.add_child(opening_prologue_body_label)
opening_body_row.add_child(_make_edict_marker_stack(["", "", ""], 206))
opening_prologue_step_label = Label.new() opening_prologue_step_label = Label.new()
opening_prologue_step_label.custom_minimum_size = Vector2(422, 26) opening_prologue_step_label.custom_minimum_size = Vector2(236, 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_story_column.add_child(opening_prologue_step_label) opening_title_column.add_child(opening_prologue_step_label)
var opening_button_row := HBoxContainer.new() var opening_body_panel := PanelContainer.new()
opening_button_row.custom_minimum_size = Vector2(422, 44) opening_body_panel.custom_minimum_size = Vector2(562, 142)
opening_button_row.add_theme_constant_override("separation", 10) _apply_panel_style(opening_body_panel, "edict")
opening_story_column.add_child(opening_button_row) opening_caption_row.add_child(opening_body_panel)
opening_prologue_body_label = Label.new()
opening_prologue_body_label.custom_minimum_size = Vector2(526, 110)
opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
opening_prologue_body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(opening_prologue_body_label, UI_AGED_INK, 20)
opening_body_panel.add_child(opening_prologue_body_label)
var opening_button_column := VBoxContainer.new()
opening_button_column.custom_minimum_size = Vector2(226, 142)
opening_button_column.add_theme_constant_override("separation", 8)
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(132, 42) opening_prologue_skip_button.custom_minimum_size = Vector2(226, 42)
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_button_row.add_child(opening_prologue_skip_button)
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(280, 42) opening_prologue_next_button.custom_minimum_size = Vector2(226, 48)
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_row.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_story_column.add_child(_make_scroll_rod(422, 5)) 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

View File

@@ -124,6 +124,10 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void:
failures.append("opening prologue should give Cao Cao, decision, Xiahou Dun, and Yingchuan their own beats") failures.append("opening prologue should give Cao Cao, decision, Xiahou Dun, and Yingchuan their own beats")
if scene.opening_prologue_scene_texture_rect == null or scene.opening_prologue_scene_texture_rect.texture == null: if scene.opening_prologue_scene_texture_rect == null or scene.opening_prologue_scene_texture_rect.texture == null:
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:
failures.append("opening prologue story artwork should use a wide cinematic frame")
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")
var first_story_texture: Texture2D = null var first_story_texture: Texture2D = null
if scene.opening_prologue_scene_texture_rect != null: if scene.opening_prologue_scene_texture_rect != null:
first_story_texture = scene.opening_prologue_scene_texture_rect.texture first_story_texture = scene.opening_prologue_scene_texture_rect.texture