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:
@@ -41,6 +41,12 @@ var _core_btn: Button = null
|
|||||||
var _inv_btn: Button = null
|
var _inv_btn: Button = null
|
||||||
var _lang_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_layer: CanvasLayer = null
|
||||||
var _inv_root: Control = null
|
var _inv_root: Control = null
|
||||||
@@ -71,6 +77,7 @@ func _ready() -> void:
|
|||||||
_setup_world_view()
|
_setup_world_view()
|
||||||
_setup_hud()
|
_setup_hud()
|
||||||
_setup_shop_ui()
|
_setup_shop_ui()
|
||||||
|
_setup_attr_shop_ui()
|
||||||
_setup_inventory_ui()
|
_setup_inventory_ui()
|
||||||
_setup_settlement_ui()
|
_setup_settlement_ui()
|
||||||
_setup_settings_ui()
|
_setup_settings_ui()
|
||||||
@@ -84,6 +91,8 @@ func _input(event: InputEvent) -> void:
|
|||||||
_close_inventory()
|
_close_inventory()
|
||||||
elif _settings_layer and _settings_layer.visible:
|
elif _settings_layer and _settings_layer.visible:
|
||||||
_close_settings()
|
_close_settings()
|
||||||
|
elif _attr_layer and _attr_layer.visible:
|
||||||
|
_close_attr_shop()
|
||||||
elif _settle_layer and _settle_layer.visible:
|
elif _settle_layer and _settle_layer.visible:
|
||||||
pass # 结算屏:ESC 无效,必须按按钮
|
pass # 结算屏:ESC 无效,必须按按钮
|
||||||
elif _shop_layer and _shop_layer.visible:
|
elif _shop_layer and _shop_layer.visible:
|
||||||
@@ -343,6 +352,15 @@ func _setup_shop_ui() -> void:
|
|||||||
settings_btn.connect("pressed", _open_settings)
|
settings_btn.connect("pressed", _open_settings)
|
||||||
_shop_layer.add_child(settings_btn)
|
_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 = Button.new()
|
||||||
_close_btn.text = tr("SHOP_NEXT_WAVE")
|
_close_btn.text = tr("SHOP_NEXT_WAVE")
|
||||||
_close_btn.position = Vector2(550, 400)
|
_close_btn.position = Vector2(550, 400)
|
||||||
@@ -356,6 +374,62 @@ func _setup_shop_ui() -> void:
|
|||||||
_shop_warn.visible = false
|
_shop_warn.visible = false
|
||||||
_shop_layer.add_child(_shop_warn)
|
_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 刷新 ────────────────────────────────────────────────
|
# ── HUD 刷新 ────────────────────────────────────────────────
|
||||||
|
|
||||||
func _refresh_hud() -> void:
|
func _refresh_hud() -> void:
|
||||||
@@ -403,6 +477,15 @@ func _refresh_shop_ui() -> void:
|
|||||||
_core_btn.text = tr("SHOP_CORE_BTN") % _cm.get_core_display()
|
_core_btn.text = tr("SHOP_CORE_BTN") % _cm.get_core_display()
|
||||||
if _lang_btn:
|
if _lang_btn:
|
||||||
_lang_btn.text = "🌐 " + Locale.get_locale()
|
_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()
|
||||||
|
|
||||||
# ── 事件 ─────────────────────────────────────────────────
|
# ── 事件 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ func get_attr_price(attr_id: String) -> int:
|
|||||||
return PriceFormula.compute(sp, get_attr_purchases(attr_id))
|
return PriceFormula.compute(sp, get_attr_purchases(attr_id))
|
||||||
|
|
||||||
## 返回 "" 表示可买;否则为禁用原因(UI 直接显示,不要只灰掉按钮)
|
## 返回 "" 表示可买;否则为禁用原因(UI 直接显示,不要只灰掉按钮)
|
||||||
|
## ⚠️ i18n 债务:以下禁用原因是中文裸串,未经 tr(),不随语言切换——这与法术/核心/状态等
|
||||||
|
## display_name 现状一致,本期不制造新例外;统一处理见路线图 E7-③「内容名 tr 化」
|
||||||
func can_buy_attribute(attr_id: String) -> String:
|
func can_buy_attribute(attr_id: String) -> String:
|
||||||
var d: Dictionary = _attr_def.get(attr_id, {})
|
var d: Dictionary = _attr_def.get(attr_id, {})
|
||||||
var sp = d.get("shop", null)
|
var sp = d.get("shop", null)
|
||||||
|
|||||||
@@ -51,6 +51,21 @@ msgstr "🎒 Inventory"
|
|||||||
msgid "SHOP_SOLD"
|
msgid "SHOP_SOLD"
|
||||||
msgstr "(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"
|
msgid "INV_TITLE"
|
||||||
msgstr "🎒 Inventory — [%s] %d slots (%s)"
|
msgstr "🎒 Inventory — [%s] %d slots (%s)"
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,21 @@ msgstr "🎒 バッグ"
|
|||||||
msgid "SHOP_SOLD"
|
msgid "SHOP_SOLD"
|
||||||
msgstr "(売却済)"
|
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"
|
msgid "INV_TITLE"
|
||||||
msgstr "🎒 バッグ — [%s] %d スロット (%s)"
|
msgstr "🎒 バッグ — [%s] %d スロット (%s)"
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,21 @@ msgstr "🎒 背包"
|
|||||||
msgid "SHOP_SOLD"
|
msgid "SHOP_SOLD"
|
||||||
msgstr "(已售出)"
|
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"
|
msgid "INV_TITLE"
|
||||||
msgstr "🎒 背包 — [%s] %d 槽 (%s)"
|
msgstr "🎒 背包 — [%s] %d 槽 (%s)"
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,21 @@ msgstr "🎒 背包"
|
|||||||
msgid "SHOP_SOLD"
|
msgid "SHOP_SOLD"
|
||||||
msgstr "(已售出)"
|
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"
|
msgid "INV_TITLE"
|
||||||
msgstr "🎒 背包 — [%s] %d 槽 (%s)"
|
msgstr "🎒 背包 — [%s] %d 槽 (%s)"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user