feat(presentation): 站桩本体 Node2D 化(Phase B T3)——StandeeBody 世界精灵新件+StandeeView 退役删除+行为面 17 方法契约保持+伴随件壳层化(enemy_box 语义改写)+world_to_shell 单点同步+ui_probe 断言等价改写+台账 171
This commit is contained in:
1 parent
87b23a810b
commit
afeb6cacd2
7 files changed
+504
-380
No files matched your search
@@ -4,29 +4,39 @@
|
||||
# 底部结算明细带(布局决策 D1 常驻双舞台分屏))。
|
||||
# Date: 2026-09-12(2026-09-20 站桩改造批:HeroCardView/EnemyCardView 退役,StandeeView 换装;
|
||||
# setter 面契约保持——set_enemies/clear_enemies/on_step_row_fx/gray_all_enemies 签名不变)
|
||||
# 战斗舞台 Node2D 迁移批 Phase B 2026-09-26(循环 1 T2,ADR-0006 / board-node2d-migration-plan §3):
|
||||
# 根壳 PanelContainer → Control(类名/尺寸 252px/树内位置不变),内部拆两半区——
|
||||
# 「Node2D 舞台半区 = SubViewportContainer + SubViewport + StageWorld(站桩本体 T3 迁入,本期空置)」
|
||||
# +「Control 文本半区 = round_label / stage_center / log_view 三件原样保留(C1 永久 Control)」。
|
||||
# SubViewport 逻辑分辨率 = 壳层像素 1:1(stretch,禁任何缩放系数——C2/C5);字体显式注入挂点
|
||||
# set_world_font(C6——SubViewport 不自动继承主场景主题,Phase A BoardView 同款)。
|
||||
# 战斗舞台 Node2D 迁移批 Phase B 2026-09-26(循环 1 T2 壳 + T3 站桩迁入;ADR-0006 /
|
||||
# board-node2d-migration-plan §3):根壳 PanelContainer → Control(类名/尺寸 252px/树内位置
|
||||
# 不变),内部两半区——「Node2D 舞台半区 = SubViewportContainer + SubViewport + StageWorld
|
||||
# (站桩本体 StandeeBody 世界精灵)」+「Control 文本半区 = round_label / stage_center /
|
||||
# log_view 三件原样保留(C1 永久 Control)」。SubViewport 逻辑分辨率 = 壳层像素 1:1(stretch,
|
||||
# 禁任何缩放系数——C2/C5);字体显式注入挂点 set_world_font(C6,Phase A BoardView 同款)。
|
||||
# T3 批内代拍板默认(待追认):①enemy_box 语义改写「站桩壳层伴随件容器」(HBoxContainer →
|
||||
# Control,子件 = 各站桩的 StatusSlotRow 伴随件,手动定位随世界同步——探针 :97 计数面不变);
|
||||
# ②hero_standee/enemy 站桩 = StandeeBody(Node2D 世界本体,行为面契约逐项保持 C3),其
|
||||
# slot_row 成员 = 壳层伴随件引用;③定位同步 = 世界→壳层换算单点 world_to_shell,重建/重排
|
||||
# 挂点调用(零每帧成本,C4)。旧 StandeeView(Control)随迁退役删除。
|
||||
# 高内聚边界:容器+组合视图——站桩槽位管理、轮次指示、结算行演出分发改由 setter 面承接;
|
||||
# 结算流水格式化归 BattleLogView,核心状态→文案装配归 hud_presenter.gd。
|
||||
class_name BattleStageView
|
||||
extends Control
|
||||
|
||||
const _STAGE_WORLD := preload("res://presentation/views/stage_world.gd")
|
||||
const _STANDEE_BODY := preload("res://presentation/views/standee_body.gd")
|
||||
const _CONTENT_MARGINS := Rect2(6, 4, 6, 4) # 原 PanelContainer stylebox 内容边距(子面几何零漂移)
|
||||
const _STANDEE_W := 150.0 # 站桩盒宽(旧 StandeeView custom_minimum_size)
|
||||
const _STANDEE_GAP := 8.0 # 敌站桩间距(旧 enemy_box HBox separation)
|
||||
|
||||
var hero_standee: StandeeView
|
||||
var hero_standee: Node2D # StandeeBody——主角站桩本体(世界内;slot_row = 壳层伴随件引用)
|
||||
var round_label: Label
|
||||
var stage_center: Label
|
||||
var enemy_box: HBoxContainer
|
||||
var enemy_box: Control # 站桩壳层伴随件容器(T3 语义改写:HBoxContainer → Control)
|
||||
var log_view: BattleLogView
|
||||
var _cards: Array = []
|
||||
var _cards: Array = [] # StandeeBody(世界本体,敌)
|
||||
var _row: HBoxContainer # 文本半区行(stage_center 载体 + 世界布局基准)
|
||||
var _companion_layer: Control # 壳层伴随件层(主角 slot_row 宿主;敌伴随件宿主 = enemy_box)
|
||||
var _svp_container: SubViewportContainer
|
||||
var _viewport: SubViewport
|
||||
var world: Node2D # StageWorld(preload 脚本实例——Node2D 世界根/字体挂点;T3 站桩迁入)
|
||||
var world: Node2D # StageWorld(preload 脚本实例——Node2D 世界根/字体挂点)
|
||||
|
||||
|
||||
func _init(db_ref) -> void:
|
||||
@@ -42,7 +52,7 @@ func _init(db_ref) -> void:
|
||||
# ── Node2D 舞台半区(ADR-0006 Phase B 同构;stretch 1:1 无缩放系数——C2/C5)──
|
||||
_svp_container = SubViewportContainer.new()
|
||||
_svp_container.stretch = true # 容器尺寸 = 视口逻辑分辨率,恒 1:1
|
||||
_svp_container.mouse_filter = Control.MOUSE_FILTER_IGNORE # B 区零输入(tooltip 归壳层伴随件)
|
||||
_svp_container.mouse_filter = Control.MOUSE_FILTER_IGNORE # 世界零输入(tooltip 归壳层伴随件)
|
||||
_svp_container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_svp_container.offset_left = _CONTENT_MARGINS.position.x
|
||||
_svp_container.offset_top = _CONTENT_MARGINS.position.y
|
||||
@@ -55,8 +65,7 @@ func _init(db_ref) -> void:
|
||||
world = _STAGE_WORLD.new()
|
||||
_viewport.add_child(world)
|
||||
|
||||
# ── Control 文本半区(round_label / stage_center / log_view 原样保留——C1 永久 Control;
|
||||
# hero_standee/enemy_box 仍为 Control 站桩,T3 迁世界后仅伴随件留壳层)──
|
||||
# ── Control 文本半区(round_label / stage_center / log_view 原样保留——C1 永久 Control)──
|
||||
var vb := VBoxContainer.new()
|
||||
vb.add_theme_constant_override("separation", 4)
|
||||
vb.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -70,17 +79,15 @@ func _init(db_ref) -> void:
|
||||
round_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vb.add_child(round_label)
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 10)
|
||||
row.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
vb.add_child(row)
|
||||
|
||||
hero_standee = StandeeView.new("hero")
|
||||
hero_standee.set_portrait_texture(UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT), true) # B 区立绘 48×48 朝右(2026-09-26 用户点名 hero_idle_01 换装+水平向右;素材缺位 null 回退字形「人」)
|
||||
row.add_child(hero_standee)
|
||||
_row = HBoxContainer.new()
|
||||
_row.add_theme_constant_override("separation", 10)
|
||||
_row.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
vb.add_child(_row)
|
||||
_row.resized.connect(_on_row_resized) # 明细带展开/收起改行高——世界布局随动
|
||||
|
||||
var center := CenterContainer.new()
|
||||
center.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
center.mouse_filter = Control.MOUSE_FILTER_IGNORE # 行内只剩文本——鼠标全放行给壳层伴随件
|
||||
var center_col := VBoxContainer.new()
|
||||
center_col.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
center.add_child(center_col)
|
||||
@@ -88,49 +95,133 @@ func _init(db_ref) -> void:
|
||||
stage_center.add_theme_color_override("font_color", UiKit.STONE_FAINT)
|
||||
stage_center.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
center_col.add_child(stage_center)
|
||||
row.add_child(center)
|
||||
_row.add_child(center)
|
||||
|
||||
enemy_box = HBoxContainer.new()
|
||||
enemy_box.add_theme_constant_override("separation", 8)
|
||||
enemy_box.alignment = BoxContainer.ALIGNMENT_END
|
||||
row.add_child(enemy_box)
|
||||
enemy_box = Control.new() # 站桩壳层伴随件容器(T3 语义改写)
|
||||
enemy_box.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_row.add_child(enemy_box)
|
||||
|
||||
log_view = BattleLogView.new(db_ref)
|
||||
vb.add_child(log_view)
|
||||
|
||||
# ── 壳层伴随件层(最顶——世界站桩之上的 Control 触达面)──
|
||||
_companion_layer = Control.new()
|
||||
_companion_layer.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_companion_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_companion_layer)
|
||||
|
||||
# SubViewport 主题/字体显式注入挂点(C6——Phase A BoardView.set_world_font 同款;
|
||||
# T3 起随站桩本体下发 world_font)
|
||||
func set_world_font(f: Font) -> void:
|
||||
world.world_font = f
|
||||
# 主角站桩本体(世界)+ 状态行伴随件(壳层)
|
||||
hero_standee = _STANDEE_BODY.new("hero")
|
||||
hero_standee.set_portrait_texture(UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT), true) # B 区立绘 48×48 朝右(2026-09-26 用户点名 hero_idle_01 换装+水平向右;素材缺位 null 回退字形「人」)
|
||||
world.add_child(hero_standee)
|
||||
hero_standee.slot_row = _make_slot_companion(_companion_layer)
|
||||
|
||||
|
||||
# 两套坐标空间换算单点(C4——台账 160 纪律延伸):SubViewport 世界内 = 局部排布;
|
||||
# 壳层/fx 层(结算演出 SettlementFxView 在 SubViewport 之外)= 全局含容器偏移。
|
||||
# 站桩重建/容器重排挂点调用(站桩无逐帧位移,零每帧成本)。
|
||||
func _make_slot_companion(parent: Control) -> StatusSlotRow:
|
||||
# 状态效果唯一展示面(2026-09-26 C 区精简批迁入站桩)——compact 档伴随件,tooltip 全明细
|
||||
var comp := StatusSlotRow.new()
|
||||
comp.compact = true
|
||||
comp.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
comp.mouse_filter = Control.MOUSE_FILTER_IGNORE # 行容器放行鼠标事件给槽位(悬停 tooltip)
|
||||
comp.visible = false # 空态 = 图标行隐藏不占位(增益状态 EC12)
|
||||
parent.add_child(comp)
|
||||
return comp
|
||||
|
||||
|
||||
# ── 布局同步(世界→壳层单点——C4;站桩无逐帧位移,重建/重排挂点调用,零每帧成本)──
|
||||
|
||||
func world_to_shell(p: Vector2) -> Vector2:
|
||||
return _svp_container.global_position + p
|
||||
|
||||
|
||||
func _on_row_resized() -> void:
|
||||
_sync_layout.call_deferred()
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_RESIZED:
|
||||
# stretch=true 时视口尺寸由容器接管;树外(冒烟直建)才需手设保 1:1(Phase A 同款)
|
||||
if not _svp_container.is_inside_tree():
|
||||
_viewport.size = _svp_container.size
|
||||
_sync_layout.call_deferred()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_sync_layout.call_deferred() # 首轮布局落定后对位(row 几何此时才有效)
|
||||
|
||||
|
||||
func _sync_layout() -> void:
|
||||
if not is_inside_tree() or hero_standee == null:
|
||||
return
|
||||
var origin: Vector2 = _svp_container.global_position
|
||||
var row_rect: Rect2 = _row.get_global_rect()
|
||||
var local := row_rect.position - origin # 行原点 → 世界坐标(1:1,C2)
|
||||
var box_h := row_rect.size.y # 站桩盒高 = 行高(旧 HBox FILL 拉伸等价)
|
||||
hero_standee.box = Vector2(_STANDEE_W, box_h)
|
||||
hero_standee.position = local
|
||||
# 敌站桩右对齐(旧 HBox ALIGNMENT_END + separation 8 等价)
|
||||
var n := _cards.size()
|
||||
var total := n * _STANDEE_W + maxi(0, n - 1) * _STANDEE_GAP
|
||||
var x := row_rect.size.x - total
|
||||
for i in n:
|
||||
var body: Node2D = _cards[i]
|
||||
if not is_instance_valid(body):
|
||||
continue
|
||||
body.box = Vector2(_STANDEE_W, box_h)
|
||||
body.position = local + Vector2(x + float(i) * (_STANDEE_W + _STANDEE_GAP), 0.0)
|
||||
_sync_companions()
|
||||
|
||||
|
||||
func _sync_companions() -> void:
|
||||
# 状态行伴随件 = 世界站桩顶部(血条正下方偏移 (8,14)——旧 top VBox 几何)的壳层投影
|
||||
var layer_pos: Vector2 = _companion_layer.global_position
|
||||
if hero_standee != null and hero_standee.slot_row != null:
|
||||
var comp: StatusSlotRow = hero_standee.slot_row
|
||||
comp.reset_size()
|
||||
comp.position = world_to_shell(hero_standee.position + Vector2(8.0 + (_STANDEE_W - 16.0 - comp.size.x) * 0.5, 14.0)) - layer_pos
|
||||
for i in _cards.size():
|
||||
var body: Node2D = _cards[i]
|
||||
if not is_instance_valid(body) or body.slot_row == null:
|
||||
continue
|
||||
var comp2: StatusSlotRow = body.slot_row
|
||||
comp2.reset_size()
|
||||
comp2.position = world_to_shell(body.position + Vector2(8.0 + (_STANDEE_W - 16.0 - comp2.size.x) * 0.5, 14.0)) - layer_pos
|
||||
|
||||
|
||||
# SubViewport 主题/字体显式注入挂点(C6——Phase A BoardView.set_world_font 同款;站桩本体随动)
|
||||
func set_world_font(f: Font) -> void:
|
||||
world.world_font = f
|
||||
hero_standee.font = f
|
||||
for c in _cards:
|
||||
if is_instance_valid(c):
|
||||
c.font = f
|
||||
c.queue_redraw()
|
||||
|
||||
|
||||
# 敌方站桩本体出口(探针等价改写用——T5;旧 enemy_box 子件 = 站桩 Control 改伴随件后的本体通路)
|
||||
func enemy_body_at(i: int) -> Node2D:
|
||||
if i < 0 or i >= _cards.size():
|
||||
return null
|
||||
return _cards[i]
|
||||
|
||||
|
||||
# 敌方站桩投影更新:数量不变 = 就地更新(死亡灰化/受击闪烁等站桩上动画不被逐行刷新重置——禁瞬消纪律);
|
||||
# 数量变化 = 新战斗域,重建站桩槽(轮内不增删,无回流)
|
||||
func set_enemies(cards_data: Array) -> void:
|
||||
if _cards.size() != cards_data.size():
|
||||
for body in _cards:
|
||||
if is_instance_valid(body):
|
||||
body.queue_free()
|
||||
for c in enemy_box.get_children():
|
||||
c.queue_free()
|
||||
_cards.clear()
|
||||
for d in cards_data:
|
||||
var standee := StandeeView.new("enemy")
|
||||
standee.set_data(d)
|
||||
enemy_box.add_child(standee)
|
||||
_cards.append(standee)
|
||||
var body: Node2D = _STANDEE_BODY.new("enemy")
|
||||
world.add_child(body)
|
||||
body.slot_row = _make_slot_companion(enemy_box)
|
||||
body.set_data(d)
|
||||
_cards.append(body)
|
||||
_sync_layout.call_deferred()
|
||||
return
|
||||
for i in cards_data.size():
|
||||
if is_instance_valid(_cards[i]):
|
||||
@@ -138,6 +229,9 @@ func set_enemies(cards_data: Array) -> void:
|
||||
|
||||
|
||||
func clear_enemies() -> void:
|
||||
for body in _cards:
|
||||
if is_instance_valid(body):
|
||||
body.queue_free()
|
||||
for c in enemy_box.get_children():
|
||||
c.queue_free()
|
||||
_cards.clear()
|
||||
@@ -155,7 +249,7 @@ func on_step_row_fx(row: Dictionary) -> void:
|
||||
var target := String(row.get("target", ""))
|
||||
if target == "hero" or target == "":
|
||||
return
|
||||
var card: StandeeView = _card_by_name(target)
|
||||
var card: Node2D = _card_by_name(target)
|
||||
if card == null:
|
||||
return
|
||||
card.flash_hit()
|
||||
@@ -163,25 +257,25 @@ func on_step_row_fx(row: Dictionary) -> void:
|
||||
card.mark_dead()
|
||||
|
||||
|
||||
func _card_by_name(display_name: String) -> StandeeView:
|
||||
func _card_by_name(display_name: String) -> Node2D:
|
||||
for c in _cards:
|
||||
if not is_instance_valid(c):
|
||||
continue
|
||||
if String(c._name_label.text).contains(display_name): # 名字行图标化批 2026-09-26:名字 Label 独立节点(_name_line 已改 HBox 容器)
|
||||
if c.display_name().contains(display_name):
|
||||
return c
|
||||
return null
|
||||
|
||||
|
||||
# 跨区弹道目标坐标(结算演出链批——组合根传给 SettlementFxView 的全局锚点)
|
||||
# 跨区弹道目标坐标(结算演出链批——组合根传给 SettlementFxView 的全局锚点;壳层口径 C4)
|
||||
func enemy_standee_center(display_name: String):
|
||||
var card := _card_by_name(display_name)
|
||||
if card == null or not is_instance_valid(card):
|
||||
return null
|
||||
return card.get_global_rect().get_center()
|
||||
return world_to_shell(card.position + card.box * 0.5)
|
||||
|
||||
|
||||
func hero_standee_center() -> Vector2:
|
||||
return hero_standee.get_global_rect().get_center()
|
||||
return world_to_shell(hero_standee.position + hero_standee.box * 0.5)
|
||||
|
||||
|
||||
# battle_over:全场敌方站桩灰化兜底(短路击杀等未走 killed 行的边角)
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 实现 GDD:design/ux/battle.md(B 区全站桩形态——2026-09-20 用户看对照样张拍板,台账 99:
|
||||
# 主角与敌人均为无框立绘+底座,英雄卡/敌人卡卡片式整体退役禁再提)。
|
||||
# 战斗舞台 Node2D 迁移批 Phase B 2026-09-26(循环 1 T3):站桩本体自 Control(旧 StandeeView,
|
||||
# 退役删除)平移为 Node2D 世界精灵(ADR-0006:B 区战斗舞台归 Node2D)。行为面契约逐项保持:
|
||||
# set_data/set_hero/_set_hp/set_idle/set_effects/set_effects_cleared_feedback/set_order/set_pending/
|
||||
# _play_intent_motion/set_portrait_texture(tex,flip)/mark_dead/is_dead/flash_hit/_revive/
|
||||
# _update_name_line/_refresh_portrait_glyph(调用点 hud_presenter/main/探针零改造目标——C3)。
|
||||
# 等价平移基线 = 旧 Control 占位表现(C9):立绘贴图(flip/NEAREST/整数 2× 档)+ 底座 + 血条
|
||||
# (直角递减 tween)+ 顺位徽标 + 意图徽标(弹入+立绘前倾一次性动画)+ 名字行(纯名+护甲类型
|
||||
# 图标——2026-09-26 终裁 AC 不展示)+ 受击闪白 + 死亡灰化摇摆塌陷(禁瞬消)+ 复活 + 字形占位
|
||||
# 回退(人/▂/▆/█/✕/敌)。批内代拍板默认(待追认):①状态图标行(slot_row)与悬停 tooltip 保留
|
||||
# Control 载体挂壳层(tooltip 机制零返工)——本体的 slot_row 成员 = 壳层伴随件引用;
|
||||
# ②状态行隐藏时序 = 随 mark_dead 同拍隐藏;③清空反馈微文案挂伴随件父层(壳层)。
|
||||
# 文字全部经 draw_string + 世界字体(world.world_font 或 ThemeDB.fallback_font——C6 挂点链)。
|
||||
# 坐标空间:本节点 position/box = SubViewport 世界内局部排布;壳层换算归
|
||||
# BattleStageView.world_to_shell 单点(C4)。
|
||||
# 无 class_name(类名缓存面纪律——BattleStageView 经 preload 使用)。
|
||||
extends Node2D
|
||||
|
||||
const CHARGE_SKILL_IDS := ["heavy"]
|
||||
# 护甲类型图标占位——形状+色相双通道(art-bible):轻甲=◇ 灰石中性 / 魔法护甲=◆ 小人蓝 /
|
||||
# 重甲=▣ 深金(避开 Boss 专属紫赤与妨害紫)。(自旧 StandeeView 平移)
|
||||
const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"}
|
||||
|
||||
var box := Vector2(150, 176) # 站桩盒尺寸(旧 Control custom_minimum_size;实际高随壳层 row 布局注入)
|
||||
var font: Font = null # 世界字体槽位(null = ThemeDB.fallback_font——world.world_font 注入)
|
||||
var slot_row: StatusSlotRow # 状态效果唯一展示面 = 壳层伴随件(挂 BattleStageView 壳层,本体进世界)
|
||||
|
||||
var _kind := "enemy"
|
||||
var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位)
|
||||
var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据)
|
||||
var _portrait_flip := false
|
||||
var _order := 1
|
||||
var _intent_glyph := ""
|
||||
var _intent_col := Color.WHITE
|
||||
var _intent_visible := false
|
||||
var _intent_scale := 1.0: # 意图弹入动画参数(旧 tween scale 等价)
|
||||
set(v):
|
||||
_intent_scale = v
|
||||
queue_redraw()
|
||||
var _glyph_rot := 0.0: # 立绘前倾角(旧只转字形 Label——贴图态无此动画,等价平移)
|
||||
set(v):
|
||||
_glyph_rot = v
|
||||
queue_redraw()
|
||||
var _display_name := ""
|
||||
var _enemy_id := ""
|
||||
var _armor := "light"
|
||||
var _dead := false
|
||||
var _hp_max := 1.0
|
||||
var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积)
|
||||
set(v):
|
||||
hp_value = clampf(v, 0.0, 1.0)
|
||||
queue_redraw()
|
||||
var _hp_tween: Tween
|
||||
var _hp_flash_tw: Tween
|
||||
|
||||
|
||||
func _init(kind: String = "enemy") -> void:
|
||||
_kind = kind
|
||||
|
||||
|
||||
func _efont() -> Font:
|
||||
return font if font != null else ThemeDB.fallback_font
|
||||
|
||||
|
||||
func display_name() -> String:
|
||||
return _display_name
|
||||
|
||||
|
||||
# 血条存在性探针出口(T5 断言等价改写——旧 _hp_bar ProgressBar 退役,世界直角血条常在)
|
||||
var has_world_hp_bar := true
|
||||
|
||||
|
||||
# 意图徽标探针出口(T5——旧 _intent_badge.visible/text 改判)
|
||||
func intent_probe() -> Dictionary:
|
||||
return {"visible": _intent_visible, "text": _intent_glyph}
|
||||
|
||||
|
||||
# 名字行探针出口(T5——旧 _name_label/_armor_icon.text 改判)
|
||||
func name_line_probe() -> Dictionary:
|
||||
return {"name": _display_name, "armor_icon": String(ARMOR_ICONS.get(_armor, "◇"))}
|
||||
|
||||
|
||||
# ---- setter 面(投影契约与旧 StandeeView 一致——presenter/组合根零改排) ----
|
||||
|
||||
# data = 战斗结算 presentation_snapshot enemies[] 投影:{order, display_name, hp, max_hp, enemy_id,
|
||||
# pending_skill/pending_type/pending_name, effects[](敌方 side 增益——各立绘同值呈现)}
|
||||
func set_data(d: Dictionary) -> void:
|
||||
_display_name = String(d.get("display_name", "敌人"))
|
||||
_enemy_id = String(d.get("enemy_id", ""))
|
||||
_armor = String(d.get("armor_type", "light"))
|
||||
_hp_max = float(maxi(1, int(d.get("max_hp", 1))))
|
||||
_set_hp(int(d.get("hp", 0)))
|
||||
set_order(int(d.get("order", 1)))
|
||||
set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", "")))
|
||||
set_effects(d.get("effects", []))
|
||||
_refresh_portrait_glyph()
|
||||
if _dead and int(d.get("hp", 0)) > 0:
|
||||
_revive()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func set_hero(hp: int, max_hp: int) -> void:
|
||||
_hp_max = float(maxi(1, max_hp))
|
||||
_set_hp(hp)
|
||||
|
||||
|
||||
# 血条短 tween 递减(直角递减占位——密度/缓动终稿归战斗表现批);逐行刷新时重启 tween 不累积
|
||||
func _set_hp(hp: int) -> void:
|
||||
var target := float(clampi(hp, 0, int(_hp_max))) / _hp_max
|
||||
if _hp_tween != null and _hp_tween.is_valid():
|
||||
_hp_tween.kill()
|
||||
_hp_tween = create_tween()
|
||||
_hp_tween.tween_property(self, "hp_value", target, UiKit.motion(0.15))
|
||||
|
||||
|
||||
func set_idle() -> void:
|
||||
_set_hp(0)
|
||||
set_effects([])
|
||||
|
||||
|
||||
# 主角/敌方共用——状态效果唯一展示面(壳层伴随件转发;空态 = 图标行隐藏不占位)
|
||||
func set_effects(effects: Array) -> void:
|
||||
if slot_row == null:
|
||||
return
|
||||
slot_row.set_effects(effects)
|
||||
slot_row.visible = not effects.is_empty()
|
||||
|
||||
|
||||
# battle_over:状态图标行全清可见反馈(微文案挂伴随件父层 = 壳层)
|
||||
func set_effects_cleared_feedback() -> void:
|
||||
if slot_row == null:
|
||||
return
|
||||
slot_row.visible = true
|
||||
slot_row.set_cleared_feedback()
|
||||
var note := UiKit.label("状态随战斗结束消散", 11, false)
|
||||
note.add_theme_color_override("font_color", UiKit.STONE_FAINT)
|
||||
note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
note.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var parent := slot_row.get_parent()
|
||||
if parent != null:
|
||||
parent.add_child(note)
|
||||
note.position = slot_row.position + Vector2((slot_row.size.x - 134.0) * 0.5, slot_row.size.y)
|
||||
var tw := create_tween()
|
||||
tw.tween_interval(1.2)
|
||||
tw.tween_property(note, "modulate:a", 0.0, UiKit.motion(0.8))
|
||||
tw.tween_callback(note.queue_free)
|
||||
|
||||
|
||||
func set_order(i: int) -> void:
|
||||
# 顺位徽标 = 行主序生成序(固定顺位——敌人系统 顺位标记义务)
|
||||
_order = i
|
||||
queue_redraw()
|
||||
|
||||
|
||||
# 摇定动作渲染(三档可见性):pending_type = 战斗结算 快照投影(basic/damage/buff/debuff;
|
||||
# "" = 未摇定/死亡残位)。不显示技能名——类型级图标 + 一次性动作动画。
|
||||
func set_pending(ptype: String, pid: String) -> void:
|
||||
if _dead:
|
||||
_intent_visible = false
|
||||
queue_redraw()
|
||||
return
|
||||
if ptype == "" or ptype == "basic":
|
||||
_intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
|
||||
queue_redraw()
|
||||
return
|
||||
var glyph := "⚔" # 攻击系(damage)
|
||||
var col: Color = UiKit.FLAG_RED
|
||||
if ptype == "buff":
|
||||
glyph = "✦" # 增益系
|
||||
col = UiKit.HERO_BLUE
|
||||
elif ptype == "debuff":
|
||||
glyph = "☠" # 妨害系(新效应词汇批 2026-09-19)
|
||||
col = UiKit.DEBUFF_PURPLE
|
||||
if pid in CHARGE_SKILL_IDS:
|
||||
col = UiKit.GOLD_DEEP # 重击类蓄力档(2026-09-18 用户拍板独立蓄力提示)
|
||||
_intent_glyph = glyph
|
||||
_intent_col = col
|
||||
_intent_visible = true
|
||||
_play_intent_motion()
|
||||
|
||||
|
||||
# 三档动作动画占位:摇定时一次性短动画(意图图标弹入 + 立绘前倾回正),随后静帧保持
|
||||
func _play_intent_motion() -> void:
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10))
|
||||
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16))
|
||||
tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10))
|
||||
tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14))
|
||||
|
||||
|
||||
# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
|
||||
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2×
|
||||
# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。
|
||||
func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
|
||||
_portrait_flip = flip
|
||||
if tex == null:
|
||||
if _portrait_sprite != null:
|
||||
_portrait_sprite.visible = false
|
||||
queue_redraw()
|
||||
return
|
||||
_portrait_tex = tex
|
||||
if _portrait_sprite == null:
|
||||
_portrait_sprite = Sprite2D.new()
|
||||
_portrait_sprite.centered = false
|
||||
_portrait_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊(棋盘小人贴图批 2026-09-25)
|
||||
add_child(_portrait_sprite)
|
||||
_portrait_sprite.texture = tex
|
||||
_portrait_sprite.flip_h = flip
|
||||
_portrait_sprite.visible = true
|
||||
_place_portrait()
|
||||
|
||||
|
||||
func _place_portrait() -> void:
|
||||
# 立绘盒 = (0,44)-(box.x, box.y-36)(旧 FULL_RECT offsets 等价);48 源 ×2 整数档居中
|
||||
if _portrait_sprite == null or _portrait_tex == null:
|
||||
return
|
||||
var box_h := box.y - 44.0 - 36.0
|
||||
var w := _portrait_tex.get_width() * 2.0
|
||||
var h := _portrait_tex.get_height() * 2.0
|
||||
_portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5)
|
||||
_portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - h) * 0.5)
|
||||
|
||||
|
||||
func mark_dead() -> void:
|
||||
if _dead:
|
||||
return
|
||||
_dead = true
|
||||
modulate = Color(0.62, 0.62, 0.62, 0.9)
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
|
||||
tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1))
|
||||
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
|
||||
_refresh_portrait_glyph()
|
||||
_intent_visible = false # 死亡残位意图退位(旧同款)
|
||||
_set_hp(0)
|
||||
if slot_row != null:
|
||||
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏(批内代拍板②待追认)
|
||||
|
||||
|
||||
func is_dead() -> bool:
|
||||
return _dead
|
||||
|
||||
|
||||
# 受击闪白(结算演出占位——§2.2 受击反馈)
|
||||
func flash_hit() -> void:
|
||||
if _hp_flash_tw != null and _hp_flash_tw.is_valid():
|
||||
_hp_flash_tw.kill()
|
||||
_hp_flash_tw = create_tween()
|
||||
_hp_flash_tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
|
||||
_hp_flash_tw.tween_property(self, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
|
||||
|
||||
|
||||
func _revive() -> void:
|
||||
_dead = false
|
||||
modulate = Color(1, 1, 1)
|
||||
rotation_degrees = 0.0
|
||||
_refresh_portrait_glyph()
|
||||
if slot_row != null:
|
||||
slot_row.visible = true
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _refresh_portrait_glyph() -> void:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _portrait_glyph() -> String:
|
||||
if _kind == "hero":
|
||||
return "人"
|
||||
if _dead:
|
||||
return "✕"
|
||||
match _enemy_id:
|
||||
"goblin":
|
||||
return "▂" # 小体量墩形
|
||||
"bugbear":
|
||||
return "▆" # 垂直延伸体量
|
||||
"ogre_chief":
|
||||
return "█" # 最大体量
|
||||
_:
|
||||
return "敌"
|
||||
|
||||
|
||||
func _portrait_color() -> Color:
|
||||
return UiKit.HERO_BLUE if _kind == "hero" else UiKit.RED_DEEP
|
||||
|
||||
|
||||
# 立绘盒矩形(世界局部):(0,44)-(box.x, box.y-36)——贴图与字形共用
|
||||
func _portrait_rect() -> Rect2:
|
||||
return Rect2(0, 44, box.x, box.y - 80.0)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var f := _efont()
|
||||
# 底座(旧 panel_flat(STONE.darkened(0.35), STONE.darkened(0.1), 6, 1) 等价——BOTTOM_WIDE 26/-26, -18/-8)
|
||||
var plat := UiKit.panel_flat(UiKit.STONE.darkened(0.35), UiKit.STONE.darkened(0.1), 6, 1)
|
||||
plat.draw(get_canvas_item(), Rect2(26, box.y - 18.0, box.x - 52.0, 10.0))
|
||||
|
||||
# 血条(直角递减):敌附顺位徽标(GOLD_DEEP ①②③)——旧 top VBox 几何等价(左 8 / 右 -8)
|
||||
var bar_x := 8.0
|
||||
if _kind == "enemy":
|
||||
var order_txt := String("①②③"[clampi(_order - 1, 0, 2)])
|
||||
f.draw_string(get_canvas_item(), Vector2(bar_x, 12), order_txt,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_DEEP)
|
||||
bar_x += 20.0
|
||||
var bar_w := box.x - 8.0 - bar_x
|
||||
draw_rect(Rect2(bar_x, 2, bar_w, 10), Color(0.10, 0.10, 0.11)) # 条底(引擎默认主题深底近似平移)
|
||||
var fill_w := bar_w * hp_value
|
||||
if fill_w > 0.0:
|
||||
draw_rect(Rect2(bar_x, 2, fill_w, 10), Color(0.41, 0.61, 0.91)) # 直角填充(默认主题蓝近似——直角递减)
|
||||
|
||||
# 立绘:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退
|
||||
if _portrait_sprite == null or _portrait_sprite.texture == null or not _portrait_sprite.visible:
|
||||
var prect := _portrait_rect()
|
||||
var glyph := _portrait_glyph()
|
||||
var center := prect.get_center()
|
||||
draw_set_transform(center, deg_to_rad(_glyph_rot), Vector2.ONE)
|
||||
var ts := f.get_string_size(glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42)
|
||||
f.draw_string(get_canvas_item(), center - Vector2(ts.x * 0.5, -ts.y * 0.35), glyph,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 42, _portrait_color())
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
|
||||
# 意图徽标(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画
|
||||
if _intent_visible:
|
||||
var icenter := Vector2(box.x * 0.5, 86.0)
|
||||
draw_set_transform(icenter, 0.0, Vector2(_intent_scale, _intent_scale))
|
||||
var its := f.get_string_size(_intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
|
||||
f.draw_string(get_canvas_item(), icenter - Vector2(its.x * 0.5, -its.y * 0.35), _intent_glyph,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, _intent_col)
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
|
||||
# 底座下 护甲类型图标+名字 常驻行(敌 only——2026-09-26 用户终裁:AC 数值不展示,类型图标化)
|
||||
if _kind == "enemy":
|
||||
var armor_txt := String(ARMOR_ICONS.get(_armor, "◇"))
|
||||
var col: Color = UiKit.STONE # 轻甲 = 灰石(中性轻薄)
|
||||
match _armor:
|
||||
"ward":
|
||||
col = UiKit.HERO_BLUE # 魔法护甲 = 小人蓝
|
||||
"heavy":
|
||||
col = UiKit.GOLD_DEEP # 重甲 = 深金
|
||||
var name_w := f.get_string_size(_display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, 12).x
|
||||
var armor_w := f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 12).x
|
||||
var total_w := armor_w + 3.0 + name_w
|
||||
var baseline := Vector2((box.x - total_w) * 0.5, box.y - 1.0)
|
||||
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 12, col)
|
||||
f.draw_string(get_canvas_item(), baseline + Vector2(armor_w + 3.0, 0), _display_name,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, UiKit.INK_TITLE)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dliuugrkyq2ra
|
||||
@@ -1,324 +0,0 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 实现 GDD:design/ux/battle.md(B 区全站桩形态——2026-09-20 用户看对照样张拍板,台账 99:
|
||||
# 主角与敌人均为无框立绘+底座,英雄卡/敌人卡卡片式整体退役禁再提)。
|
||||
# 形态契约:无框立绘(程序占位字形;set_portrait_texture = 资产挂点,战斗表现样张批落位零代码换装)
|
||||
# + 底座;血条悬浮头顶;状态效果唯一展示面挂血条正下方(2026-09-26 C 区精简批:compact 档
|
||||
# StatusSlotRow 组件整体迁入——定稿 PNG 图标+层数 ×N 角标+悬停 tooltip〔来源+语义+剩余〕,
|
||||
# tooltip 义务自 C 区状态槽并入;空态 = 图标行隐藏)。
|
||||
# 敌方专属:顺位徽标悬浮头顶;意图三档动作动画占位(表现层升级批拍板:不显示技能名——普攻零表示 /
|
||||
# 攻击系 ⚔ / 增益系 ✦ / 妨害系 ☠ / 蓄力 = ⚔ 金档;开轮摇定播一次性短动画后静帧保持,
|
||||
# 挖掘期不循环——art-bible §2.1 能量纪律,终稿动画密度归战斗表现批);底座下 护甲类型图标+名字
|
||||
# 常驻行(2026-09-26 用户终裁:AC 数值不展示、护甲类型图标化——敌人技能池批克制对策可读性
|
||||
# 义务降级承载:类型保留可读、数值移除;HP 信息已足够)。
|
||||
# 死亡呈现(敌人系统 资产下限):灰化 + 摇摆塌陷残位,禁瞬消;受击闪白沿用。
|
||||
# Date: 2026-09-20(站桩改造批——hero_card_view/enemy_card_view 退役删除)
|
||||
class_name StandeeView
|
||||
extends Control
|
||||
|
||||
const CHARGE_SKILL_IDS := ["heavy"]
|
||||
# 护甲类型图标占位——终稿图标资产归美术批(2026-09-26 用户终裁:AC 数值不展示,类型图标化)。
|
||||
# 形状+色相双通道(art-bible):轻甲=◇ 空心菱(轻薄)·灰石中性 / 魔法护甲=◆ 实心晶菱·小人蓝
|
||||
# (避开 Boss 专属紫赤 #8F2F6A 与妨害紫——两者各有专属语义)/ 重甲=▣ 实心方板(厚重板甲)·深金。
|
||||
const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"}
|
||||
|
||||
var _kind := "enemy"
|
||||
var _portrait: Label
|
||||
var _portrait_tex: TextureRect = null # 资产挂点(null = 未落位,字形在位)
|
||||
var _platform: Panel
|
||||
var _hp_bar: ProgressBar
|
||||
var _order_badge: Label = null
|
||||
var _intent_badge: Label = null
|
||||
var _top_row: VBoxContainer
|
||||
var slot_row: StatusSlotRow # 状态效果唯一展示面(2026-09-26 C 区精简批——compact 档组件整体迁入;
|
||||
# 悬停 tooltip 来源+语义+剩余 义务随之从 C 区状态槽并入,主角/敌方共用)
|
||||
var _name_line: HBoxContainer = null
|
||||
var _armor_icon: Label = null
|
||||
var _name_label: Label = null
|
||||
var _display_name := ""
|
||||
var _enemy_id := ""
|
||||
var _armor := "light"
|
||||
var _dead := false
|
||||
var _hp_tween: Tween
|
||||
|
||||
|
||||
func _init(kind: String = "enemy") -> void:
|
||||
_kind = kind
|
||||
custom_minimum_size = Vector2(150, 176)
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
# 头顶悬浮层:血条(敌附顺位徽标)+ 状态小图标行
|
||||
var top := VBoxContainer.new()
|
||||
_top_row = top
|
||||
top.set_anchors_preset(Control.PRESET_TOP_WIDE)
|
||||
top.offset_left = 8.0
|
||||
top.offset_right = -8.0
|
||||
top.add_theme_constant_override("separation", 2)
|
||||
add_child(top)
|
||||
var hp_row := HBoxContainer.new()
|
||||
hp_row.add_theme_constant_override("separation", 4)
|
||||
top.add_child(hp_row)
|
||||
if kind == "enemy":
|
||||
_order_badge = UiKit.label("①", 13, true)
|
||||
_order_badge.add_theme_color_override("font_color", UiKit.GOLD_DEEP)
|
||||
hp_row.add_child(_order_badge)
|
||||
_hp_bar = ProgressBar.new()
|
||||
_hp_bar.custom_minimum_size = Vector2(0, 10)
|
||||
_hp_bar.show_percentage = false
|
||||
_hp_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_hp_bar.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
hp_row.add_child(_hp_bar)
|
||||
slot_row = StatusSlotRow.new()
|
||||
slot_row.compact = true # 站桩迷你档——图标 16px+层数角标,tooltip 全明细
|
||||
slot_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
slot_row.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
slot_row.mouse_filter = Control.MOUSE_FILTER_IGNORE # 行容器放行鼠标事件给槽位(悬停 tooltip)
|
||||
top.add_child(slot_row)
|
||||
|
||||
# 立绘(程序占位字形)
|
||||
_portrait = UiKit.label("人" if kind == "hero" else "敌", 42, true)
|
||||
_portrait.add_theme_color_override("font_color", UiKit.HERO_BLUE if kind == "hero" else UiKit.RED_DEEP)
|
||||
_portrait.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_portrait.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_portrait.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_portrait.offset_top = 44.0
|
||||
_portrait.offset_bottom = -26.0
|
||||
_portrait.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_portrait)
|
||||
|
||||
# 意图指示(敌 only——立绘上半部居中悬浮)
|
||||
if kind == "enemy":
|
||||
_intent_badge = UiKit.label("", 15, true)
|
||||
_intent_badge.set_anchors_preset(Control.PRESET_CENTER_TOP)
|
||||
_intent_badge.offset_left = -20.0
|
||||
_intent_badge.offset_right = 20.0
|
||||
_intent_badge.offset_top = 74.0
|
||||
_intent_badge.offset_bottom = 98.0
|
||||
_intent_badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_intent_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_intent_badge.visible = false
|
||||
add_child(_intent_badge)
|
||||
|
||||
# 底座
|
||||
_platform = Panel.new()
|
||||
_platform.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
||||
_platform.offset_left = 26.0
|
||||
_platform.offset_right = -26.0
|
||||
_platform.offset_top = -18.0
|
||||
_platform.offset_bottom = -8.0
|
||||
_platform.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_platform.add_theme_stylebox_override("panel",
|
||||
UiKit.panel_flat(UiKit.STONE.darkened(0.35), UiKit.STONE.darkened(0.1), 6, 1))
|
||||
add_child(_platform)
|
||||
|
||||
# 底座下 护甲类型图标+名字 常驻行(敌 only——2026-09-26 用户终裁:AC 数值不展示,类型图标化)
|
||||
if kind == "enemy":
|
||||
_name_line = HBoxContainer.new()
|
||||
_name_line.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
_name_line.add_theme_constant_override("separation", 3)
|
||||
_name_line.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_name_line.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
||||
_name_line.offset_top = -8.0
|
||||
add_child(_name_line)
|
||||
_armor_icon = UiKit.label("", 12, true)
|
||||
_armor_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_name_line.add_child(_armor_icon)
|
||||
_name_label = UiKit.label("", 12, false)
|
||||
_name_label.add_theme_color_override("font_color", UiKit.INK_TITLE)
|
||||
_name_label.clip_text = true
|
||||
_name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
_name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_name_line.add_child(_name_label)
|
||||
|
||||
|
||||
# ---- setter 面(投影契约与旧敌卡一致——presenter 装配面零改排) ----
|
||||
|
||||
# data = 战斗结算 presentation_snapshot enemies[] 投影:{order, display_name, hp, max_hp, enemy_id,
|
||||
# pending_skill/pending_type/pending_name, effects[](敌方 side 增益——各立绘同值呈现)}
|
||||
func set_data(d: Dictionary) -> void:
|
||||
_display_name = String(d.get("display_name", "敌人"))
|
||||
_enemy_id = String(d.get("enemy_id", ""))
|
||||
_armor = String(d.get("armor_type", "light"))
|
||||
_hp_bar.max_value = maxi(1, int(d.get("max_hp", 1)))
|
||||
_set_hp(int(d.get("hp", 0)))
|
||||
set_order(int(d.get("order", 1)))
|
||||
set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", "")))
|
||||
set_effects(d.get("effects", []))
|
||||
_update_name_line()
|
||||
_refresh_portrait_glyph()
|
||||
if _dead and int(d.get("hp", 0)) > 0:
|
||||
_revive()
|
||||
|
||||
|
||||
func set_hero(hp: int, max_hp: int) -> void:
|
||||
_hp_bar.max_value = maxi(1, max_hp)
|
||||
_set_hp(hp)
|
||||
|
||||
|
||||
# 血条短 tween 递减(§2.2 直角递减占位——密度/缓动终稿归战斗表现批);逐行刷新时重启 tween 不累积
|
||||
func _set_hp(hp: int) -> void:
|
||||
var target := float(clampi(hp, 0, int(_hp_bar.max_value)))
|
||||
if _hp_tween != null and _hp_tween.is_valid():
|
||||
_hp_tween.kill()
|
||||
_hp_tween = create_tween()
|
||||
_hp_tween.tween_property(_hp_bar, "value", target, UiKit.motion(0.15))
|
||||
|
||||
|
||||
func set_idle() -> void:
|
||||
_set_hp(0)
|
||||
set_effects([])
|
||||
|
||||
|
||||
# 主角/敌方共用——状态效果唯一展示面(2026-09-26 C 区精简批:compact 档 StatusSlotRow 整体迁入,
|
||||
# 原 B 区迷你字形行退役;悬停 tooltip(来源+语义+剩余结算次数)义务自 C 区状态槽并入本组件;
|
||||
# 空态 = 图标行隐藏不占位(增益状态 EC12))
|
||||
func set_effects(effects: Array) -> void:
|
||||
slot_row.set_effects(effects)
|
||||
slot_row.visible = not effects.is_empty()
|
||||
|
||||
|
||||
# battle_over:状态图标行全清可见反馈(增益状态×4④ 载体迁移——逐槽化光 + 微文案,
|
||||
# 自 C 区状态槽迁入;主角侧调用,敌方不适用)
|
||||
func set_effects_cleared_feedback() -> void:
|
||||
slot_row.visible = true
|
||||
slot_row.set_cleared_feedback()
|
||||
var note := UiKit.label("状态随战斗结束消散", 11, false)
|
||||
note.add_theme_color_override("font_color", UiKit.STONE_FAINT)
|
||||
note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
note.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_top_row.add_child(note)
|
||||
var tw := create_tween()
|
||||
tw.tween_interval(1.2)
|
||||
tw.tween_property(note, "modulate:a", 0.0, UiKit.motion(0.8))
|
||||
tw.tween_callback(note.queue_free)
|
||||
|
||||
|
||||
func set_order(i: int) -> void:
|
||||
# 顺位徽标 = 行主序生成序(固定顺位——敌人系统 顺位标记义务)
|
||||
if _order_badge != null:
|
||||
_order_badge.text = String("①②③"[clampi(i - 1, 0, 2)])
|
||||
|
||||
|
||||
# 摇定动作渲染(敌人技能池批 三档可见性;渲染映射唯一定义处归本类——code-review 2026-09-12 先例):
|
||||
# pending_type = 战斗结算 快照投影(basic/damage/buff/debuff;"" = 未摇定/死亡残位)。
|
||||
# 表现层升级批拍板:不显示技能名——类型级图标 + 一次性动作动画(_play_intent_motion)。
|
||||
# 信号/入参契约:set_data 每结算行刷新、set_pending 每轮开轮刷新(开轮摇号时点)。
|
||||
func set_pending(ptype: String, pid: String) -> void:
|
||||
if _intent_badge == null or _dead:
|
||||
return
|
||||
if ptype == "" or ptype == "basic":
|
||||
_intent_badge.visible = false # 普通攻击零表示(完全不可预测——§2.4)
|
||||
return
|
||||
var glyph := "⚔" # 攻击系(damage)
|
||||
var col: Color = UiKit.FLAG_RED
|
||||
if ptype == "buff":
|
||||
glyph = "✦" # 增益系
|
||||
col = UiKit.HERO_BLUE
|
||||
elif ptype == "debuff":
|
||||
glyph = "☠" # 妨害系(新效应词汇批 2026-09-19)
|
||||
col = UiKit.DEBUFF_PURPLE
|
||||
if pid in CHARGE_SKILL_IDS:
|
||||
col = UiKit.GOLD_DEEP # 重击类蓄力档(2026-09-18 用户拍板独立蓄力提示)
|
||||
_intent_badge.text = glyph
|
||||
_intent_badge.add_theme_color_override("font_color", col)
|
||||
_intent_badge.visible = true
|
||||
_play_intent_motion()
|
||||
|
||||
|
||||
# 三档动作动画占位:摇定时一次性短动画(意图图标弹入 + 立绘前倾回正),随后静帧保持——
|
||||
# 挖掘期唯一在动元素 = 可挖域微光(art-bible §2.1);终稿动画密度归战斗表现样张批
|
||||
func _play_intent_motion() -> void:
|
||||
_intent_badge.pivot_offset = _intent_badge.size * 0.5
|
||||
_portrait.pivot_offset = _portrait.size * 0.5
|
||||
var tw := create_tween()
|
||||
tw.tween_property(_intent_badge, "scale", Vector2(1.6, 1.6), UiKit.motion(0.10))
|
||||
tw.tween_property(_intent_badge, "scale", Vector2.ONE, UiKit.motion(0.16))
|
||||
tw.parallel().tween_property(_portrait, "rotation_degrees", 4.0, UiKit.motion(0.10))
|
||||
tw.tween_property(_portrait, "rotation_degrees", 0.0, UiKit.motion(0.14))
|
||||
|
||||
|
||||
# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
|
||||
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右——2026-09-26 用户点名英雄立绘水平向右)。
|
||||
# 盒高 96px = 48px 立绘源整数 2×(圣经 §8.2 禁非整数缩放;offset_top 44 = 血条+状态行之下净空)——
|
||||
# 源换代时随源重算盒高。
|
||||
func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
|
||||
if _portrait_tex == null:
|
||||
_portrait_tex = TextureRect.new()
|
||||
_portrait_tex.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_portrait_tex.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_portrait_tex.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊(棋盘小人贴图批 2026-09-25)
|
||||
_portrait_tex.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_portrait_tex.offset_top = 44.0
|
||||
_portrait_tex.offset_bottom = -36.0 # 盒高 96 = 48 源 ×2 整数档(原 −26 盒高 106 系非整数缩放)
|
||||
_portrait_tex.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_portrait_tex)
|
||||
_portrait_tex.texture = tex
|
||||
_portrait_tex.flip_h = flip
|
||||
_portrait_tex.visible = tex != null
|
||||
_portrait.visible = tex == null
|
||||
|
||||
|
||||
func mark_dead() -> void:
|
||||
if _dead:
|
||||
return
|
||||
_dead = true
|
||||
modulate = Color(0.62, 0.62, 0.62, 0.9)
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
|
||||
tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1))
|
||||
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
|
||||
_refresh_portrait_glyph()
|
||||
if _intent_badge != null:
|
||||
_intent_badge.visible = false
|
||||
_set_hp(0)
|
||||
|
||||
|
||||
func is_dead() -> bool:
|
||||
return _dead
|
||||
|
||||
|
||||
# 受击闪白(结算演出占位——§2.2 受击反馈)
|
||||
func flash_hit() -> void:
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
|
||||
tw.tween_property(self, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
|
||||
|
||||
|
||||
func _revive() -> void:
|
||||
_dead = false
|
||||
modulate = Color(1, 1, 1)
|
||||
rotation_degrees = 0.0
|
||||
_refresh_portrait_glyph()
|
||||
_update_name_line()
|
||||
|
||||
|
||||
func _update_name_line() -> void:
|
||||
if _name_line == null:
|
||||
return
|
||||
_name_label.text = _display_name
|
||||
var col: Color = UiKit.STONE # 轻甲 = 灰石(中性轻薄)
|
||||
match _armor:
|
||||
"ward":
|
||||
col = UiKit.HERO_BLUE # 魔法护甲 = 小人蓝(避开 Boss 专属紫赤/妨害紫)
|
||||
"heavy":
|
||||
col = UiKit.GOLD_DEEP # 重甲 = 深金(厚重达意金)
|
||||
_armor_icon.text = String(ARMOR_ICONS.get(_armor, "◇"))
|
||||
_armor_icon.add_theme_color_override("font_color", col)
|
||||
|
||||
|
||||
func _refresh_portrait_glyph() -> void:
|
||||
if _kind == "hero":
|
||||
_portrait.text = "人"
|
||||
return
|
||||
if _dead:
|
||||
_portrait.text = "✕"
|
||||
return
|
||||
match _enemy_id:
|
||||
"goblin":
|
||||
_portrait.text = "▂" # 小体量墩形
|
||||
"bugbear":
|
||||
_portrait.text = "▆" # 垂直延伸体量
|
||||
"ogre_chief":
|
||||
_portrait.text = "█" # 最大体量
|
||||
_:
|
||||
_portrait.text = "敌"
|
||||
@@ -1 +0,0 @@
|
||||
uid://gfvj2sxds3ph
|
||||
+16
-11
@@ -110,21 +110,26 @@ func _ready() -> void:
|
||||
problems.append("B 区明细带挖掘期应收起 24px(mode=%s h=%s)" % [main.log_view.mode(), str(main.log_view.custom_minimum_size.y)])
|
||||
# E 区道具面板已随可选类删除下线(格面资源批 2026-09-15)——四区构建由其余断言覆盖
|
||||
# ---- 敌方站桩投影(顺位徽标/意图三档动作动画占位——敌人系统×4;2026-09-20 全站桩拍板)----
|
||||
var standee = main.stage.enemy_box.get_child(0)
|
||||
if standee != null and (standee._name_label.text == "" or standee._armor_icon.text == ""):
|
||||
problems.append("敌立绘名字行缺失(名字或护甲类型图标占位为空——2026-09-26 终裁:AC 不展示、类型图标化)")
|
||||
if standee != null and standee._intent_badge == null:
|
||||
problems.append("敌立绘意图指示缺失")
|
||||
elif standee != null:
|
||||
# B 区站桩 Node2D 迁移批 T3(2026-09-26)断言等价改写(台账 171 改判记录):enemy_box 子件
|
||||
# 伴随件化(壳层 tooltip 载体),穿透明(子件 _name_label/_armor_icon/_intent_badge)改本体
|
||||
# 探针出口 stage.enemy_body_at(i).name_line_probe()/intent_probe()——呈现口径逐字保持
|
||||
var standee = main.stage.enemy_body_at(0)
|
||||
if standee == null:
|
||||
problems.append("敌立绘本体缺失(enemy_body_at(0) 无世界站桩)")
|
||||
else:
|
||||
var name_line: Dictionary = standee.name_line_probe()
|
||||
if String(name_line.name) == "" or String(name_line.armor_icon) == "":
|
||||
problems.append("敌立绘名字行缺失(名字或护甲类型图标占位为空——2026-09-26 终裁:AC 不展示、类型图标化)")
|
||||
# 三档动作动画占位契约(表现层升级批拍板:不显示技能名——图标档位 + 一次性动画)
|
||||
standee.set_pending("damage", "")
|
||||
if not standee._intent_badge.visible or standee._intent_badge.text != "⚔":
|
||||
problems.append("攻击系意图未呈现(visible=%s text=%s)" % [str(standee._intent_badge.visible), standee._intent_badge.text])
|
||||
var intent_on: Dictionary = standee.intent_probe()
|
||||
if not bool(intent_on.visible) or String(intent_on.text) != "⚔":
|
||||
problems.append("攻击系意图未呈现(visible=%s text=%s)" % [str(intent_on.visible), str(intent_on.text)])
|
||||
standee.set_pending("basic", "")
|
||||
if standee._intent_badge.visible:
|
||||
if bool(standee.intent_probe().visible):
|
||||
problems.append("普攻应零表示(意图指示应隐藏)")
|
||||
if main.stage.hero_standee == null or main.stage.hero_standee._hp_bar == null:
|
||||
problems.append("B 区主角站桩缺失")
|
||||
if main.stage.hero_standee == null or not main.stage.hero_standee.has_world_hp_bar:
|
||||
problems.append("B 区主角站桩缺失(世界血条面缺失)")
|
||||
# ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)----
|
||||
var slots_row = main.stage.hero_standee.slot_row # BUG-0001 回归随组件迁入站桩(tooltip/槽位错位同源)
|
||||
var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1,
|
||||
|
||||
@@ -457,4 +457,5 @@
|
||||
|
||||
168. **战斗舞台 Node2D 循环 1 · T0 开工批(2026-09-26,本会话——计划书 .zcode/plans/battle-stage-node2d-loop-plan-2026-09-26.md 已批准从 T0 自治开跑;worktree battle-stage-node2d-20260926)**:worktree 自 master 70d2320 建+editor --quit 刷缓存 0 error;git status 归属核对=主区脏文件(.gitignore/.zcode config/ui-reuse 指引/playnote/project.godot/loop-dashboard/active.md 165-167 未提交并行登记+untracked 美术文档组)均非本批只标旗不碰。**基线复读全绿记档**——冒烟 557/0+五探针 PASS(ui_probe cells=144 board=12x12/ui_click 问题=0/ui_tune 全链/ui_layout dump:stage 1280×252·board_view 1036×965·info_panel 240×1028/ui_input 十一断言)+static_scan exit0+文档三闸基线存档(死链 7/退役词命中/gdd_lint 8 文件 8 不通过 69 项级)+MC 12 局 both(max 路由) 24 行 RUNSUM sha256=de8225b3 存 /tmp 供 T7 逐位对拍。**§2 现状事实逐条 grep 复核零漂移**——battle_stage_view(PanelContainer/252px/五成员/setter 面)+standee_view(Control/行为面 17 方法/slot_row compact/字形占位)+hud_presenter 53-54/79-80 单投喂+main 309/441/503+ui_assembler 23+ui_probe 95-129/ui_click 223 穿透面+ui_input_probe 零 B 区耦合(grep stage/standee/enemy=0 命中)+TEX_HERO_PORTRAIT 素材 worktree 缺位=C7 回退字形路径预期。**Alpha 清单初稿落盘** production/alpha-acceptance-battle-screen.md——六源拼装(battle.md AC+§13 义务表/HUD框架 验收标准+基线表/art-bible §8 判据/07§⑨ 试玩指引/06 挂 Alpha 条目备查/工程回归面),七节 30 条=判据+验法三档(headless/真机渲染/人)+状态列,销项归 Alpha 验收批。轻闸全绿,进入 T1。
|
||||
169. **样张 v1 制作与机检批(2026-09-26,本会话——循环 1 T1;worktree battle-stage-node2d-20260926 批次本提交)**:①**四源对照表先行** production/mockups/画法参数四源对照表-2026-09-26.md(battle.md 布局/机制代码〔0 格无字形 board_view:586·旗只插未开 :589·脚锚自底 25%〕/引擎常量〔45px·1.5× 档·96px 盒〕/art-bible §1.2-§4.6 画法)——候选值偏差两笔登记(小人蓝 #2E86DE 候选 vs UiKit #2E6E8E=样张批冻结项非矛盾;格底 L≥0.30 系换皮槽参数域不复裁已验收 dungeon 瓦片)。②**脚本重写** tools/render_sample_sheet.py v1:方案甲词表查表拼装(16 情形+对角回落 nw>ne>sw>se+flip 镜像+变体 60/20/20 哈希)+三区全内容 1280×800 实比 mockup+S2 数字档 0.5× 抽检+S3 underdark 四槽换色+S4 图例;素材两级回退(worktree→主检出,素材不入 git)。③**机检 12 断言生成即验全绿**——F1 词表拼装零错位 15 种用件/F2 旗∈未开/F3 脚锚自底 25%(两态)〔预筛抓出素材底部 7 行全透明→裁除留白后锚定,视觉脚=锚线;**游戏内 BoardWorld.ensure_hero 同病(视觉脚浮锚线 ~10px)=呈现缺陷登记移交循环 2/接线批**〕/F4 0 格无字形/F5a 接触带微光⊆英雄格/F5b 类型微光禁整格染色(中心 50% 区零像素——角嵌臂长两轮收紧 L9→L5 留白砖心)/A1 色板对表+禁入带 300-345° 零命中(腐蚀两轮滤 AA 混色)/A2 周界闭合(墙侧 2px 带墨线存在性 min<55——45px 非整数缩放下均值判误报改最暗值判)/A3 墨×牌 11.8-12.0:1+图底分离 113 vs 51/A4 混排在库原件/A5 画布四缘净空(溢出永久断言)。④**visual-judge 预筛三轮**(≤3 预算内):R1 四 FATAL→triage=站桩横向重叠/小人脚浮(素材留白)/同格态色牌被小人盖(改 §1.2 栈序数字>小人)/缘件裸拼误报(1px 墨线逐像素 zoom 自证 end_n 三向墨线俱在+15 种用件机检反证,判官 3-6px 采样带均值漏 1px 线)+地精无徽标误报(普攻零表示=设计);R2 两 FATAL=画布右缘溢出×2(说明行+S4 末项)→画布 1560+A5 断言化;R3 窄域复核 pass。⑤**交付**=样张 v1+机检报告+对照表三件已拷主区 production/mockups/(评审产物不入 git 同 v0 先例),**用户异步终审**(过=冻结为循环 2 输入;不过=对表改参重出);终审不阻塞 T2-T7。轻闸全绿(当轮基线=T0 记档),进入 T2。
|
||||
170. **B 区战斗舞台壳改造批(Phase B T2,2026-09-26,本会话——循环 1;worktree battle-stage-node2d-20260926)**:BattleStageView 根壳 PanelContainer→**Control**(类名/252px/树内位置不变;面板样式=Panel 子面+原 stylebox 等价平移,子面几何沿原内容边距 Rect2(6,4,6,4));内部拆两半区——**Node2D 舞台半区**=SubViewportContainer(stretch=true 1:1 禁缩放系数)+SubViewport(transparent_bg)+**StageWorld**(新件 presentation/views/stage_world.gd,无 class_name 经 preload 接线,BoardWorld 同构:world_font 字体注入槽位+font_probe() C6 判据出口〔注入非空+has_char+宽度>0〕;T2 期世界空置待 T3 站桩迁入)+**Control 文本半区**=round_label/stage_center/log_view 三件原样保留(C1 永久 Control),hero_standee/enemy_box 本期仍 Control 原位。新挂点=set_world_font(f)(Phase A BoardView 同款,无调用方纯挂点)+world_to_shell(p) 坐标换算单点(C4——世界=局部排布/壳层=全局含容器偏移)。**C6 判据实证**(临时脚本跑完即删):未注入=回退字体 has_char=false(引擎默认无 CJK=文档既有口径);注入 fusion-pixel ja TTF(内存 load_dynamic_font 零素材入工程——C7)后 has_char=true 宽度 70>0 PASS。**验收**=轻闸全绿:editor --quit 0 error+冒烟 557/0(=基线)+五探针全绿+ui_layout_probe 外框逐位零漂移(stage pos=(0,0) size=(1280,252) min=(0,252)=T0 基线同值;B 区内部树结构变化记录:PanelContainer 自动布局→Control 锚点+VBox 同边距)+static_scan exit0+文档三闸零漂移。进入 T3。
|
||||
170. **B 区战斗舞台壳改造批(Phase B T2,2026-09-26,本会话——循环 1;worktree battle-stage-node2d-20260926)**:BattleStageView 根壳 PanelContainer→**Control**(类名/252px/树内位置不变;面板样式=Panel 子面+原 stylebox 等价平移,子面几何沿原内容边距 Rect2(6,4,6,4));内部拆两半区——**Node2D 舞台半区**=SubViewportContainer(stretch=true 1:1 禁缩放系数)+SubViewport(transparent_bg)+**StageWorld**(新件 presentation/views/stage_world.gd,无 class_name 经 preload 接线,BoardWorld 同构:world_font 字体注入槽位+font_probe() C6 判据出口〔注入非空+has_char+宽度>0〕;T2 期世界空置待 T3 站桩迁入)+**Control 文本半区**=round_label/stage_center/log_view 三件原样保留(C1 永久 Control),hero_standee/enemy_box 本期仍 Control 原位。新挂点=set_world_font(f)(Phase A BoardView 同款,无调用方纯挂点)+world_to_shell(p) 坐标换算单点(C4——世界=局部排布/壳层=全局含容器偏移)。**C6 判据实证**(临时脚本跑完即删):未注入=回退字体 has_char=false(引擎默认无 CJK=文档既有口径);注入 fusion-pixel ja TTF(内存 load_dynamic_font 零素材入工程——C7)后 has_char=true 宽度 70>0 PASS。**验收**=轻闸全绿:editor --quit 0 error+冒烟 557/0(=基线)+五探针全绿+ui_layout_probe 外框逐位零漂移(stage pos=(0,0) size=(1280,252) min=(0,252)=T0 基线同值;B 区内部树结构变化记录:PanelContainer 自动布局→Control 锚点+VBox 同边距)+static_scan exit0+文档三闸零漂移。进入 T3。
|
||||
171. **站桩本体 Node2D 化批(Phase B T3,2026-09-26,本会话——循环 1 最重卡;worktree battle-stage-node2d-20260926)**:站桩本体自 Control(旧 StandeeView **退役删除**)平移为 Node2D 世界精灵 **standee_body.gd**(新件,无 class_name 经 preload)。**行为面 17 方法契约逐项保持(C3)**——set_data/set_hero/_set_hp/set_idle/set_effects/set_effects_cleared_feedback/set_order/set_pending/_play_intent_motion/set_portrait_texture(tex,flip)/mark_dead/is_dead/flash_hit/_revive/_update_name_line/_refresh_portrait_glyph+is_dead,调用点 hud_presenter:53-54/79-80、main:309/441/475/495/503、main:768/770 零改排。**等价平移清单(C9)**——立绘=set_portrait_texture 惰性 Sprite2D(NEAREST+flip_h+整数 2×=48 源盒 96px)缺位回退字形(人/▂/▆/█/✕/敌 draw_string);底座=UiKit.panel_flat(STONE 系) StyleBox.draw 直绘;血条=直角矩形 hp_value 属性 tween 0.15 递减(引擎默认主题深底/蓝填充近似平移——色值列目检清单);顺位徽标 ①②③ GOLD_DEEP;意图徽标=draw_set_transform 弹入 scale 1.6→1+字形前倾 4°→0(旧只转字形 Label——贴图态无动画等价保持);名字行=护甲图标+纯名 draw_string 居中(AC 不展示 2026-09-26 终裁);受击闪白 modulate 1.6/1.4/1.2→白/死灰;死亡=灰化+rotation 2.5/-1.5/0 摇摆塌陷+意图退位(禁瞬消)+**状态行随 mark_dead 同拍隐藏(代拍板②待追认)**;复活=全态重置。**批内代拍板默认(待追认)**——①slot_row+悬停 tooltip 保留 Control 载体挂壳层(_companion_layer 顶层,敌伴随件宿主=**enemy_box 语义改写「站桩壳层伴随件容器」HBoxContainer→Control**,手动定位随世界同步,探针 :97 计数面不变);②定位同步=world_to_shell 单点(T2 建挂),_sync_layout 挂点=ready/NOTIFICATION_RESIZED/_row.resized(明细带展收改行高随动)/set_enemies 重建,站桩盒高=行高(旧 HBox FILL 拉伸等价)、敌右对齐 +8 间距等价;③hero_standee/enemy 站桩成员类型 StandeeView→Node2D(StandeeBody),**hero_standee_center/enemy_standee_center 改 world_to_shell(本体中心)=壳层口径(C4)**。ui_probe 敌站桩断言等价改写(台账 170 前例):穿透明(enemy_box 子件 _name_label/_armor_icon/_intent_badge/_hp_bar)→本体出口 enemy_body_at(i)+name_line_probe()/intent_probe()/has_world_hp_bar——呈现口径逐字保持。**踩坑记录**——standee_body 漏声明 _portrait_tex 成员→编译连坐 main.gd 假性两错(w/h 推断失败),editor --quit 前置闸暴露后修复。**验证**——editor --quit 0 error+冒烟 557/0(=基线零增删)+五探针全绿(ui_probe/ui_click 问题=0/ui_tune/ui_input 十一断言/layout 外框零漂移 stage 1280×252 逐位=T0 基线)+static_scan exit0+文档三闸零漂移。进入 T4。
|
||||
Reference in new issue
Block a user