diff --git a/game/assets/ui/icons/status_ac_up.png b/game/assets/ui/icons/status_ac_up.png new file mode 100644 index 00000000..5b029195 Binary files /dev/null and b/game/assets/ui/icons/status_ac_up.png differ diff --git a/game/assets/ui/icons/status_alert.png b/game/assets/ui/icons/status_alert.png new file mode 100644 index 00000000..d793db8b Binary files /dev/null and b/game/assets/ui/icons/status_alert.png differ diff --git a/game/assets/ui/icons/status_attack_up.png b/game/assets/ui/icons/status_attack_up.png new file mode 100644 index 00000000..17a0f777 Binary files /dev/null and b/game/assets/ui/icons/status_attack_up.png differ diff --git a/game/assets/ui/icons/status_empower.png b/game/assets/ui/icons/status_empower.png new file mode 100644 index 00000000..1726b348 Binary files /dev/null and b/game/assets/ui/icons/status_empower.png differ diff --git a/game/assets/ui/icons/status_enrage.png b/game/assets/ui/icons/status_enrage.png new file mode 100644 index 00000000..17a0f777 Binary files /dev/null and b/game/assets/ui/icons/status_enrage.png differ diff --git a/game/assets/ui/icons/status_fortify.png b/game/assets/ui/icons/status_fortify.png new file mode 100644 index 00000000..5b029195 Binary files /dev/null and b/game/assets/ui/icons/status_fortify.png differ diff --git a/game/assets/ui/icons/status_off_guard.png b/game/assets/ui/icons/status_off_guard.png new file mode 100644 index 00000000..0bf95fd3 Binary files /dev/null and b/game/assets/ui/icons/status_off_guard.png differ diff --git a/game/assets/ui/icons/status_sapped.png b/game/assets/ui/icons/status_sapped.png new file mode 100644 index 00000000..e2872b23 Binary files /dev/null and b/game/assets/ui/icons/status_sapped.png differ diff --git a/game/assets/ui/icons/status_weaken.png b/game/assets/ui/icons/status_weaken.png new file mode 100644 index 00000000..e2872b23 Binary files /dev/null and b/game/assets/ui/icons/status_weaken.png differ diff --git a/game/presentation/views/standee_view.gd b/game/presentation/views/standee_view.gd index e921dde7..27441a9c 100644 --- a/game/presentation/views/standee_view.gd +++ b/game/presentation/views/standee_view.gd @@ -2,7 +2,8 @@ # 实现 GDD:design/ux/battle.md(B 区全站桩形态——2026-09-20 用户看对照样张拍板,台账 99: # 主角与敌人均为无框立绘+底座,英雄卡/敌人卡卡片式整体退役禁再提)。 # 形态契约:无框立绘(程序占位字形;set_portrait_texture = 资产挂点,战斗表现样张批落位零代码换装) -# + 底座;血条悬浮头顶;状态小图标一排挂血条正下方(形状+色相双通道、层数角标同 C 区状态槽键—— +# + 底座;血条悬浮头顶;状态小图标一排挂血条正下方(2026-09-24 状态图标接入批:定稿 PNG 纹理 +# 取代程序化占位字形,极性染色退役——绿↑红↓极性语言内化于图标本体;层数 ×N 角标保留; # 词汇单点复用 StatusSlotRow.EFFECT_ICON,未知键同款 fail-fast + ? 占位)。 # 敌方专属:顺位徽标悬浮头顶;意图三档动作动画占位(表现层升级批拍板:不显示技能名——普攻零表示 / # 攻击系 ⚔ / 增益系 ✦ / 妨害系 ☠ / 蓄力 = ⚔ 金档;开轮摇定播一次性短动画后静帧保持, @@ -151,18 +152,32 @@ func set_idle() -> void: set_effects([]) -# 主角/敌方共用——状态小图标行重建(B 区迷你版:icon + 层数角标;完整 tooltip 义务归 C 区状态槽) +# 主角/敌方共用——状态小图标行重建(B 区迷你版:定稿纹理 16px + 层数 ×N 角标; +# 完整 tooltip 义务归 C 区状态槽。2026-09-24 状态图标接入批:占位字形+极性染色退役) func set_effects(effects: Array) -> void: for c in _status_row.get_children(): _status_row.remove_child(c) c.queue_free() for eff in effects: - var cfg: Array = _icon_cfg(String(eff.get("effect_id", ""))) + var tex := _icon_tex(String(eff.get("effect_id", ""))) var layers := maxi(1, int(eff.get("layers", 1))) - var lab := UiKit.label(String(cfg[0]) + ("×%d" % layers if layers > 1 else ""), 12, true) - lab.add_theme_color_override("font_color", cfg[1]) - lab.mouse_filter = Control.MOUSE_FILTER_IGNORE - _status_row.add_child(lab) + if tex != null: + var icon := TextureRect.new() + icon.texture = tex + icon.custom_minimum_size = Vector2(16, 16) # 32px 源 ÷2 整数档缩放 + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR + icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + _status_row.add_child(icon) + else: + var ph := UiKit.label(StatusSlotRow.ICON_PLACEHOLDER, 12, true) # release 回退占位(debug 已断言) + ph.mouse_filter = Control.MOUSE_FILTER_IGNORE + _status_row.add_child(ph) + if layers > 1: + var lab := UiKit.label("×%d" % layers, 12, true) + lab.mouse_filter = Control.MOUSE_FILTER_IGNORE + _status_row.add_child(lab) func set_order(i: int) -> void: @@ -283,10 +298,10 @@ func _refresh_portrait_glyph() -> void: # 状态图标键解析(词汇单点复用 C 区 StatusSlotRow——HUD框架:查不到的键按错误处理, -# 立即断言 + ? 占位纹样 + 诊断,禁止静默显示空图标) -func _icon_cfg(effect_id: String) -> Array: +# 立即断言 + ? 占位 + 诊断,禁止静默显示空图标) +func _icon_tex(effect_id: String) -> Texture2D: if StatusSlotRow.EFFECT_ICON.has(effect_id): return StatusSlotRow.EFFECT_ICON[effect_id] push_error("[站桩] 未知状态图标键(映射表外键——立即断言): %s" % effect_id) assert(false, "未知状态图标键: " + effect_id) - return ["?", UiKit.PANEL_BORDER, "?"] + return null diff --git a/game/presentation/views/status_slot_row.gd b/game/presentation/views/status_slot_row.gd index 11d22ea0..6197e5fa 100644 --- a/game/presentation/views/status_slot_row.gd +++ b/game/presentation/views/status_slot_row.gd @@ -1,7 +1,9 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# 实现 GDD:design/ux/battle.md(C 区状态效果槽 ×4〔增益状态 D1×①〕——effect_id→icon 形状+色相双通道、 +# 实现 GDD:design/ux/battle.md(C 区状态效果槽 ×4〔增益状态 D1×①〕——effect_id→定稿 PNG 纹理图标、 # 层数角标、剩余结算次数点、悬停 tooltip(来源+语义+剩余);空态 = 槽隐藏(数据框上移补位, # EC12 空态裁决);极性映射 = 战斗结算 SettlementTable 效果语义而非 magnitude 符号〔增益状态 D1×③〕)。 +# 2026-09-24 状态图标接入批:程序化占位字形(▲◇▼◎⚔✕☾⇧▣)+ 按极性染色退役——定稿九枚 PNG +# (白剑/白盾/白眼+绿↑红↓箭头语言,用户逐键验收,圣经 §4.6.1 v2.2 纹样系统)自带极性,裸图标呈现。 # Date: 2026-09-12 # 高内聚边界:纯视图——效果投影装配归 hud_presenter.gd;增益状态 纯被动容器零信号——刷新绑 战斗结算 # 结算编排完成时点(battle.md 更新触发注记)。 @@ -9,22 +11,28 @@ class_name StatusSlotRow extends HBoxContainer const MAX_SLOTS := 4 -# 极性/语义映射(数据源 = SettlementTable.effect_pool_map 效果语义 + StatusEffectTable display_name): -# 益 = ▲/◆/▼/◇(金/蓝族);害 = ⚡/⚔/✕/☾/⇧/▣(旗朱族;⇧/▣ = 敌方增益威胁纹样)——形状+色相双通道, -# 文字名常驻第三通道。状态表九行全映射(attack_up/ac_up/weaken/alert/enrage/off_guard/sapped/empower/fortify) -# ——HUD框架 图标键契约"一键一枚专属纹样,查不到的键立即断言 + ? 占位 + 诊断" +# 纹理映射(2026-09-24 状态图标接入批):effect_id→定稿纹理,全局恒定禁换图(图标键契约 §4.6 的 +# 文件侧延伸——文件键 = status_.png,命名规范圣经 §8.3「对象键 = 数据表主键」)。 +# 状态表九行全映射(attack_up/ac_up/weaken/alert/enrage/off_guard/sapped/empower/fortify) +# ——HUD框架 图标键契约"一键一枚专属纹样,查不到的键立即断言 + 占位 + 诊断" # (2026-09-15 格面资源批补 sapped;2026-09-16 护甲等级掷点批 defense_up → ac_up 换装; # 同日 敌人技能池批补 empower/fortify 敌方增益两键——敌方增益显示 = 敌卡状态行文本 + 本图标键词汇)。 const EFFECT_ICON := { - "attack_up": ["▲", UiKit.GOLD_DEEP, "益"], - "ac_up": ["◇", UiKit.HERO_BLUE, "益"], - "weaken": ["▼", UiKit.HERO_BLUE, "益"], - "alert": ["◎", UiKit.FLAG_RED, "害"], - "enrage": ["⚔", UiKit.FLAG_RED, "害"], - "off_guard": ["✕", UiKit.FLAG_RED, "害"], - "sapped": ["☾", UiKit.FLAG_RED, "害"], - "empower": ["⇧", UiKit.FLAG_RED, "害"], - "fortify": ["▣", UiKit.PANEL_BORDER, "害"], + "attack_up": preload("res://assets/ui/icons/status_attack_up.png"), + "ac_up": preload("res://assets/ui/icons/status_ac_up.png"), + "weaken": preload("res://assets/ui/icons/status_weaken.png"), + "alert": preload("res://assets/ui/icons/status_alert.png"), + "enrage": preload("res://assets/ui/icons/status_enrage.png"), + "off_guard": preload("res://assets/ui/icons/status_off_guard.png"), + "sapped": preload("res://assets/ui/icons/status_sapped.png"), + "empower": preload("res://assets/ui/icons/status_empower.png"), + "fortify": preload("res://assets/ui/icons/status_fortify.png"), +} +# 极性词(tooltip 第三通道;运行时不再按极性染色/画框——绿↑红↓极性语言已内化于定稿图标本体) +const EFFECT_POLARITY := { + "attack_up": "益", "ac_up": "益", "weaken": "益", + "alert": "害", "enrage": "害", "off_guard": "害", + "sapped": "害", "empower": "害", "fortify": "害", } const EFFECT_SEMANTIC := { "attack_up": "主角攻击加成(hero_offense 池加算)", @@ -38,8 +46,8 @@ const EFFECT_SEMANTIC := { "fortify": "敌方防御 −50(enemy_defense 池——硬化技施放,主角攻击 ×0.5;持续 1 个有效结算)", } const SOURCE_NAMES := {"safe_cell": "安全格", "flag_hit": "标中旗", "mine_hit": "踩雷", "clear_reward": "清图奖励"} -# 未知图标键的占位纹样(HUD框架:占位也要可见,禁静默空图标) -const ICON_PLACEHOLDER := ["?", UiKit.PANEL_BORDER, "?"] +# 未知图标键的占位(HUD框架:占位也要可见,禁静默空图标——debug 断言先行,release 回退槽内 ? 文本) +const ICON_PLACEHOLDER := "?" var _tip: PanelContainer var _tip_label: Label @@ -71,15 +79,13 @@ func set_effects(effects: Array) -> void: _slots[i].visible = true -# battle_over 反馈(增益状态×4④):逐槽化光收回 + 微文案(下次 set_effects 恢复常态) +# battle_over 反馈(增益状态×4④):逐槽化光收回 + 微灰化(下次 set_effects 恢复常态; +# 2026-09-24 状态图标接入批:原字形 "✕ " 前缀随占位字形退役,灰化即清场反馈) func set_cleared_feedback() -> void: _cleared_feedback = true for slot in _slots: slot.visible = true slot.modulate = Color(0.7, 0.7, 0.7, 0.5) - var col := slot.get_child(0) as VBoxContainer - var icon := col.get_child(0) as Label - icon.text = "✕ " + icon.text func _sync_slots(n: int) -> void: @@ -105,8 +111,13 @@ func _make_slot() -> PanelContainer: var col := VBoxContainer.new() col.alignment = BoxContainer.ALIGNMENT_CENTER slot.add_child(col) - var icon := UiKit.label("", 16, true) - icon.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + # 定稿纹理图标(32px 源 → 24px 槽内呈现;线性过滤匹配抗锯齿矢量风源图) + var icon := TextureRect.new() + icon.custom_minimum_size = Vector2(24, 24) + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR + icon.mouse_filter = Control.MOUSE_FILTER_IGNORE col.add_child(icon) var meta := UiKit.label("", 11, false) meta.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER @@ -118,17 +129,19 @@ func _fill_slot(slot: PanelContainer, eff: Dictionary) -> void: slot.modulate = Color(1, 1, 1) slot.set_meta("eff", eff) var col := slot.get_child(0) as VBoxContainer - var icon := col.get_child(0) as Label + var icon := col.get_child(0) as TextureRect var meta := col.get_child(1) as Label - var icon_cfg: Array = _icon_cfg(String(eff.effect_id)) - icon.text = String(icon_cfg[0]) - icon.add_theme_color_override("font_color", icon_cfg[1]) - # 层数角标 + 剩余结算次数点(● 未耗余量) + # 层数角标 + 剩余结算次数点(● 未耗余量)——叠层数字呈现逻辑保持不动 var dots := "" var left: int = int(eff.get("remaining_settlements", 0)) for i in maxi(left, 1): dots += "●" if i < left else "○" meta.text = "×%d %s" % [int(eff.get("layers", 1)), dots] + var tex := _icon_tex(String(eff.effect_id)) + icon.visible = tex != null + icon.texture = tex + if tex == null: + meta.text = ICON_PLACEHOLDER + " " + meta.text # release 回退占位(debug 已断言) func _on_slot_hover(slot: Control) -> void: @@ -136,11 +149,11 @@ func _on_slot_hover(slot: Control) -> void: if not slot.has_meta("eff"): return var eff: Dictionary = slot.get_meta("eff") - var icon_cfg: Array = _icon_cfg(String(eff.effect_id)) + var eid := String(eff.effect_id) var src: String = String(SOURCE_NAMES.get(String(eff.get("source_class", "")), String(eff.get("source_class", "")))) - var semantic: String = String(EFFECT_SEMANTIC.get(String(eff.effect_id), String(eff.get("display_name", "")))) + var semantic: String = String(EFFECT_SEMANTIC.get(eid, String(eff.get("display_name", "")))) _tip_label.text = "%s〔%s〕\n%s\n剩余结算 %d 次\n来源:%s(第 %d 轮)" % [ - String(eff.get("display_name", "")), String(icon_cfg[2]), semantic, + String(eff.get("display_name", "")), String(EFFECT_POLARITY.get(eid, ICON_PLACEHOLDER)), semantic, int(eff.get("remaining_settlements", 0)), src, int(eff.get("round_index", 0))] if not _tip.is_inside_tree(): add_child(_tip) @@ -159,10 +172,10 @@ func _on_slot_hover(slot: Control) -> void: _tip.visible = true -# 图标键解析(HUD框架:查不到的键按错误处理——立即断言 + ? 占位纹样 + 诊断,禁止静默显示空图标) -func _icon_cfg(effect_id: String) -> Array: +# 图标键解析(HUD框架:查不到的键按错误处理——立即断言 + 占位 + 诊断,禁止静默显示空图标) +func _icon_tex(effect_id: String) -> Texture2D: if EFFECT_ICON.has(effect_id): return EFFECT_ICON[effect_id] push_error("[状态槽] 未知图标键(映射表外键——立即断言): %s" % effect_id) assert(false, "未知图标键: " + effect_id) - return ICON_PLACEHOLDER + return null diff --git a/game/smoke/ui_probe.gd b/game/smoke/ui_probe.gd index c1e76fb0..13d15b76 100644 --- a/game/smoke/ui_probe.gd +++ b/game/smoke/ui_probe.gd @@ -137,20 +137,21 @@ func _ready() -> void: elif slots_row._slots.size() != 1 or slots_row._slots[0] == slots_row._tip: problems.append("BUG-0001 回归:tooltip 入树后槽引用错位") else: - var icon_lab: Label = slots_row._slots[0].get_child(0).get_child(0) - if icon_lab.text != "▲": - problems.append("BUG-0001 回归:悬停后重刷槽未填充(icon=%s)" % icon_lab.text) + var icon_rect: TextureRect = slots_row._slots[0].get_child(0).get_child(0) + if icon_rect.texture == null: + problems.append("BUG-0001 回归:悬停后重刷槽未填充(texture=null)") slots_row.set_effects([probe_eff[0], probe_eff[0], probe_eff[0], probe_eff[0], probe_eff[0], probe_eff[0]]) if slots_row._slots.size() != 4: problems.append("BUG-0001 回归:超上限效果未截断至 4 槽(%d)" % slots_row._slots.size()) slots_row.set_effects([]) if not slots_row._slots.is_empty(): problems.append("BUG-0001 回归:空态未清槽(%d)" % slots_row._slots.size()) - # ---- 图标键回归(HUD框架 图标键契约:状态表七行全映射、格面资源 cell_type 九型全映射〔gp 键 = 九型批 2026-09-20〕; - # 查不到的键 fail-fast + ? 占位——此处钉死映射不残缺,未知键断言由 _icon_cfg/_badge_glyph 承担)---- - if not StatusSlotRow.EFFECT_ICON.has("enrage") or not StatusSlotRow.EFFECT_ICON.has("off_guard") \ - or not StatusSlotRow.EFFECT_ICON.has("sapped"): - problems.append("状态图标表缺暴走/失衡/乏力键(states 表七行须全映射)") + # ---- 图标键回归(HUD框架 图标键契约:状态表九行全映射〔定稿 PNG 纹理,2026-09-24 状态图标接入批〕、 + # 格面资源 cell_type 九型全映射〔gp 键 = 九型批 2026-09-20〕;查不到的键 fail-fast + ? 占位 + # ——此处钉死映射不残缺,未知键断言由 _icon_tex/_badge_glyph 承担)---- + for eid in ["attack_up", "ac_up", "alert", "weaken", "enrage", "off_guard", "sapped", "empower", "fortify"]: + if not StatusSlotRow.EFFECT_ICON.has(eid) or StatusSlotRow.EFFECT_ICON[eid] == null: + problems.append("状态图标九键纹理映射缺失(%s)" % eid) for cat in ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action", "gp"]: if not BoardView.REWARD_BADGE.has(cat): problems.append("奖励图标表缺键(%s)" % cat) @@ -162,12 +163,12 @@ func _ready() -> void: if slots_row._slots.size() != 2: problems.append("图标键回归:enrage/off_guard 未建槽(%d)" % slots_row._slots.size()) else: - var ic_enrage: Label = slots_row._slots[0].get_child(0).get_child(0) - var ic_off: Label = slots_row._slots[1].get_child(0).get_child(0) - if ic_enrage.text == "?" or ic_enrage.text == "": - problems.append("暴走图标键未映射(icon=%s)" % ic_enrage.text) - if ic_off.text == "?" or ic_off.text == "": - problems.append("失衡图标键未映射(icon=%s)" % ic_off.text) + var ic_enrage: TextureRect = slots_row._slots[0].get_child(0).get_child(0) + var ic_off: TextureRect = slots_row._slots[1].get_child(0).get_child(0) + if ic_enrage.texture == null: + problems.append("暴走图标键未映射(texture=null)") + if ic_off.texture == null: + problems.append("失衡图标键未映射(texture=null)") slots_row.set_effects([]) # ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)---- # 2026-09-13 单场用户入口已删——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线;