## 主菜单:新游戏 / 继续 / 排行榜 / 设置 / 退出 extends Control var _settings_layer: CanvasLayer = null var _settings_root: Control = null var _leaderboard_layer: CanvasLayer = null var _ftue_layer: CanvasLayer = null var _unlock_layer: CanvasLayer = null var _unlock_root: Control = null var _continue_btn: Button = null var _quit_confirm: bool = false func _ready() -> void: SceneManager.fade_in_first() _build_bg() _build_menu() _build_settings_panel() _build_leaderboard_panel() _build_ftue_panel() _build_unlock_panel() func _build_bg() -> void: # 深色渐变背景 var bg := ColorRect.new() bg.color = Color(0.04, 0.04, 0.09) bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(bg) # 左侧装饰色条 var stripe := ColorRect.new() stripe.color = Color(0.18, 0.12, 0.40, 0.6) stripe.size = Vector2(320, 648) add_child(stripe) func _build_menu() -> void: # 游戏大标题 var title := Label.new() title.text = "魔法工匠" title.position = Vector2(40, 80) title.add_theme_font_size_override("font_size", 56) title.modulate = Color(1.0, 0.85, 0.3) add_child(title) var sub := Label.new() sub.text = "Arcane Artificer" sub.position = Vector2(44, 148) sub.add_theme_font_size_override("font_size", 18) sub.modulate = Color(0.7, 0.75, 1.0) add_child(sub) # 按钮列 var items: Array = [ ["MENU_NEW_GAME", _on_new_game], ["MENU_CONTINUE", _on_continue], ["MENU_LEADERBOARD", _on_leaderboard], ["MENU_UNLOCK", _on_unlock], ["MENU_SETTINGS", _on_settings], ["MENU_QUIT", _on_quit], ] var y: float = 240.0 for item in items: var btn := _make_btn(tr(item[0]), item[1]) btn.position = Vector2(52, y) btn.size = Vector2(220, 48) add_child(btn) if item[0] == "MENU_CONTINUE": _continue_btn = btn _continue_btn.disabled = not SceneManager.has_save() _continue_btn.modulate = Color(1, 1, 1, 0.45) if _continue_btn.disabled else Color.WHITE y += 62.0 # 版本号 var ver := Label.new() ver.text = "v1.0.0" ver.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) ver.position = Vector2(-80, -20) ver.add_theme_font_size_override("font_size", 12) ver.modulate = Color(0.4, 0.4, 0.4) add_child(ver) # 语言切换 var lang := Button.new() lang.text = "🌐 %s" % Locale.get_locale() lang.set_anchors_preset(Control.PRESET_BOTTOM_LEFT) lang.position = Vector2(16, -52) lang.size = Vector2(120, 36) lang.flat = true lang.connect("pressed", _on_lang) add_child(lang) func _make_btn(label: String, cb: Callable) -> Button: var btn := Button.new() btn.text = label btn.flat = false btn.alignment = HORIZONTAL_ALIGNMENT_LEFT btn.add_theme_font_size_override("font_size", 20) btn.connect("pressed", cb) return btn func _on_new_game() -> void: AudioManager.play("shop") # 首次启动:先引导玩家选难度(FTUE),否则直接开始 if SettingsManager.needs_ftue(): _ftue_layer.visible = true _rebuild_ftue() else: SceneManager.start_new_game() func _on_continue() -> void: if SceneManager.has_save(): AudioManager.play("shop") SceneManager.continue_game() func _on_leaderboard() -> void: _leaderboard_layer.visible = true _rebuild_leaderboard() func _on_settings() -> void: _settings_layer.visible = true _rebuild_settings() func _on_lang() -> void: SettingsManager.set_locale(Locale.cycle_locale()) # 重建菜单文本(语言已切换) for c in get_children(): c.queue_free() _settings_layer = null _leaderboard_layer = null _unlock_layer = null _settings_root = null _build_bg() _build_menu() _build_settings_panel() _build_leaderboard_panel() _build_unlock_panel() func _on_quit() -> void: if not _quit_confirm: _quit_confirm = true # 把退出按钮文字改为确认提示 for c in get_children(): if c is Button and tr("MENU_QUIT") in c.text: c.text = tr("MENU_QUIT_CONFIRM") c.modulate = Color(1.0, 0.4, 0.4) # 3 秒后自动取消确认态 var t := Timer.new() add_child(t) t.wait_time = 3.0 t.one_shot = true t.timeout.connect(func(): c.text = tr("MENU_QUIT") c.modulate = Color.WHITE _quit_confirm = false) t.start() return get_tree().quit() func _input(event: InputEvent) -> void: if event.is_action_pressed("ui_cancel"): if _settings_layer and _settings_layer.visible: _settings_layer.visible = false elif _leaderboard_layer and _leaderboard_layer.visible: _leaderboard_layer.visible = false elif _unlock_layer and _unlock_layer.visible: _unlock_layer.visible = false # ── 设置面板 ───────────────────────────────────────────────── func _build_settings_panel() -> void: _settings_layer = CanvasLayer.new() _settings_layer.layer = 10 _settings_layer.visible = false add_child(_settings_layer) var bg := ColorRect.new() bg.color = Color(0.0, 0.0, 0.0, 0.72) bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _settings_layer.add_child(bg) _settings_root = Control.new() _settings_layer.add_child(_settings_root) func _rebuild_settings() -> void: for c in _settings_root.get_children(): c.queue_free() var panel := ColorRect.new() panel.color = Color(0.06, 0.06, 0.14, 0.98) panel.size = Vector2(560, 440) panel.position = Vector2(300, 100) _settings_root.add_child(panel) var title := Label.new() title.text = tr("SETTINGS_TITLE") title.position = Vector2(324, 120) title.add_theme_font_size_override("font_size", 28) title.modulate = Color.GOLD _settings_root.add_child(title) _settings_row(tr("SETTINGS_DIFFICULTY") % tr(SettingsManager.difficulty_name()), 176, func(): SettingsManager.cycle_difficulty(); _rebuild_settings()) _settings_row(tr("SETTINGS_LANGUAGE") % Locale.get_locale(), 232, func(): SettingsManager.set_locale(Locale.cycle_locale()); _rebuild_settings()) var vol_lbl := Label.new() vol_lbl.text = tr("SETTINGS_VOLUME") % int(SettingsManager.master_volume * 100.0) vol_lbl.position = Vector2(324, 292) vol_lbl.add_theme_font_size_override("font_size", 16) _settings_root.add_child(vol_lbl) var slider := HSlider.new() slider.min_value = 0.0; slider.max_value = 1.0; slider.step = 0.05 slider.value = SettingsManager.master_volume slider.position = Vector2(324, 318); slider.size = Vector2(360, 24) slider.connect("value_changed", func(v): SettingsManager.set_master_volume(v); _rebuild_settings()) _settings_root.add_child(slider) var close := Button.new() close.text = tr("SETTINGS_CLOSE") close.position = Vector2(710, 504) close.size = Vector2(130, 40) close.connect("pressed", func(): _settings_layer.visible = false) _settings_root.add_child(close) func _settings_row(text: String, y: float, cb: Callable) -> void: var btn := Button.new() btn.text = text btn.position = Vector2(324, y) btn.size = Vector2(360, 40) btn.connect("pressed", cb) _settings_root.add_child(btn) # ── 排行榜面板 ─────────────────────────────────────────────── func _build_leaderboard_panel() -> void: _leaderboard_layer = CanvasLayer.new() _leaderboard_layer.layer = 10 _leaderboard_layer.visible = false add_child(_leaderboard_layer) func _rebuild_leaderboard() -> void: for c in _leaderboard_layer.get_children(): if c is ColorRect or c is Control: c.queue_free() var bg := ColorRect.new() bg.color = Color(0.0, 0.0, 0.0, 0.72) bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _leaderboard_layer.add_child(bg) var panel := ColorRect.new() panel.color = Color(0.05, 0.05, 0.13, 0.98) panel.size = Vector2(640, 500) panel.position = Vector2(256, 74) _leaderboard_layer.add_child(panel) var title := Label.new() title.text = tr("SETTLE_BOARD") title.position = Vector2(280, 92) title.add_theme_font_size_override("font_size", 22) title.modulate = Color.GOLD _leaderboard_layer.add_child(title) var top: Array = EndlessRecords.get_top(10) if top.is_empty(): var empty := Label.new() empty.text = tr("LEADERBOARD_EMPTY") empty.position = Vector2(440, 300) empty.add_theme_font_size_override("font_size", 18) empty.modulate = Color(0.6, 0.6, 0.6) _leaderboard_layer.add_child(empty) else: for i in top.size(): var rec: Dictionary = top[i] var row := Label.new() var elapsed: int = int(rec.get("elapsed", 0)) row.text = "%2d. Wave %d %d:%02d Score %d" % [ i + 1, int(rec.get("wave", 0)), int(elapsed / 60.0), elapsed % 60, int(rec.get("score", 0))] row.position = Vector2(296, 140 + i*30) row.add_theme_font_size_override("font_size", 15) _leaderboard_layer.add_child(row) var close := Button.new() close.text = tr("SETTINGS_CLOSE") close.position = Vector2(762, 538) close.size = Vector2(120, 40) close.connect("pressed", func(): _leaderboard_layer.visible = false) _leaderboard_layer.add_child(close) # ── FTUE:首启难度引导 ─────────────────────────────────────── func _build_ftue_panel() -> void: _ftue_layer = CanvasLayer.new() _ftue_layer.layer = 12 _ftue_layer.visible = false add_child(_ftue_layer) func _rebuild_ftue() -> void: for c in _ftue_layer.get_children(): c.queue_free() var dim := ColorRect.new() dim.color = Color(0.02, 0.02, 0.06, 0.97) dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _ftue_layer.add_child(dim) var title := Label.new() title.text = tr("FTUE_TITLE") title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title.set_anchors_preset(Control.PRESET_TOP_WIDE) title.position.y = 70 title.add_theme_font_size_override("font_size", 38) title.modulate = Color(1.0, 0.85, 0.3) _ftue_layer.add_child(title) # 三张难度卡片(横排) var cards: Array = [ [0, "DIFF_BEGINNER", "FTUE_BEGINNER_DESC", Color(0.3, 0.85, 0.5)], [1, "DIFF_STANDARD", "FTUE_STANDARD_DESC", Color(0.4, 0.7, 1.0)], [2, "DIFF_CHALLENGE", "FTUE_CHALLENGE_DESC", Color(1.0, 0.45, 0.4)], ] var card_w: float = 300.0 var gap: float = 30.0 var total_w: float = card_w * 3 + gap * 2 var start_x: float = (1152.0 - total_w) / 2.0 for i in cards.size(): var cd: Array = cards[i] var x: float = start_x + i * (card_w + gap) var card := Button.new() card.position = Vector2(x, 200) card.size = Vector2(card_w, 240) card.connect("pressed", _on_ftue_pick.bind(int(cd[0]))) _ftue_layer.add_child(card) # 难度名 var name_lbl := Label.new() name_lbl.text = tr(cd[1]) name_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER name_lbl.size = Vector2(card_w, 40) name_lbl.position = Vector2(x, 230) name_lbl.add_theme_font_size_override("font_size", 30) name_lbl.modulate = cd[3] name_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE _ftue_layer.add_child(name_lbl) # 说明 var desc := Label.new() desc.text = tr(cd[2]) desc.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART desc.size = Vector2(card_w - 40, 120) desc.position = Vector2(x + 20, 300) desc.add_theme_font_size_override("font_size", 16) desc.modulate = Color(0.85, 0.85, 0.9) desc.mouse_filter = Control.MOUSE_FILTER_IGNORE _ftue_layer.add_child(desc) var hint := Label.new() hint.text = tr("FTUE_HINT") hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER hint.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) hint.position.y = -60 hint.add_theme_font_size_override("font_size", 14) hint.modulate = Color(0.55, 0.55, 0.6) _ftue_layer.add_child(hint) func _on_ftue_pick(difficulty_idx: int) -> void: SettingsManager.complete_ftue(difficulty_idx) # 设难度 + 标记 FTUE 完成 + 存档 AudioManager.play("buy") _ftue_layer.visible = false SceneManager.start_new_game() # ── 解锁面板 ───────────────────────────────────────────────── func _on_unlock() -> void: _unlock_layer.visible = true _rebuild_unlock() func _build_unlock_panel() -> void: _unlock_layer = CanvasLayer.new() _unlock_layer.layer = 10 _unlock_layer.visible = false add_child(_unlock_layer) var bg := ColorRect.new() bg.color = Color(0.0, 0.0, 0.0, 0.72) bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) _unlock_layer.add_child(bg) _unlock_root = Control.new() _unlock_layer.add_child(_unlock_root) func _rebuild_unlock() -> void: for c in _unlock_root.get_children(): c.queue_free() var panel := ColorRect.new() panel.color = Color(0.06, 0.06, 0.14, 0.98) panel.size = Vector2(560, 470) panel.position = Vector2(300, 90) _unlock_root.add_child(panel) var title := Label.new() title.text = tr("META_TITLE") title.position = Vector2(324, 106) title.add_theme_font_size_override("font_size", 28) title.modulate = Color.GOLD _unlock_root.add_child(title) var aether_lbl := Label.new() aether_lbl.text = tr("META_AETHER") % MetaProgress.aether aether_lbl.position = Vector2(324, 148) aether_lbl.add_theme_font_size_override("font_size", 18) aether_lbl.modulate = Color(0.6, 0.85, 1.0) _unlock_root.add_child(aether_lbl) var y: float = 188.0 for id in SpellRegistry.get_all_ids(): var s: SpellNode = SpellRegistry.get_spell(id) if s == null: continue var cost: int = int(s.meta.get("unlock_cost", 0)) if cost <= 0: continue var name_lbl := Label.new() name_lbl.text = s.display_name name_lbl.position = Vector2(324, y + 4) name_lbl.add_theme_font_size_override("font_size", 16) _unlock_root.add_child(name_lbl) var btn := Button.new() btn.position = Vector2(560, y) btn.size = Vector2(230, 32) if MetaProgress.is_unlocked(id): btn.text = tr("META_UNLOCKED") btn.disabled = true else: btn.text = tr("META_UNLOCK_BTN") % cost btn.disabled = MetaProgress.aether < cost var sid: String = id var scost: int = cost btn.connect("pressed", func(): if MetaProgress.unlock(sid, scost): AudioManager.play("buy") _rebuild_unlock()) _unlock_root.add_child(btn) y += 40.0 var close := Button.new() close.text = tr("META_CLOSE") close.position = Vector2(700, 508) close.size = Vector2(120, 36) close.connect("pressed", func(): _unlock_layer.visible = false) _unlock_root.add_child(close)