feat(shop): 商店「属性」区独立子面板 UI + 4 语言 i18n 键

主商店面板已无空间容纳四行属性(面板 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 11:46:05 +08:00
co-authored by Claude Opus 5
parent cc57980e63
commit ad98f916e9
6 changed files with 145 additions and 0 deletions
+83
View File
@@ -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()
# ── 事件 ─────────────────────────────────────────────────