Feature opening story art on title screen
This commit is contained in:
@@ -126,7 +126,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
||||
- Campaign completion screen.
|
||||
- New campaign save reset.
|
||||
- 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, 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, a highlighted four-beat story strip, 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, opens with a large generated Yellow Turban story feature panel plus four generated opening story previews 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, a highlighted four-beat story strip, 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.
|
||||
- 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.
|
||||
|
||||
@@ -142,6 +142,8 @@ const BRIEFING_TACTICAL_MARKER_MAX_COUNT := 3
|
||||
const BRIEFING_FORCE_PREVIEW_ICON_SIZE := Vector2(32, 32)
|
||||
const BRIEFING_FORCE_PREVIEW_MAX_ALLIES := 2
|
||||
const BRIEFING_FORCE_PREVIEW_MAX_ENEMY_TYPES := 4
|
||||
const TITLE_STORY_FEATURE_SIZE := Vector2(616, 122)
|
||||
const TITLE_STORY_FEATURE_IMAGE_SIZE := Vector2(238, 96)
|
||||
const OPENING_STORY_BEAT_CARD_SIZE := Vector2(122, 66)
|
||||
const OPENING_STORY_BEAT_IMAGE_SIZE := Vector2(112, 42)
|
||||
const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32)
|
||||
@@ -1968,14 +1970,85 @@ func _make_seal_cluster(height: float) -> HBoxContainer:
|
||||
return cluster
|
||||
|
||||
|
||||
func _make_title_story_preview_strip() -> PanelContainer:
|
||||
func _make_title_story_feature_panel() -> PanelContainer:
|
||||
var page: Dictionary = OPENING_PROLOGUE_PAGES[0]
|
||||
var title := str(page.get("title", "황건의 난"))
|
||||
var body := str(page.get("body", ""))
|
||||
var image_path := str(page.get("image", ""))
|
||||
var tooltip := "%s\n%s" % [title, body]
|
||||
var panel := PanelContainer.new()
|
||||
panel.name = "TitleStoryPreviewStrip"
|
||||
panel.custom_minimum_size = Vector2(616, 88)
|
||||
panel.name = "TitleStoryFeaturePanel"
|
||||
panel.custom_minimum_size = TITLE_STORY_FEATURE_SIZE
|
||||
panel.tooltip_text = tooltip
|
||||
_apply_panel_style(panel, "hud_info")
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.custom_minimum_size = Vector2(584, 68)
|
||||
row.custom_minimum_size = Vector2(584, 104)
|
||||
row.add_theme_constant_override("separation", 10)
|
||||
row.tooltip_text = tooltip
|
||||
panel.add_child(row)
|
||||
|
||||
var image_stack := PanelContainer.new()
|
||||
image_stack.name = "TitleStoryFeatureImageFrame"
|
||||
image_stack.custom_minimum_size = Vector2(252, 104)
|
||||
image_stack.tooltip_text = tooltip
|
||||
_apply_panel_style(image_stack, "caption")
|
||||
row.add_child(image_stack)
|
||||
|
||||
var image := TextureRect.new()
|
||||
image.name = "TitleStoryFeatureImage"
|
||||
image.custom_minimum_size = TITLE_STORY_FEATURE_IMAGE_SIZE
|
||||
image.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
|
||||
image.texture = _load_art_texture(image_path)
|
||||
image.modulate = Color(0.98, 0.95, 0.88, 0.98)
|
||||
image.tooltip_text = tooltip
|
||||
image_stack.add_child(image)
|
||||
|
||||
var text_column := VBoxContainer.new()
|
||||
text_column.custom_minimum_size = Vector2(320, 104)
|
||||
text_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
text_column.add_theme_constant_override("separation", 4)
|
||||
text_column.tooltip_text = tooltip
|
||||
row.add_child(text_column)
|
||||
|
||||
var title_row := HBoxContainer.new()
|
||||
title_row.custom_minimum_size = Vector2(320, 26)
|
||||
title_row.add_theme_constant_override("separation", 6)
|
||||
title_row.tooltip_text = tooltip
|
||||
text_column.add_child(title_row)
|
||||
title_row.add_child(_make_seal_tile("1", 24))
|
||||
|
||||
var title_label := Label.new()
|
||||
title_label.name = "TitleStoryFeatureTitle"
|
||||
title_label.text = title
|
||||
title_label.custom_minimum_size = Vector2(284, 26)
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
title_label.tooltip_text = tooltip
|
||||
_apply_label_style(title_label, UI_PARCHMENT_TEXT, 18)
|
||||
title_row.add_child(title_label)
|
||||
|
||||
var body_label := Label.new()
|
||||
body_label.name = "TitleStoryFeatureBody"
|
||||
body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
body_label.custom_minimum_size = Vector2(320, 68)
|
||||
body_label.tooltip_text = tooltip
|
||||
_apply_label_style(body_label, UI_AGED_INK, 13)
|
||||
_set_fitted_label_text(body_label, body, 13, 10, body_label.custom_minimum_size)
|
||||
text_column.add_child(body_label)
|
||||
|
||||
_set_control_tree_tooltip(panel, tooltip)
|
||||
return panel
|
||||
|
||||
|
||||
func _make_title_story_preview_strip() -> PanelContainer:
|
||||
var panel := PanelContainer.new()
|
||||
panel.name = "TitleStoryPreviewStrip"
|
||||
panel.custom_minimum_size = Vector2(616, 78)
|
||||
_apply_panel_style(panel, "hud_info")
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.custom_minimum_size = Vector2(584, 58)
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
panel.add_child(row)
|
||||
|
||||
@@ -1988,13 +2061,13 @@ func _make_title_story_preview_strip() -> PanelContainer:
|
||||
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.custom_minimum_size = Vector2(140, 58)
|
||||
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.custom_minimum_size = Vector2(140, 38)
|
||||
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)
|
||||
@@ -3764,22 +3837,22 @@ func _create_hud() -> void:
|
||||
|
||||
title_panel = PanelContainer.new()
|
||||
title_panel.name = "TitleMenuPanel"
|
||||
title_panel.position = Vector2(58, 46)
|
||||
title_panel.size = Vector2(708, 628)
|
||||
title_panel.custom_minimum_size = Vector2(708, 628)
|
||||
title_panel.position = Vector2(58, 24)
|
||||
title_panel.size = Vector2(708, 670)
|
||||
title_panel.custom_minimum_size = Vector2(708, 670)
|
||||
_apply_panel_style(title_panel, "paper")
|
||||
title_screen_root.add_child(title_panel)
|
||||
|
||||
var title_column := VBoxContainer.new()
|
||||
title_column.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
title_column.add_theme_constant_override("separation", 10)
|
||||
title_column.add_theme_constant_override("separation", 7)
|
||||
title_panel.add_child(title_column)
|
||||
title_column.add_child(_make_scroll_rod(616, 5))
|
||||
title_column.add_child(_make_seal_ribbon(616, 18))
|
||||
|
||||
title_caption_label = Label.new()
|
||||
title_caption_label.text = "조조전"
|
||||
title_caption_label.custom_minimum_size = Vector2(616, 58)
|
||||
title_caption_label.custom_minimum_size = Vector2(616, 48)
|
||||
title_caption_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
title_caption_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_apply_label_style(title_caption_label, UI_PARCHMENT_TEXT, 34)
|
||||
@@ -3787,16 +3860,17 @@ func _create_hud() -> void:
|
||||
|
||||
title_subtitle_label = Label.new()
|
||||
title_subtitle_label.text = "난세를 여는 첫 군령"
|
||||
title_subtitle_label.custom_minimum_size = Vector2(616, 34)
|
||||
title_subtitle_label.custom_minimum_size = Vector2(616, 26)
|
||||
title_subtitle_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
title_subtitle_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_apply_label_style(title_subtitle_label, UI_OLD_BRONZE, 17)
|
||||
title_column.add_child(title_subtitle_label)
|
||||
|
||||
title_column.add_child(_make_title_story_feature_panel())
|
||||
title_column.add_child(_make_title_story_preview_strip())
|
||||
|
||||
var title_body_panel := PanelContainer.new()
|
||||
title_body_panel.custom_minimum_size = Vector2(616, 314)
|
||||
title_body_panel.custom_minimum_size = Vector2(616, 300)
|
||||
_apply_panel_style(title_body_panel, "edict")
|
||||
title_column.add_child(title_body_panel)
|
||||
|
||||
|
||||
@@ -74,6 +74,21 @@ 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")
|
||||
if scene.title_caption_label == null or scene.title_caption_label.text != "조조전":
|
||||
failures.append("title screen should present the campaign title in Korean")
|
||||
var title_story_feature: Node = null
|
||||
if scene.title_screen_root != null:
|
||||
title_story_feature = scene.title_screen_root.find_child("TitleStoryFeaturePanel", true, false)
|
||||
if title_story_feature == null:
|
||||
failures.append("title screen should feature a large generated opening story panel")
|
||||
else:
|
||||
var feature_image := title_story_feature.find_child("TitleStoryFeatureImage", true, false) as TextureRect
|
||||
var feature_title := title_story_feature.find_child("TitleStoryFeatureTitle", true, false) as Label
|
||||
var feature_body := title_story_feature.find_child("TitleStoryFeatureBody", true, false) as Label
|
||||
if feature_image == null or feature_image.texture == null:
|
||||
failures.append("title story feature should load generated opening artwork")
|
||||
if feature_title == null or feature_title.text != "황건의 난":
|
||||
failures.append("title story feature should name the first opening beat")
|
||||
if feature_body == null or not feature_body.text.contains("조조") or not feature_body.text.contains("황건의 난"):
|
||||
failures.append("title story feature should summarize the first opening beat 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)
|
||||
|
||||
Reference in New Issue
Block a user