|
| 1 | +extends CanvasLayer |
| 2 | +class_name CardfrontTutorialOverlay |
| 3 | + |
| 4 | +const PlayerSettingsStoreClass = preload("res://scripts/PlayerSettingsStore.gd") |
| 5 | + |
| 6 | +const VIEW_W: float = 1120.0 |
| 7 | +const VIEW_H: float = 720.0 |
| 8 | + |
| 9 | +const DIM_COLOR := Color(0.03, 0.06, 0.15, 0.75) |
| 10 | +const FOCUS_BORDER_COLOR := Color(1.0, 1.0, 1.0, 0.60) |
| 11 | +const FOCUS_BORDER_WIDTH: float = 2.0 |
| 12 | + |
| 13 | +const TOOLTIP_SIZE := Vector2(360.0, 160.0) |
| 14 | +const TOOLTIP_GAP: float = 20.0 |
| 15 | + |
| 16 | +const PULSE_SPEED: float = 2.0 |
| 17 | +const PULSE_ALPHA_MIN: float = 0.30 |
| 18 | +const PULSE_ALPHA_MAX: float = 0.80 |
| 19 | + |
| 20 | +const STEP_DATA: Array[Dictionary] = [ |
| 21 | + { |
| 22 | + "title": "① 资源栏", |
| 23 | + "text": "屏幕顶部显示的能量(⚡)和零件(⚙)是您的资源。\n能量和零件会随时间自动增长,用于打出各种卡牌。", |
| 24 | + "focus_rect": Rect2(440, 82, 240, 48), |
| 25 | + }, |
| 26 | + { |
| 27 | + "title": "② 手牌栏", |
| 28 | + "text": "屏幕底部是您的手牌。点击一张卡牌将其选中,\n然后点击战场上的高亮区域来释放卡牌效果。", |
| 29 | + "focus_rect": Rect2(276, 562, 568, 158), |
| 30 | + }, |
| 31 | + { |
| 32 | + "title": "③ 战场区域", |
| 33 | + "text": "选中卡牌后,战场上会高亮显示可以释放的区域。\n点击高亮格子即可释放效果,影响战局走向。", |
| 34 | + "focus_rect": Rect2(100, 150, 600, 400), |
| 35 | + }, |
| 36 | +] |
| 37 | + |
| 38 | +var _current_step: int = 0 |
| 39 | +var _total_steps: int = STEP_DATA.size() |
| 40 | +var _ui_time: float = 0.0 |
| 41 | +var _is_tutorial_active: bool = false |
| 42 | + |
| 43 | +var _dim_top: ColorRect |
| 44 | +var _dim_bottom: ColorRect |
| 45 | +var _dim_left: ColorRect |
| 46 | +var _dim_right: ColorRect |
| 47 | +var _focus_highlight: ColorRect |
| 48 | +var _tooltip_panel: Panel |
| 49 | +var _tooltip_bg: ColorRect |
| 50 | +var _accent_line: ColorRect |
| 51 | +var _title_label: Label |
| 52 | +var _text_label: Label |
| 53 | +var _step_label: Label |
| 54 | +var _next_button: Button |
| 55 | +var _skip_button: Button |
| 56 | + |
| 57 | + |
| 58 | +func _init() -> void: |
| 59 | + name = "CardfrontTutorialOverlay" |
| 60 | + layer = 30 |
| 61 | + process_mode = Node.PROCESS_MODE_ALWAYS |
| 62 | + |
| 63 | + |
| 64 | +func setup() -> void: |
| 65 | + _build_nodes() |
| 66 | + var settings := PlayerSettingsStoreClass.load_settings() |
| 67 | + var show_hint: bool = bool(settings.get("show_newbie_hint", true)) |
| 68 | + var tutorial_done: bool = bool(settings.get("tutorial_completed", false)) |
| 69 | + if show_hint and not tutorial_done: |
| 70 | + start_tutorial() |
| 71 | + else: |
| 72 | + queue_free() |
| 73 | + |
| 74 | + |
| 75 | +func _build_nodes() -> void: |
| 76 | + for name_str in ["DimTop", "DimBottom", "DimLeft", "DimRight"]: |
| 77 | + var rect := ColorRect.new() |
| 78 | + rect.name = name_str |
| 79 | + rect.color = DIM_COLOR |
| 80 | + rect.mouse_filter = Control.MOUSE_FILTER_IGNORE |
| 81 | + add_child(rect) |
| 82 | + _dim_top = $DimTop |
| 83 | + _dim_bottom = $DimBottom |
| 84 | + _dim_left = $DimLeft |
| 85 | + _dim_right = $DimRight |
| 86 | + |
| 87 | + _focus_highlight = ColorRect.new() |
| 88 | + _focus_highlight.name = "FocusHighlight" |
| 89 | + _focus_highlight.color = FOCUS_BORDER_COLOR |
| 90 | + _focus_highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE |
| 91 | + add_child(_focus_highlight) |
| 92 | + |
| 93 | + _tooltip_panel = Panel.new() |
| 94 | + _tooltip_panel.name = "TooltipPanel" |
| 95 | + _tooltip_panel.size = TOOLTIP_SIZE |
| 96 | + _tooltip_panel.mouse_filter = Control.MOUSE_FILTER_STOP |
| 97 | + add_child(_tooltip_panel) |
| 98 | + |
| 99 | + _tooltip_bg = ColorRect.new() |
| 100 | + _tooltip_bg.name = "PanelBg" |
| 101 | + _tooltip_bg.color = Color(0.06, 0.09, 0.15, 0.96) |
| 102 | + _tooltip_bg.size = TOOLTIP_SIZE |
| 103 | + _tooltip_panel.add_child(_tooltip_bg) |
| 104 | + |
| 105 | + _accent_line = ColorRect.new() |
| 106 | + _accent_line.name = "AccentLine" |
| 107 | + _accent_line.color = Color(0.98, 0.76, 0.18, 0.55) |
| 108 | + _accent_line.position = Vector2(12.0, 4.0) |
| 109 | + _accent_line.size = Vector2(TOOLTIP_SIZE.x - 24.0, 2.0) |
| 110 | + _tooltip_panel.add_child(_accent_line) |
| 111 | + |
| 112 | + _title_label = Label.new() |
| 113 | + _title_label.name = "TitleLabel" |
| 114 | + _title_label.position = Vector2(16.0, 12.0) |
| 115 | + _title_label.size = Vector2(TOOLTIP_SIZE.x - 32.0, 24.0) |
| 116 | + _title_label.add_theme_font_size_override("font_size", 18) |
| 117 | + _title_label.add_theme_color_override("font_color", Color(1.0, 0.95, 0.72, 1.0)) |
| 118 | + _tooltip_panel.add_child(_title_label) |
| 119 | + |
| 120 | + _text_label = Label.new() |
| 121 | + _text_label.name = "TextLabel" |
| 122 | + _text_label.position = Vector2(16.0, 40.0) |
| 123 | + _text_label.size = Vector2(TOOLTIP_SIZE.x - 32.0, 74.0) |
| 124 | + _text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART |
| 125 | + _text_label.add_theme_font_size_override("font_size", 13) |
| 126 | + _text_label.add_theme_color_override("font_color", Color(0.82, 0.90, 1.0, 1.0)) |
| 127 | + _tooltip_panel.add_child(_text_label) |
| 128 | + |
| 129 | + _step_label = Label.new() |
| 130 | + _step_label.name = "StepLabel" |
| 131 | + _step_label.position = Vector2(16.0, TOOLTIP_SIZE.y - 38.0) |
| 132 | + _step_label.size = Vector2(80.0, 22.0) |
| 133 | + _step_label.add_theme_font_size_override("font_size", 12) |
| 134 | + _step_label.add_theme_color_override("font_color", Color(0.62, 0.68, 0.82, 1.0)) |
| 135 | + _tooltip_panel.add_child(_step_label) |
| 136 | + |
| 137 | + _next_button = Button.new() |
| 138 | + _next_button.name = "NextButton" |
| 139 | + _next_button.position = Vector2(TOOLTIP_SIZE.x - 110.0, TOOLTIP_SIZE.y - 40.0) |
| 140 | + _next_button.size = Vector2(94.0, 28.0) |
| 141 | + _next_button.text = "下一步" |
| 142 | + _next_button.add_theme_font_size_override("font_size", 14) |
| 143 | + _next_button.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0, 1.0)) |
| 144 | + _next_button.self_modulate = Color(0.24, 0.52, 0.92, 1.0) |
| 145 | + _next_button.pressed.connect(_on_next_pressed) |
| 146 | + _tooltip_panel.add_child(_next_button) |
| 147 | + |
| 148 | + _skip_button = Button.new() |
| 149 | + _skip_button.name = "SkipButton" |
| 150 | + _skip_button.position = Vector2(VIEW_W - 140.0, 16.0) |
| 151 | + _skip_button.size = Vector2(124.0, 28.0) |
| 152 | + _skip_button.text = "跳过 / 不再提示" |
| 153 | + _skip_button.add_theme_font_size_override("font_size", 12) |
| 154 | + _skip_button.add_theme_color_override("font_color", Color(0.62, 0.68, 0.82, 1.0)) |
| 155 | + _skip_button.self_modulate = Color(0.34, 0.34, 0.54, 0.85) |
| 156 | + _skip_button.mouse_filter = Control.MOUSE_FILTER_STOP |
| 157 | + _skip_button.pressed.connect(_on_skip_pressed) |
| 158 | + add_child(_skip_button) |
| 159 | + |
| 160 | + |
| 161 | +func start_tutorial() -> void: |
| 162 | + _current_step = 0 |
| 163 | + visible = true |
| 164 | + _is_tutorial_active = true |
| 165 | + _apply_step(_current_step) |
| 166 | + set_process(true) |
| 167 | + |
| 168 | + |
| 169 | +func _apply_step(step_index: int) -> void: |
| 170 | + if step_index < 0 or step_index >= _total_steps: |
| 171 | + return |
| 172 | + var step: Dictionary = STEP_DATA[step_index] |
| 173 | + var focus: Rect2 = step["focus_rect"] |
| 174 | + |
| 175 | + _dim_top.position = Vector2(0.0, 0.0) |
| 176 | + _dim_top.size = Vector2(VIEW_W, focus.position.y) |
| 177 | + |
| 178 | + _dim_bottom.position = Vector2(0.0, focus.position.y + focus.size.y) |
| 179 | + _dim_bottom.size = Vector2(VIEW_W, VIEW_H - focus.position.y - focus.size.y) |
| 180 | + |
| 181 | + _dim_left.position = Vector2(0.0, focus.position.y) |
| 182 | + _dim_left.size = Vector2(focus.position.x, focus.size.y) |
| 183 | + |
| 184 | + _dim_right.position = Vector2(focus.position.x + focus.size.x, focus.position.y) |
| 185 | + _dim_right.size = Vector2(VIEW_W - focus.position.x - focus.size.x, focus.size.y) |
| 186 | + |
| 187 | + _focus_highlight.position = focus.position - Vector2(FOCUS_BORDER_WIDTH, FOCUS_BORDER_WIDTH) |
| 188 | + _focus_highlight.size = focus.size + Vector2(FOCUS_BORDER_WIDTH * 2.0, FOCUS_BORDER_WIDTH * 2.0) |
| 189 | + |
| 190 | + _position_tooltip(focus) |
| 191 | + |
| 192 | + _title_label.text = step["title"] |
| 193 | + _text_label.text = step["text"] |
| 194 | + _step_label.text = "%d / %d" % [step_index + 1, _total_steps] |
| 195 | + _next_button.text = "完成!" if step_index >= _total_steps - 1 else "下一步" |
| 196 | + |
| 197 | + |
| 198 | +func _position_tooltip(focus: Rect2) -> void: |
| 199 | + var tooltip_x: float = (VIEW_W - TOOLTIP_SIZE.x) * 0.5 |
| 200 | + var tooltip_y: float |
| 201 | + match _current_step: |
| 202 | + 0: |
| 203 | + tooltip_y = focus.position.y + focus.size.y + TOOLTIP_GAP |
| 204 | + 1: |
| 205 | + tooltip_y = focus.position.y - TOOLTIP_GAP - TOOLTIP_SIZE.y |
| 206 | + _: |
| 207 | + tooltip_y = VIEW_H - TOOLTIP_SIZE.y - 240.0 |
| 208 | + _tooltip_panel.position = Vector2(tooltip_x, tooltip_y) |
| 209 | + |
| 210 | + |
| 211 | +func _on_next_pressed() -> void: |
| 212 | + _current_step += 1 |
| 213 | + if _current_step >= _total_steps: |
| 214 | + _complete_tutorial() |
| 215 | + else: |
| 216 | + _apply_step(_current_step) |
| 217 | + |
| 218 | + |
| 219 | +func _on_skip_pressed() -> void: |
| 220 | + _complete_tutorial() |
| 221 | + |
| 222 | + |
| 223 | +func _complete_tutorial() -> void: |
| 224 | + var settings := PlayerSettingsStoreClass.load_settings() |
| 225 | + settings["tutorial_completed"] = true |
| 226 | + PlayerSettingsStoreClass.save_settings(settings) |
| 227 | + _is_tutorial_active = false |
| 228 | + visible = false |
| 229 | + set_process(false) |
| 230 | + |
| 231 | + |
| 232 | +func on_settings_changed(settings: Dictionary) -> void: |
| 233 | + var show_hint: bool = bool(settings.get("show_newbie_hint", true)) |
| 234 | + if _is_tutorial_active and not show_hint: |
| 235 | + _is_tutorial_active = false |
| 236 | + visible = false |
| 237 | + set_process(false) |
| 238 | + var saved := PlayerSettingsStoreClass.load_settings() |
| 239 | + saved["tutorial_completed"] = true |
| 240 | + PlayerSettingsStoreClass.save_settings(saved) |
| 241 | + |
| 242 | + |
| 243 | +func _process(delta: float) -> void: |
| 244 | + _ui_time += delta |
| 245 | + var pulse := PULSE_ALPHA_MIN + (PULSE_ALPHA_MAX - PULSE_ALPHA_MIN) * (0.5 + 0.5 * sin(_ui_time * PULSE_SPEED)) |
| 246 | + _focus_highlight.modulate = Color(FOCUS_BORDER_COLOR.r, FOCUS_BORDER_COLOR.g, FOCUS_BORDER_COLOR.b, pulse) |
0 commit comments