diff --git a/README.md b/README.md index 65339a9..c17132b 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,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, Cao Cao portrait art, and a compact left-side command board with `처음부터`, `로드하기`, and `환경설정`; its generated command board and title buttons keep safer source-art proportions, linear filtering, and tile-fit 9-slice surfaces for QHD fullscreen scaling, while story/briefing action buttons use crisp flat readable surfaces. New campaigns then move into 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 text-only chapter-side story ledger instead of exposed numeric cards or tick marks, `다음 기록`/`군막으로`/`전투 준비` story controls, and a bottom scroll caption panel. Briefing and right-side battle information panels now favor high-contrast readable paper/card surfaces over busy decorative frames. Title and in-game settings expose clearer background-music, combat-SFX, edge-scroll, and fullscreen display controls, include one-click audio recovery, and persist preferences in `user://heros_settings.json`. +- Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop, Cao Cao portrait art, and a compact left-side command board with `처음부터`, `로드하기`, and `환경설정`; its generated command board and title buttons keep safer source-art proportions, linear filtering, tile-fit 9-slice surfaces, and aspect-locked canvas scaling for QHD fullscreen, while story/briefing action buttons use crisp flat readable surfaces. New campaigns then move into 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 text-only chapter-side story ledger instead of exposed numeric cards or tick marks, `다음 기록`/`군막으로`/`전투 준비` story controls, and a bottom scroll caption panel. Briefing and right-side battle information panels now favor high-contrast readable paper/card surfaces over busy decorative frames. Title and in-game settings expose clearer background-music, combat-SFX, edge-scroll, and fullscreen display controls, include one-click audio recovery, and persist preferences 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. diff --git a/project.godot b/project.godot index 129f6a2..140b26f 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ config/features=PackedStringArray("4.6") window/size/viewport_width=1280 window/size/viewport_height=720 window/stretch/mode="canvas_items" +window/stretch/aspect="keep" [rendering] diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 70db1b0..ade6a7f 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -3025,6 +3025,7 @@ func _create_hud() -> void: layer.add_child(root) var top_bar := PanelContainer.new() + top_bar.name = "TopHudBar" top_bar.position = Vector2(24, 14) top_bar.size = Vector2(1232, 76) _apply_panel_style(top_bar, "compact") @@ -3428,6 +3429,7 @@ func _create_hud() -> void: auto_end_button_row.add_child(auto_end_turn_no_button) var side_panel := PanelContainer.new() + side_panel.name = "BattleSidePanel" side_panel.position = SIDE_PANEL_POSITION side_panel.size = SIDE_PANEL_SIZE _apply_panel_style(side_panel, "hud_readable") diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 9cc508a..d1e628e 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -103,6 +103,7 @@ func _init() -> void: _check_scene_texture_loading(failures) _check_map_overlay_softness(failures) _check_core_text_visibility(failures) + _check_qhd_design_layout_contract(failures) _check_hud_focus_text(failures) _check_ancient_ui_theme(failures) _check_opening_prologue_presentation(failures) @@ -3406,6 +3407,66 @@ func _check_readable_text_control(failures: Array[String], control: Control, con failures.append("%s text should keep a soft shadow: %s" % [context, str(shadow_color)]) +func _check_qhd_design_layout_contract(failures: Array[String]) -> void: + var design_width := int(ProjectSettings.get_setting("display/window/size/viewport_width", 0)) + var design_height := int(ProjectSettings.get_setting("display/window/size/viewport_height", 0)) + if design_width != 1280 or design_height != 720: + failures.append("QHD layout should keep a 1280x720 design viewport, got %dx%d" % [design_width, design_height]) + if str(ProjectSettings.get_setting("display/window/stretch/mode", "")) != "canvas_items": + failures.append("QHD layout should scale UI through canvas_items stretch mode") + if str(ProjectSettings.get_setting("display/window/stretch/aspect", "")) != "keep": + failures.append("QHD fullscreen should keep aspect ratio so buttons are not distorted") + if design_width > 0 and design_height > 0: + var qhd_scale := Vector2(2560.0 / float(design_width), 1440.0 / float(design_height)) + if absf(qhd_scale.x - qhd_scale.y) > 0.001 or absf(qhd_scale.x - 2.0) > 0.001: + failures.append("QHD should scale the design viewport evenly at 2x, got %s" % str(qhd_scale)) + + var scene = BattleSceneScript.new() + scene._create_hud() + var top_bar := _find_descendant_by_name(scene.ui_root_control, "TopHudBar") as Control + _check_control_design_rect(failures, top_bar, "top icon toolbar", 0.0) + var side_panel := _find_descendant_by_name(scene.ui_root_control, "BattleSidePanel") as Control + _check_control_design_rect(failures, side_panel, "right battle information panel", 0.0) + if side_panel != null and str((side_panel as PanelContainer).get_meta("panel_style_variant", "")) != "hud_readable": + failures.append("right battle information panel should use the readable HUD surface") + if not scene.mission_detail_collapsed: + failures.append("right mission detail should start collapsed to avoid clutter") + if scene.mission_detail_panel != null and scene.mission_detail_panel.visible: + failures.append("right mission detail panel should not consume space by default") + _check_control_design_rect(failures, scene.briefing_panel, "pre-battle briefing sheet", 0.0) + if scene.briefing_panel != null and str(scene.briefing_panel.get_meta("panel_style_variant", "")) != "briefing_sheet": + failures.append("pre-battle briefing should use the readable sheet surface") + if scene.briefing_objective_panel != null and str(scene.briefing_objective_panel.get_meta("panel_style_variant", "")) != "briefing_card": + failures.append("briefing objective should use a readable card surface") + if scene.briefing_camp_overview_panel != null and str(scene.briefing_camp_overview_panel.get_meta("panel_style_variant", "")) != "briefing_card": + failures.append("briefing map report should use a readable card surface") + _check_button_uses_flat_readable_surface(failures, scene.briefing_start_button, "briefing start") + + var opening_caption := _find_descendant_by_name(scene.opening_prologue_root, "OpeningCaptionScroll") as Control + _check_control_design_rect(failures, opening_caption, "opening story caption", 0.0) + var opening_ledger := _find_descendant_by_name(scene.opening_prologue_root, "OpeningStoryLedgerStrip") as Control + _check_control_design_rect(failures, opening_ledger, "opening story ledger", 0.0) + _check_button_uses_flat_readable_surface(failures, scene.opening_prologue_next_button, "opening next") + _check_button_uses_flat_readable_surface(failures, scene.opening_prologue_skip_button, "opening skip") + scene.free() + + +func _check_control_design_rect(failures: Array[String], control: Control, context: String, margin: float = 10.0) -> void: + if control == null: + failures.append("%s missing for design-view fit check" % context) + return + var size := control.size + if size.x <= 0.0 or size.y <= 0.0: + size = control.custom_minimum_size + if size.x <= 0.0 or size.y <= 0.0: + failures.append("%s should reserve stable layout size before QHD scaling" % context) + return + var rect := Rect2(control.position, size) + var design_rect := Rect2(Vector2(margin, margin), Vector2(1280.0 - margin * 2.0, 720.0 - margin * 2.0)) + if not design_rect.encloses(rect): + failures.append("%s should fit inside the 1280x720 design viewport before QHD scaling: %s" % [context, str(rect)]) + + func _check_hud_focus_text(failures: Array[String]) -> void: var scene = BattleSceneScript.new() if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):