初次提交

This commit is contained in:
2026-07-20 10:56:52 +08:00
commit 7bcc0026e0
462 changed files with 50191 additions and 0 deletions
+353
View File
@@ -0,0 +1,353 @@
## 主菜单:新游戏 / 继续 / 排行榜 / 设置 / 退出
extends Control
var _settings_layer: CanvasLayer = null
var _settings_root: Control = null
var _leaderboard_layer: CanvasLayer = null
var _ftue_layer: CanvasLayer = 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()
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_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
_settings_root = null
_build_bg()
_build_menu()
_build_settings_panel()
_build_leaderboard_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
# ── 设置面板 ─────────────────────────────────────────────────
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()
+1
View File
@@ -0,0 +1 @@
uid://o7kn0dw126em
+10
View File
@@ -0,0 +1,10 @@
[gd_scene format=3 uid="uid://cu6pbsyyjldos"]
[ext_resource type="Script" uid="uid://o7kn0dw126em" path="res://scenes/ui/main_menu.gd" id="1"]
[node name="MainMenu" type="Control" unique_id=369072083]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1")
+91
View File
@@ -0,0 +1,91 @@
## 启动画面:公司/游戏商标 + 引擎版权 → 自动跳转主菜单
extends Control
const HOLD_SEC: float = 1.8 # 完全显示后停留时长
var _timer: float = 0.0
var _skip_ready: bool = false # 防止首帧立即跳过
func _ready() -> void:
SceneManager.fade_in_first()
_build_ui()
# 延迟一帧后才允许跳过(防止启动时按键残留)
call_deferred("_allow_skip")
func _allow_skip() -> void:
_skip_ready = true
func _build_ui() -> 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 studio := Label.new()
studio.text = "LANTERN GAMES"
studio.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
studio.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
studio.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
studio.position.y = -80
studio.add_theme_font_size_override("font_size", 18)
studio.modulate = Color(0.7, 0.7, 0.7)
add_child(studio)
# 游戏标题
var title := Label.new()
title.text = "魔法工匠"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
title.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
title.add_theme_font_size_override("font_size", 64)
title.modulate = Color(1.0, 0.85, 0.3)
add_child(title)
# 英文副标题
var sub := Label.new()
sub.text = "Arcane Artificer"
sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
sub.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
sub.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
sub.position.y = 64
sub.add_theme_font_size_override("font_size", 22)
sub.modulate = Color(0.7, 0.75, 1.0)
add_child(sub)
# 提示
var hint := Label.new()
hint.text = tr("SPLASH_SKIP_HINT")
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
hint.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
hint.position.y = -40
hint.add_theme_font_size_override("font_size", 13)
hint.modulate = Color(0.5, 0.5, 0.5)
add_child(hint)
# 版权
var copy := Label.new()
copy.text = "© 2026 Lantern Games | Powered by Godot 4.6"
copy.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
copy.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
copy.position.y = -16
copy.add_theme_font_size_override("font_size", 11)
copy.modulate = Color(0.4, 0.4, 0.4)
add_child(copy)
func _process(delta: float) -> void:
_timer += delta
if _timer >= HOLD_SEC:
_go_to_menu()
func _input(event: InputEvent) -> void:
if not _skip_ready:
return
if event.is_action_pressed("ui_accept") or event.is_action_pressed("ui_cancel") \
or (event is InputEventMouseButton and event.pressed):
_go_to_menu()
func _go_to_menu() -> void:
set_process(false)
set_process_input(false)
SceneManager.goto_main_menu()
+1
View File
@@ -0,0 +1 @@
uid://uch25wxg7638
+8
View File
@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://splash_scene"]
[ext_resource type="Script" path="res://scenes/ui/splash.gd" id="1"]
[node name="Splash" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1")