From ad98f916e9419e34fe011692b34dc6b5f2ee93a9 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Mon, 3 Aug 2026 11:46:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(shop):=20=E5=95=86=E5=BA=97=E3=80=8C?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E3=80=8D=E5=8C=BA=E7=8B=AC=E7=AB=8B=E5=AD=90?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=20UI=20+=204=20=E8=AF=AD=E8=A8=80=20i18n=20?= =?UTF-8?q?=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主商店面板已无空间容纳四行属性(面板 y=200~540,既有控件已占到 y≈522,仅剩约 18px), 经确认后改为独立子面板方案(参照 _setup_settings_ui 既有模式),不改动主面板任何既有控件坐标。 按 get_sellable_attrs() 动态生成行,加可售属性无需改 UI 代码。 禁用态显示具体原因(金币不足/已达上限)而非只灰掉按钮。 can_buy_attribute 注释补 i18n 债务说明(禁用原因暂未 tr(),见路线图 E7-③)。 Co-Authored-By: Claude Opus 5 --- scenes/main/combat_s2.gd | 83 +++++++++++++++++++++++++++++++ scripts/autoloads/shop_manager.gd | 2 + translations/en.po | 15 ++++++ translations/ja.po | 15 ++++++ translations/zh_CN.po | 15 ++++++ translations/zh_TW.po | 15 ++++++ 6 files changed, 145 insertions(+) diff --git a/scenes/main/combat_s2.gd b/scenes/main/combat_s2.gd index 8c74630..d045779 100644 --- a/scenes/main/combat_s2.gd +++ b/scenes/main/combat_s2.gd @@ -41,6 +41,12 @@ var _core_btn: Button = null var _inv_btn: Button = null var _lang_btn: Button = null +## 属性购买(货架 C):独立子面板,由商店面板上的入口按钮打开 +## (商店面板自身已无空间容纳四行属性——面板 y=200~540,既有控件已占到 y≈522, +## 仅剩约 18px,故不内联在商店面板里,改开独立弹窗,参照 _setup_settings_ui 的结构) +var _attr_layer: CanvasLayer = null +var _attr_rows: Array = [] # [{id: String, label: Label, button: Button}] + ## 背包(插槽编辑) var _inv_layer: CanvasLayer = null var _inv_root: Control = null @@ -71,6 +77,7 @@ func _ready() -> void: _setup_world_view() _setup_hud() _setup_shop_ui() + _setup_attr_shop_ui() _setup_inventory_ui() _setup_settlement_ui() _setup_settings_ui() @@ -84,6 +91,8 @@ func _input(event: InputEvent) -> void: _close_inventory() elif _settings_layer and _settings_layer.visible: _close_settings() + elif _attr_layer and _attr_layer.visible: + _close_attr_shop() elif _settle_layer and _settle_layer.visible: pass # 结算屏:ESC 无效,必须按按钮 elif _shop_layer and _shop_layer.visible: @@ -343,6 +352,15 @@ func _setup_shop_ui() -> void: settings_btn.connect("pressed", _open_settings) _shop_layer.add_child(settings_btn) + # 属性购买入口(货架 C):面板 y=445 行内剩余空白(settings_btn 右边到面板边缘 x=610~800), + # 不改动本函数内任何既有控件的坐标;点击打开独立子面板 _attr_layer(见 _setup_attr_shop_ui) + var attr_btn := Button.new() + attr_btn.text = tr("SHOP_ATTR_BTN") + attr_btn.position = Vector2(630, 445) + attr_btn.size = Vector2(150, 34) + attr_btn.connect("pressed", _open_attr_shop) + _shop_layer.add_child(attr_btn) + _close_btn = Button.new() _close_btn.text = tr("SHOP_NEXT_WAVE") _close_btn.position = Vector2(550, 400) @@ -356,6 +374,62 @@ func _setup_shop_ui() -> void: _shop_warn.visible = false _shop_layer.add_child(_shop_warn) +# ── 属性购买(货架 C)子面板 ──────────────────────────────── +## 完全数据驱动:行数与内容全部来自 ShopManager.get_sellable_attrs()/get_attr_def(), +## 本函数不含任何属性 id 的硬编码分支;新增可售属性只需改 data/attributes.json 的 shop 段 +func _setup_attr_shop_ui() -> void: + _attr_layer = CanvasLayer.new() + _attr_layer.name = "AttrShopLayer" + _attr_layer.visible = false + add_child(_attr_layer) + + var bg := ColorRect.new() + bg.color = Color(0.06, 0.06, 0.12, 0.97) + bg.size = Vector2(560, 320) + bg.position = Vector2(300, 160) + _attr_layer.add_child(bg) + + var title := Label.new() + title.text = tr("SHOP_ATTR_TITLE") + title.position = Vector2(324, 178) + title.add_theme_font_size_override("font_size", 20) + title.modulate = Color.GOLD + _attr_layer.add_child(title) + + _attr_rows.clear() + var ids: Array[String] = ShopManager.get_sellable_attrs() + for i in ids.size(): + var id: String = ids[i] + var y: float = 220.0 + float(i) * 34.0 + var lbl := Label.new() + lbl.position = Vector2(324, y) + lbl.size = Vector2(300, 30) + _attr_layer.add_child(lbl) + var btn := Button.new() + btn.position = Vector2(640, y) + btn.size = Vector2(180, 30) + btn.connect("pressed", _on_buy_attr_pressed.bind(id)) + _attr_layer.add_child(btn) + _attr_rows.append({"id": id, "label": lbl, "button": btn}) + + var close := Button.new() + close.text = tr("SHOP_ATTR_CLOSE") + close.position = Vector2(700, 420) + close.size = Vector2(120, 40) + close.connect("pressed", _close_attr_shop) + _attr_layer.add_child(close) + +func _open_attr_shop() -> void: + _attr_layer.visible = true + _refresh_shop_ui() + +func _close_attr_shop() -> void: + _attr_layer.visible = false + +func _on_buy_attr_pressed(id: String) -> void: + if ShopManager.buy_attribute(id): + _refresh_shop_ui() + # ── HUD 刷新 ──────────────────────────────────────────────── func _refresh_hud() -> void: @@ -403,6 +477,15 @@ func _refresh_shop_ui() -> void: _core_btn.text = tr("SHOP_CORE_BTN") % _cm.get_core_display() if _lang_btn: _lang_btn.text = "🌐 " + Locale.get_locale() + for row in _attr_rows: + var id: String = row["id"] + var reason: String = ShopManager.can_buy_attribute(id) + var price: int = ShopManager.get_attr_price(id) + var n: int = ShopManager.get_attr_purchases(id) + var disp: String = String(ShopManager.get_attr_def(id).get("display_name", id)) + row["label"].text = tr("SHOP_ATTR_ROW") % [disp, "%.2f" % PlayerStats.get_attr_value(id), n] + row["button"].text = (tr("SHOP_ATTR_BUY") % price) if reason.is_empty() else reason + row["button"].disabled = not reason.is_empty() # ── 事件 ───────────────────────────────────────────────── diff --git a/scripts/autoloads/shop_manager.gd b/scripts/autoloads/shop_manager.gd index 8d037b6..7c3456a 100644 --- a/scripts/autoloads/shop_manager.gd +++ b/scripts/autoloads/shop_manager.gd @@ -128,6 +128,8 @@ func get_attr_price(attr_id: String) -> int: return PriceFormula.compute(sp, get_attr_purchases(attr_id)) ## 返回 "" 表示可买;否则为禁用原因(UI 直接显示,不要只灰掉按钮) +## ⚠️ i18n 债务:以下禁用原因是中文裸串,未经 tr(),不随语言切换——这与法术/核心/状态等 +## display_name 现状一致,本期不制造新例外;统一处理见路线图 E7-③「内容名 tr 化」 func can_buy_attribute(attr_id: String) -> String: var d: Dictionary = _attr_def.get(attr_id, {}) var sp = d.get("shop", null) diff --git a/translations/en.po b/translations/en.po index cd81a59..6ee7910 100644 --- a/translations/en.po +++ b/translations/en.po @@ -51,6 +51,21 @@ msgstr "🎒 Inventory" msgid "SHOP_SOLD" msgstr "(Sold)" +msgid "SHOP_ATTR_BTN" +msgstr "📊 Attributes" + +msgid "SHOP_ATTR_TITLE" +msgstr "📊 Buy Attributes" + +msgid "SHOP_ATTR_ROW" +msgstr "%s Current %s Bought %d×" + +msgid "SHOP_ATTR_BUY" +msgstr "Buy %dG" + +msgid "SHOP_ATTR_CLOSE" +msgstr "Close" + msgid "INV_TITLE" msgstr "🎒 Inventory — [%s] %d slots (%s)" diff --git a/translations/ja.po b/translations/ja.po index e4c9b0c..5c6c13b 100644 --- a/translations/ja.po +++ b/translations/ja.po @@ -51,6 +51,21 @@ msgstr "🎒 バッグ" msgid "SHOP_SOLD" msgstr "(売却済)" +msgid "SHOP_ATTR_BTN" +msgstr "📊 属性" + +msgid "SHOP_ATTR_TITLE" +msgstr "📊 属性購入" + +msgid "SHOP_ATTR_ROW" +msgstr "%s 現在 %s 購入回数 %d" + +msgid "SHOP_ATTR_BUY" +msgstr "購入 %dG" + +msgid "SHOP_ATTR_CLOSE" +msgstr "閉じる" + msgid "INV_TITLE" msgstr "🎒 バッグ — [%s] %d スロット (%s)" diff --git a/translations/zh_CN.po b/translations/zh_CN.po index b1797c6..8063270 100644 --- a/translations/zh_CN.po +++ b/translations/zh_CN.po @@ -51,6 +51,21 @@ msgstr "🎒 背包" msgid "SHOP_SOLD" msgstr "(已售出)" +msgid "SHOP_ATTR_BTN" +msgstr "📊 属性" + +msgid "SHOP_ATTR_TITLE" +msgstr "📊 属性购买" + +msgid "SHOP_ATTR_ROW" +msgstr "%s 当前 %s 已购 %d 次" + +msgid "SHOP_ATTR_BUY" +msgstr "购买 %dG" + +msgid "SHOP_ATTR_CLOSE" +msgstr "关闭" + msgid "INV_TITLE" msgstr "🎒 背包 — [%s] %d 槽 (%s)" diff --git a/translations/zh_TW.po b/translations/zh_TW.po index da2424e..596798b 100644 --- a/translations/zh_TW.po +++ b/translations/zh_TW.po @@ -51,6 +51,21 @@ msgstr "🎒 背包" msgid "SHOP_SOLD" msgstr "(已售出)" +msgid "SHOP_ATTR_BTN" +msgstr "📊 屬性" + +msgid "SHOP_ATTR_TITLE" +msgstr "📊 屬性購買" + +msgid "SHOP_ATTR_ROW" +msgstr "%s 目前 %s 已購 %d 次" + +msgid "SHOP_ATTR_BUY" +msgstr "購買 %dG" + +msgid "SHOP_ATTR_CLOSE" +msgstr "關閉" + msgid "INV_TITLE" msgstr "🎒 背包 — [%s] %d 槽 (%s)"