From e7920e8fe01a71a46ac167640630557d9674a8c2 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 31 Oct 2026 00:33:02 +0800 Subject: [PATCH] =?UTF-8?q?iter45-=E5=8D=A11=EF=BC=9A=E5=8D=95=E6=A0=BC?= =?UTF-8?q?=E4=B8=A4=E7=BB=98=E5=88=B6=E5=9F=9F=E4=BB=B6+=E7=AB=99?= =?UTF-8?q?=E6=A1=A9=E4=B8=A4=E7=8A=B6=E6=80=81=E4=BB=B6+=E6=8E=A2?= =?UTF-8?q?=E9=92=88u.face=E6=94=B9=E5=9D=80=EF=BC=88golden=E4=B8=89?= =?UTF-8?q?=E5=BC=A00.00%=EF=BC=8C=E9=94=AE=E8=B4=A630=E2=86=9210/19?= =?UTF-8?q?=E2=86=9210=E6=B8=85=E5=81=BF=EF=BC=89=E3=80=94=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=BB=B6V1-V4=E3=80=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/views/board_cell_unit.gd | 412 ++---------------- game/presentation/views/board_view.gd | 96 ++-- game/presentation/views/cell_base_layer.gd | 204 +++++++++ .../presentation/views/cell_base_layer.gd.uid | 1 + game/presentation/views/cell_mark_overlay.gd | 145 ++++++ .../views/cell_mark_overlay.gd.uid | 1 + game/presentation/views/standee_body.gd | 207 ++------- game/presentation/views/standee_motion.gd | 128 ++++++ game/presentation/views/standee_motion.gd.uid | 1 + game/presentation/views/standee_vitals.gd | 36 ++ game/presentation/views/standee_vitals.gd.uid | 1 + game/probe/ui_input_probe.gd | 22 +- 12 files changed, 666 insertions(+), 588 deletions(-) create mode 100644 game/presentation/views/cell_base_layer.gd create mode 100644 game/presentation/views/cell_base_layer.gd.uid create mode 100644 game/presentation/views/cell_mark_overlay.gd create mode 100644 game/presentation/views/cell_mark_overlay.gd.uid create mode 100644 game/presentation/views/standee_motion.gd create mode 100644 game/presentation/views/standee_motion.gd.uid create mode 100644 game/presentation/views/standee_vitals.gd create mode 100644 game/presentation/views/standee_vitals.gd.uid diff --git a/game/presentation/views/board_cell_unit.gd b/game/presentation/views/board_cell_unit.gd index f1c99076..8bb85643 100644 --- a/game/presentation/views/board_cell_unit.gd +++ b/game/presentation/views/board_cell_unit.gd @@ -1,128 +1,31 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION -## CellUnit——BoardView SubViewport 世界内逐格渲染件(Node2D),全部程序化 _draw 占位视觉(纹理槽位已留未装载)。 +## CellUnit——BoardView SubViewport 世界内逐格渲染件(Node2D),被动绘制壳:_draw 六步时序器 +## +视野迷雾域+微光子件生命周期+格件几何;基座层与标识叠加两绘制域件 _init 自建持有 +## (cell_base_layer/cell_mark_overlay——单一变化原因=迷雾规格与格件节点生命周期)。 ## 无 class_name(类名缓存面纪律)——使用方经 preload 引用。 -## 内部状态件(u3 子域拆件——契约 9 职责语句「单格绘制输入集」):本件为被动绘制壳, -## 状态件 _CellPaintState 承载全部绘制入参(单一变化原因=格面呈现),setter 触发宿主重绘语义 -## 经 _owner 回引保持逐字;探针直读冻结成员(glyph/fog_state/plate/glyph_color)与 _glow_layer 住宿主。 +## 探针直读冻结成员(glyph/fog_state/plate/glyph_color)与 _glow_layer 住宿主; +## hl_color/hl_visible/dot_visible/mutation_mark 探针读 u.face.*。 extends Node2D const _GLOW_LAYER := preload("res://presentation/views/cell_type_glow_layer.gd") +const _BASE_LAYER := preload("res://presentation/views/cell_base_layer.gd") +const _MARK_OVERLAY := preload("res://presentation/views/cell_mark_overlay.gd") -# ── D 投影档绘制常量(研究件实测取色——立面高光/双段暗带/地面投影;黑罩 alpha 叠加实测等效)── -const FACADE_HIGHLIGHT_PX := 2.0 # 口沿高光线厚(像素) -const FACADE_NEAR_FRAC := 0.16 # 立面近段高(格边比——研究件 10/64) -const FACADE_FAR_FRAC := 0.25 # 立面远段高(格边比——研究件 16/64) -const FACADE_NEAR_COL := Color(0, 0, 0, 0.60) # 立面近段黑罩(墙顶 132→52 实测 α≈0.61) -const FACADE_FAR_COL := Color(0, 0, 0, 0.82) # 立面远段黑罩(墙顶 132→24 实测 α≈0.82) -const PROJECTION_NEAR_COL := Color(0, 0, 0, 0.28) # 投影贴墙段(地面 66→46 实测 α≈0.28) -const PROJECTION_MID_COL := Color(0, 0, 0, 0.16) # 投影中段(渐隐) -const PROJECTION_EDGE_COL := Color(0, 0, 0, 0.06) # 投影远段(渐隐收边) -# ── E 格高墙面(圣经 v3.5 冻结①——地面格南邻墙→南部 30% 承接墙面:墙顶纹理原亮度 -# 直贴零暗档零阴影;墙脚阴线只画地面/墙面交界;(b)-(c) 交界 -# 无边框由 BoardView 侧跳过墙格北向描边承载)── -const E_FACADE_FRAC := 0.30 # 墙面承接高(格边比 30%) - - -## 单格绘制输入集(被动绘制壳的全部绘制入参——BoardView 直写面;setter 触发宿主重绘语义逐字) -class _CellPaintState extends RefCounted: - var _owner: Node2D = null - var cell := Vector2i.ZERO # 板坐标(诊断观测面) - var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动) - set(v): - edge = v - if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer): - _owner._glow_layer.setup(edge, glow_group) - _owner.queue_redraw() - var bg_sb: StyleBoxFlat = null # 格底 StyleBox(BoardView 每次 paint 重建;呼吸微光就地改边框) - # ── 接触带微光绘制面(可挖域微光画在英雄格周界空间侧——art-bible §3.2 v2.8; - # 仅英雄格单元承载,BoardView.refresh 按可挖域写边/角码、_process 逐拍更新呼吸色)── - var contact_bands: Array[int] = [] # 正交接触边码 0=N/1=S/2=W/3=E(该侧整条边) - var contact_corners: Array[int] = [] # 斜向共享角码 0=NW/1=NE/2=SW/3=SE(角点+两侧各半条边) - var contact_col := Color(0, 0, 0, 0) # 当前呼吸色(alpha 0 = 不画) - var outline_sides: Array[int] = [] # 墙格地面朝向描边码 0=N/1=S/2=W/3=E(程序侧结构件) - # ── 高墙感 D 投影档(研究件四变体定 D=C+地面投影,ITB 同款高度线索)── - # 南立面 = 墙格南缘内侧「口沿高光线 + 双段暗带」(邻地面格才画,墙群内聚);地面投影 = 北邻墙格 - # 在本已开格北缘投渐隐暗带。参数按研究件 64px 拼版实测比例换算(立面双段 0.16/0.25 格边、投影带 - # 0.22 格边;坐标推导留档 commit)。素材缺位程序化回退同样服役(结构归程序——art-bible §8.5)。 - var facade_south := false: # 南立面开关(BoardView paint 按「南邻已开格」写入) - set(v): - facade_south = v - _owner.queue_redraw() - var proj_band := 0.0: # 地面投影带宽(像素,0 = 不画——BoardView 按北邻墙判定写入) - set(v): - proj_band = v - _owner.queue_redraw() - var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区) - set(v): - badge_glyph = v - if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer): - _owner._glow_layer.set_badge_mask(v != "") - _owner.queue_redraw() - var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝) - set(v): - dot_visible = v - _owner.queue_redraw() - var hl_visible := false: # 焦点高亮(悬停态边框覆盖层) - set(v): - hl_visible = v - _owner.queue_redraw() - var hl_color := UiKit.GOLD: # 高亮色(金目标/封印朱/禁入灰禁红) - set(v): - hl_color = v - _owner.queue_redraw() - var font: Font = null # 字体槽位(null = ThemeDB.fallback_font——BoardView.set_world_font 注入) - var bevel := 0.0: # 顶面倒角强度(0=关;档位 0/0.35/0.6/1.0) - set(v): - bevel = v - _owner.queue_redraw() - # ── 素材槽位(现全 null = 程序化占位服役;art-bible §8.2 格面 32×32 暂定档)── - var tex_base: Texture2D = null # ① 砖面底 - var tex_mutation: Texture2D = null # ② 变异纹带 - var tex_glyph: Texture2D = null # ③ 数字 - var tex_badge: Texture2D = null # ③' 徽章 - var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark - set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示)) - if tex_seal != v: - tex_seal = v - _owner.queue_redraw() - var tex_facade: Texture2D = null # ⑥ E 格墙面材质(v3.5——南邻墙顶 center 瓦片同源,BoardView 注入) - var facade_strip := false: # E 格开关(BoardView paint 按「已开格且南邻未开墙」写入——瓦片态专用) - set(v): - facade_strip = v - _owner.queue_redraw() - - # 变异角标(程序化,tex_mutation 纹带槽暂留): - # "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记) - # 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写): - # "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,封印照常计费); - # "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形) - var treasure_mark := "": - set(v): - if treasure_mark != v: - treasure_mark = v - _owner.queue_redraw() - var mutation_mark := "": - set(v): - if mutation_mark != v: - mutation_mark = v - _owner.queue_redraw() - - # ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示) - # 作用集 = 可挖域 ∩ 身份可查格,由 BoardView 侧派生下发;本件只承接绘制与子件生命周期。 - # 静态:不呼吸、不入动效预算;相位门控(结算/道具窗口态隐藏)在 BoardView 侧。 - var glow_group := 0: - set(v): - if glow_group != v: - glow_group = v - _owner._sync_glow_layer() - - var removed_dim := false: # 中立移除退色层(BoardView paint 按排雷玩法 is_removed 写入) - set(v): - removed_dim = v - _owner.queue_redraw() - - -var st := _CellPaintState.new() +var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动) + set(v): + edge = v + if _glow_layer != null and is_instance_valid(_glow_layer): + _glow_layer.setup(edge, glow_group) + queue_redraw() +var cell := Vector2i.ZERO # 板坐标(诊断观测面) +# ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示) +# 作用集 = 可挖域 ∩ 身份可查格,由 BoardView 侧派生下发;本件只承接绘制与子件生命周期。 +# 静态:不呼吸、不入动效预算;相位门控(结算/道具窗口态隐藏)在 BoardView 侧。 +var glow_group := 0: + set(v): + if glow_group != v: + glow_group = v + _sync_glow_layer() var glyph := "": # 文字位——数字/雷/封印/人/?(现行视觉封印与数字互斥,同位承载;探针直读冻结) set(v): glyph = v @@ -141,268 +44,51 @@ var fog_state := 0: # 探针直读冻结 queue_redraw() var _glow_layer: Node2D = null +var base = null # 格面基座层绘制域件(_init 自建) +var face = null # 格面标识与叠加层绘制域件(_init 自建) func _init() -> void: - st._owner = self + base = _BASE_LAYER.new(self) + face = _MARK_OVERLAY.new(self) func _draw() -> void: - var rect := Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)) - _draw_base_layer(rect) - _draw_mutation_marks() - _draw_glyph_layer(rect) - _draw_overlay_layers(rect) - - -## ① 砖面底层域(占位 = StyleBoxFlat:格底色 + 暗化描边 + 3px 圆角;瓦片态描边覆盖层; -## 呼吸微光/主角蓝框由 BoardView 改 bg_sb 后触发重绘。素材缺位回退走 elif 程序化原样,零观感变化) -func _draw_base_layer(rect: Rect2) -> void: - if st.tex_base != null: - draw_texture_rect(st.tex_base, rect, false) - # 轮廓加强(轮廓线不明显):墙格地面朝向边补 2px 深墨线(§8.5 结构归程序—— - # 素材 1px 墨线在 45/36px 非整数缩放下视觉衰减,程序线保底;墙-墙内界不画,保墙群内聚; - # 坐标推导:各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合) - if not st.outline_sides.is_empty(): - _draw_outline_sides() - _draw_south_facade() - _draw_north_projection() - _draw_e_facade() - # 瓦片态描边覆盖层:tex_base 在位时 BoardView 传入的 bg_sb - # 已降级为「透明底 + 描边」载体——呼吸微光/主角蓝框画在瓦片之上(art-bible §1.2 叠层序 - # 不变:底 < 纹带 < 数字 < 封印 < 焦点高亮);素材缺位回退走 elif 程序化原样,零观感变化 - if st.bg_sb != null: - draw_style_box(st.bg_sb, rect) - _draw_contact_or_fallback(rect) - - -## 接触带微光或平色回退域(英雄格周界空间侧——正交邻墙=整条边;斜向=共享角点+两侧各半条边;光不进墙格): -## 带宽 4px 贴格边内侧(坐标推导:N=[0,0,edge,4] S=[0,edge-4,edge,4] W=[0,0,4,edge] E=[edge-4,0,4,edge]; -## 半条边=沿邻边自角点起 edge/2;角点圆 r=6 贴角点圆心,近白芯 r=2.5 提亮) -func _draw_contact_or_fallback(rect: Rect2) -> void: - if st.contact_col.a > 0.0: - _draw_north_projection() # 平色回退+微光并发的英雄格补画(瓦片路径已画——同像素重复无观感差) - _draw_e_facade() # E 格墙面补画(同上——英雄格兼 E 格时瓦片路径已画,重复无害) - _draw_contact_bands() - elif st.bg_sb != null: - draw_style_box(st.bg_sb, rect) - _draw_south_facade() # 平色回退路径——D 投影档两件同样服役(结构归程序) - _draw_north_projection() - _draw_bevel() - - -## ②③④ 中层域:变异纹带与标记/数字色牌与字形/徽章/封印 -func _draw_glyph_layer(rect: Rect2) -> void: - # ② 变异纹带(无程序化纹带——槽位先行,贴图落位即用) - if st.tex_mutation != null: - draw_texture_rect(st.tex_mutation, rect, false) - # ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?") - if glyph != "": - if plate: - _draw_digit_plate() - if st.tex_glyph != null: - draw_texture_rect(st.tex_glyph, rect, false) - else: - _draw_glyph(glyph, glyph_color, maxi(18, int(ceil(st.edge * 0.5)))) # 字号下限 18(§7.4 冻结公式) - if st.badge_glyph != "": - if st.tex_badge != null: - draw_texture_rect(st.tex_badge, rect, false) - else: - _draw_badge(st.badge_glyph) - # ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图) - if st.tex_seal != null: - draw_texture_rect(st.tex_seal, rect, false) - - -## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌; -## 牌=后备件——雷/「?」维持牌载保读性(牌上深墨族 12:1); -## 牌尺寸 0.60/0.62 格边居中;封印=未开格不垫牌) -func _draw_digit_plate() -> void: - var pw := st.edge * 0.60 - var ph := st.edge * 0.62 - var pr := Rect2((st.edge - pw) * 0.5, (st.edge - ph) * 0.5, pw, ph) - draw_rect(pr, UiKit.DIGIT_PLATE, true) - draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0) - - -## ⑤⑥⑦ 顶层域:路径点迹 + 焦点高亮 + 视野迷雾 + 中立移除退色 -func _draw_overlay_layers(rect: Rect2) -> void: - # ⑤ 路径点迹 + 焦点高亮(悬停态覆盖层,不参与命中) - if st.dot_visible: - var ds := 6.0 - draw_rect(Rect2(Vector2((st.edge - ds) * 0.5, (st.edge - ds) * 0.5), Vector2(ds, ds)), UiKit.HERO_BLUE, true) - if st.hl_visible: - # 悬停高亮减淡(金框太明显——绘制时 55% alpha,hl_color 属性与探针断言面不动) - draw_rect(Rect2(1, 1, st.edge - 2.0, st.edge - 2.0), Color(st.hl_color, 0.55), false, 2.0) # 2px 内描边 - _draw_fog_layer() - # ⑦ 中立移除退色层(退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义; - # 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示危险格(无数字无封印印)) - if st.removed_dim: - draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(0.12, 0.10, 0.09, 0.62), true) + var rect := Rect2(Vector2.ZERO, Vector2(edge, edge)) + base.draw_base(self, rect) # 基座层整树(描边/立面/投影/接触带/倒角与其补画序) + face.draw_marks(self, rect) # 变异标记 + face.draw_glyph_layer(self, rect, glyph, glyph_color, plate) # 字形/色牌/徽章/封印 + face.draw_overlay_under(self, rect) # 点迹+高亮(雾层由宿主夹在其后) + _draw_fog_layer(rect) # 宿主迷雾域(原位——雾夹在 hl 与 removed 之间) + face.draw_removed_dim(self, rect) # 退色层 ## ⑥ 视野迷雾层(最顶遮罩,§6.5 落文): ## 全黑=不透明纯黑盖全部叠层(含封印印/徽章/高亮——封印印记随格隐没,§6.5 显式推论); ## 记忆态=记忆板素色压平(纹理免疫——色牌/封印印/徽章随板隐没)+数字记忆墨重画(数字可读硬指标 ≥4.5:1) -func _draw_fog_layer() -> void: +func _draw_fog_layer(rect: Rect2) -> void: if fog_state == 1: - draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(0, 0, 0, 1.0), true) + draw_rect(rect, Color(0, 0, 0, 1.0), true) elif fog_state == 2: - _draw_fog_memory() - - -## 墙格轮廓域:地面朝向边补 2px 深墨线(各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合) -func _draw_outline_sides() -> void: - var ow := 2.0 - var oc := UiKit.FOG_OUTLINE - for b in st.outline_sides: - var or_ := Rect2() - match int(b): - 0: or_ = Rect2(0, 0, st.edge, ow) - 1: or_ = Rect2(0, st.edge - ow, st.edge, ow) - 2: or_ = Rect2(0, 0, ow, st.edge) - 3: or_ = Rect2(st.edge - ow, 0, ow, st.edge) - draw_rect(or_, oc, true) - - -## 接触带微光域:正交整条边 + 斜向共享角点与两侧半条边 -func _draw_contact_bands() -> void: - var band := 4.0 - var half := st.edge * 0.5 - for b in st.contact_bands: - var r := Rect2() - match int(b): - 0: r = Rect2(0, 0, st.edge, band) - 1: r = Rect2(0, st.edge - band, st.edge, band) - 2: r = Rect2(0, 0, band, st.edge) - 3: r = Rect2(st.edge - band, 0, band, st.edge) - draw_rect(r, st.contact_col, true) - for cc in st.contact_corners: - _draw_contact_corner(int(cc), band, half) - - -## 斜向角点域:角点圆 r=6 贴角点圆心 + 近白芯 r=2.5 提亮 + 半条边(角点沿两条邻边各延伸 edge/2—— -## 坐标推导:横带沿 N/S 自角点向板心侧 half;NW/SE 半带 x=y=角点坐标,NE/SE 半带自 edge-half 收进—— -## 正交整边时重叠同色无害) -func _draw_contact_corner(cc: int, band: float, half: float) -> void: - var pt := Vector2.ZERO - match cc: - 0: pt = Vector2(0, 0) - 1: pt = Vector2(st.edge, 0) - 2: pt = Vector2(0, st.edge) - 3: pt = Vector2(st.edge, st.edge) - draw_circle(pt, 6.0, st.contact_col) - draw_circle(pt, 2.5, Color(1, 1, 1, minf(1.0, st.contact_col.a * 0.95))) - if cc == 0 or cc == 1: - # N 侧横带:NW 自 x=0、NE 自 x=edge-half - draw_rect(Rect2(pt.x if cc == 0 else pt.x - half, 0.0, half, band), st.contact_col, true) - if cc == 2 or cc == 3: - # S 侧横带:SW 自 x=0、SE 自 x=edge-half - draw_rect(Rect2(pt.x if cc == 2 else pt.x - half, st.edge - band, half, band), st.contact_col, true) - if cc == 0 or cc == 2: - # W 侧竖带:NW 自 y=0、SW 自 y=edge-half - draw_rect(Rect2(0.0, pt.y if cc == 0 else pt.y - half, band, half), st.contact_col, true) - if cc == 1 or cc == 3: - # E 侧竖带:NE 自 y=0、SE 自 y=edge-half - draw_rect(Rect2(st.edge - band, pt.y if cc == 1 else pt.y - half, band, half), st.contact_col, true) - - -## 顶面倒角域(光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让 -## 呼吸微光/主角蓝边框保持最外沿可见;强度随档位) -func _draw_bevel() -> void: - if st.bevel <= 0.0: - return - var base: Color = st.bg_sb.bg_color - var lit: Color = base.lightened(0.22 * st.bevel) - var dark: Color = base.darkened(0.22 * st.bevel) - var t := 2.0 - draw_rect(Rect2(t, t, st.edge - t * 2.0, t), lit, true) - draw_rect(Rect2(t, t, t, st.edge - t * 2.0), lit, true) - draw_rect(Rect2(t, st.edge - t * 2.0, st.edge - t * 2.0, t), dark, true) - draw_rect(Rect2(st.edge - t * 2.0, t, t, st.edge - t * 2.0), dark, true) - - -## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角 -func _draw_mutation_marks() -> void: - if st.treasure_mark == "mine_reveal": - var inset := st.edge * 0.14 - draw_rect(Rect2(inset, inset, st.edge - inset * 2.0, st.edge - inset * 2.0), - Color(UiKit.RED_MINE, 0.9), false, 2.0) - if st.mutation_mark == "flat_damage": - var mw := st.edge * 0.28 - var mpts := PackedVector2Array([Vector2(0, st.edge - mw), Vector2(0, st.edge), Vector2(mw, st.edge)]) - draw_colored_polygon(mpts, Color(UiKit.RED_MINE, 0.85)) + _draw_fog_memory(rect) ## 记忆态换装域(demo 口径「地板纹理 + 灰罩 α.66」: ## 地板纹理原样直画 + FOG_MEMORY_PLATE α.66 灰罩压顶;素材零改动、格边长零改动。 ## 记忆墨 FOG_MEMORY_INK 对合成底归一后(真实渲染面)三关 5.71–5.93:1 ≥4.5:1(§7.2 硬指标; ## 测点行值与素材随动,见 ui_contrast_probe 迷雾段)) -func _draw_fog_memory() -> void: - if st.tex_base != null: - draw_texture_rect(st.tex_base, Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), false) - draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(UiKit.FOG_MEMORY_PLATE, 0.66), true) +func _draw_fog_memory(rect: Rect2) -> void: + if base.tex_base != null: + draw_texture_rect(base.tex_base, rect, false) + draw_rect(rect, Color(UiKit.FOG_MEMORY_PLATE, 0.66), true) if glyph != "": - _draw_glyph(glyph, UiKit.FOG_MEMORY_INK, maxi(18, int(ceil(st.edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源) - - -# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿): -# 坐标推导(自格底边起算):远段=[edge−far, edge],近段=[edge−far−near, edge−far], -# 高光线=[edge−far−near−hl, edge−far−near]——与研究件「墙顶面→高光→暗段→更暗段→格界」下行序一致 -func _draw_south_facade() -> void: - if not st.facade_south: - return - var far_h := st.edge * FACADE_FAR_FRAC - var near_h := st.edge * FACADE_NEAR_FRAC - var y_far := st.edge - far_h - var y_near := y_far - near_h - var y_hl := y_near - FACADE_HIGHLIGHT_PX - draw_rect(Rect2(0.0, y_hl, st.edge, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点) - draw_rect(Rect2(0.0, y_near, st.edge, near_h), FACADE_NEAR_COL, true) - draw_rect(Rect2(0.0, y_far, st.edge, far_h), FACADE_FAR_COL, true) - - -# 地面投影(D 档件二——北邻墙格在本已开格北缘投渐隐暗带,自北缘向南三段衰减): -# 坐标推导:三等分 proj_band,α 0.28/0.16/0.06(研究件实测贴墙 α≈0.28、带末渐融入地面) -func _draw_north_projection() -> void: - if st.proj_band <= 0.0: - return - var seg := st.proj_band / 3.0 - draw_rect(Rect2(0.0, 0.0, st.edge, seg), PROJECTION_NEAR_COL, true) - draw_rect(Rect2(0.0, seg, st.edge, seg), PROJECTION_MID_COL, true) - draw_rect(Rect2(0.0, seg * 2.0, st.edge, seg), PROJECTION_EDGE_COL, true) - - -# E 格高墙面(圣经 v3.5 冻结①——方向稿 draw_facade 移植):本格已开且南邻为墙 → -# 南部 30% 承接墙面。材质 = 墙顶 center 瓦片顶部 30% 区带**原亮度**直贴(零暗档零阴影); -# 墙脚阴线只画地面/墙面交界(strip 顶缘 1px);墙面底缘(格界处)不画任何线——直接接南侧墙顶 -# (白边导致两截)。仅瓦片态服役(tex_facade 缺位 = 平色回退原语义不动)。 -func _draw_e_facade() -> void: - if not st.facade_strip or st.tex_facade == null: - return - var strip_h := st.edge * E_FACADE_FRAC - var y0 := st.edge - strip_h - var ts := st.tex_facade.get_size() - draw_texture_rect_region(st.tex_facade, Rect2(0.0, y0, st.edge, strip_h), - Rect2(0.0, 0.0, ts.x, ts.y * E_FACADE_FRAC)) - draw_rect(Rect2(0.0, y0, st.edge, 1.0), UiKit.E_FACADE_FOOT_COL, true) - - -# 文字位绘制:水平居中 + 垂直居中(基线 = (格高 − 字高)/2 + 上伸部) -func _draw_glyph(g: String, col: Color, fs: int) -> void: - var f := st.font if st.font != null else ThemeDB.fallback_font - if f == null: - return - var asc := f.get_ascent(fs) - var desc := f.get_descent(fs) - draw_string(f, Vector2(0.0, (st.edge - (asc + desc)) * 0.5 + asc), g, - HORIZONTAL_ALIGNMENT_CENTER, st.edge, fs, col) + face.draw_centered_text(self, rect, glyph, UiKit.FOG_MEMORY_INK, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源) # 微光子件生命周期:有组才建、组变即换、无组即撤——作用集 ≤8 格常驻, # 其余格零节点零绘制成本。与父件 _draw() 分离的三条理由见本件头注(命令序列/重绘预算/材质挂点)。 func _sync_glow_layer() -> void: - if st.glow_group == 0: + if glow_group == 0: if _glow_layer != null and is_instance_valid(_glow_layer): _glow_layer.queue_free() _glow_layer = null @@ -410,15 +96,5 @@ func _sync_glow_layer() -> void: if _glow_layer == null or not is_instance_valid(_glow_layer): _glow_layer = _GLOW_LAYER.new() add_child(_glow_layer) - _glow_layer.setup(st.edge, st.glow_group) - _glow_layer.set_badge_mask(st.badge_glyph != "") - - -# 徽章绘制:右下角金(文字贴角内缩 2–3px) -func _draw_badge(g: String) -> void: - var f := st.font if st.font != null else ThemeDB.fallback_font - if f == null: - return - var fs := maxi(10, int(st.edge * 0.28)) - var sz := f.get_string_size(g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs) - draw_string(f, Vector2(st.edge - sz.x - 2.0, st.edge - 3.0), g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs, UiKit.GOLD) + _glow_layer.setup(edge, glow_group) + _glow_layer.set_badge_mask(face.badge_glyph != "") diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 40297d4f..ea4502b8 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -182,7 +182,7 @@ func set_treasure_overlays(reveal_cells: Array, preview_cells: Array) -> void: func set_world_font(f: Font) -> void: _world.board_font = f for c in _cells: - _cells[c].st.font = f + _cells[c].face.font = f _cells[c].queue_redraw() @@ -201,7 +201,7 @@ func set_cell_area(avail: Vector2) -> void: var stride := _cell + float(SEP) for c in _cells: var u: Node2D = _cells[c] - u.st.edge = _cell + u.edge = _cell u.position = Vector2(c) * stride if _world.hero != null and _hero_sprite.last.x >= 0: _place_hero(_hero_sprite.last, _hero_sprite.last, true) # 格边长变更(窗口缩放)——英雄即时随格重置(杀位移 tween 防飞陈旧落点) @@ -255,17 +255,17 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void: # 逐格构建器(rebuild 建格循环唯一出函面):.new() 与属性装配逐字迁入,循环体只调构建器 func _build_cell_unit(c: Vector2i, stride: float) -> Node2D: var u: Node2D = _CELL_UNIT.new() - u.st.cell = c - u.st.edge = _cell + u.cell = c + u.edge = _cell u.position = Vector2(c) * stride - u.st.bevel = DEPTH_BEVEL # 新建格单元继承 + u.base.bevel = DEPTH_BEVEL # 新建格单元继承 u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化 if _palette.tiles_ok: # NEAREST 采样(32×32 源 × 45/36px 格边非整数缩放接受档, # 与英雄小人 1.5× 豁免同级(圣经 §8.2);只在瓦片 # 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化) u.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - u.st.font = _world.board_font + u.face.font = _world.board_font _world.add_child(u) return u @@ -514,21 +514,21 @@ func _apply_hover_visuals(c: Vector2i) -> void: var seal: bool = _minesweeper.has_seal(c) var adjacent: bool = _diggable.has(c) if revealed: - u.st.hl_visible = true - u.st.hl_color = COL_HL_TARGET # 移动目标格边框(金) + u.face.hl_visible = true + u.face.hl_color = COL_HL_TARGET # 移动目标格边框(金) _set_cursor_shape(c, CURSOR_MOVE_SEM) _show_path_preview(c) elif adjacent: - u.st.hl_visible = true - u.st.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖) + u.face.hl_visible = true + u.face.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖) _set_cursor_shape(c, CURSOR_DIG) elif seal: - u.st.hl_visible = true - u.st.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供) + u.face.hl_visible = true + u.face.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供) _set_cursor_shape(c, Input.CURSOR_FORBIDDEN) else: - u.st.hl_visible = true - u.st.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道) + u.face.hl_visible = true + u.face.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道) _set_cursor_shape(c, Input.CURSOR_FORBIDDEN) hover_forbidden.emit(c) @@ -536,7 +536,7 @@ func _apply_hover_visuals(c: Vector2i) -> void: func _clear_hover_visuals(c: Vector2i) -> void: if not _cells.has(c): return - _cells[c].st.hl_visible = false + _cells[c].face.hl_visible = false _hide_path_preview() @@ -551,12 +551,12 @@ func _show_path_preview(c: Vector2i) -> void: var path: Array[Variant] = _minesweeper.compute_move_path(c) for pc in path: if _cells.has(pc): - _cells[pc].st.dot_visible = true + _cells[pc].face.dot_visible = true func _hide_path_preview() -> void: for c in _cells: - _cells[c].st.dot_visible = false + _cells[c].face.dot_visible = false # ---- 奖励徽章(已开格身份 icon——封印之下、纹带之上、不进数字区) ---- @@ -613,19 +613,19 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: var is_hero: bool = c == hero # 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal # 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。 - u.st.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null + u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null if removed: revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结) seal = false - u.st.tex_seal = null + u.face.tex_seal = null # 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面 - #(u.st/u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载) + #(u.base/u.face 域件与 u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载) var bg: Color = _paint_cell_ink(c, u, revealed, seal) _paint_cell_base(c, u, revealed, is_hero, bg) _paint_cell_facade(c, u, revealed) _paint_cell_overlays(c, u, revealed) _paint_cell_fog(c, u, revealed, hero) - u.st.removed_dim = removed + u.face.removed_dim = removed u.queue_redraw() @@ -696,17 +696,17 @@ func _paint_cell_base(c: Vector2i, u: Node2D, revealed: bool, is_hero: bool, bg: # 贴图缺位回退字形「人」(项目纹理槽惯例——贴图在位=格内字形退役,单位本体由独立精灵承载) u.glyph = "人" u.glyph_color = COL_HERO - u.st.tex_base = tile - u.st.bg_sb = sb + u.base.tex_base = tile + u.base.bg_sb = sb # 立面投影段:未开墙格南缘立面/轮廓描边码;已开格北缘投影暗带/E 格高墙面——段内重置与赋值同序 func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void: - u.st.outline_sides.assign([]) # typed Array[int](assign 保持引用语义) - u.st.facade_south = false - u.st.proj_band = 0.0 - u.st.tex_facade = null - u.st.facade_strip = false + u.base.outline_sides.assign([]) # typed Array[int](assign 保持引用语义) + u.base.facade_south = false + u.base.proj_band = 0.0 + u.base.tex_facade = null + u.base.facade_strip = false # 封印槽不在此重置:尾部重置会与此上方条件赋值构成双写踩踏(把封印纹理恒清 null=素材在位时封印不可见); # 上方 else 分支已是全路径清残留,尾部重置系冗余且有害 var south_n: Vector2i = c + Vector2i(0, 1) @@ -716,26 +716,26 @@ func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void: if not revealed: # 高墙感 D 投影档:南邻地面格 → 墙格南缘立「口沿高光+双段暗带」立面 #(邻地面才画——墙-墙内界保墙群内聚);板外=墙不画 - u.st.facade_south = south_in and _minesweeper.is_revealed(south_n) + u.base.facade_south = south_in and _minesweeper.is_revealed(south_n) # 轮廓加强:墙格四邻中已开(地面)格朝向记描边码(板外=墙不描——保墙群外轮廓只在墙/地交界) for d in [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]: var n: Vector2i = c + d if n.x >= 0 and n.y >= 0 and n.x < _board.width and n.y < _board.height and _minesweeper.is_revealed(n): var code := 0 if d.y < 0 else (1 if d.y > 0 else (2 if d.x < 0 else 3)) - if code == 1 and u.st.facade_south: + if code == 1 and u.base.facade_south: continue # 南缘由立面件承载(高光+双段暗带)——2px 描边让位防三重叠黑 if code == 0 and _palette.tiles_ok: continue # v3.5 冻结① (b)-(c) 交界无边框:墙面直贴接墙顶(E 格墙面件在邻格承载)——北向描边不画(平色回退态保留描边) - u.st.outline_sides.append(code) + u.base.outline_sides.append(code) else: # 地面投影:北邻墙格在本已开格北缘投渐隐暗带(ITB 同款高度线索——带宽按格边比例常量) if north_in and not _minesweeper.is_revealed(north_n): - u.st.proj_band = u.st.edge * WALL_PROJECTION_FRAC + u.base.proj_band = u.edge * WALL_PROJECTION_FRAC # E 格高墙面(圣经 v3.5 冻结①——方向稿 is_facade_cell 口径):已开格南邻未开墙 → 南部 30% # 承接墙面(材质=同板墙顶 center 瓦片,原亮度直贴);仅瓦片态启用——平色回退保持原语义 if south_in and not _minesweeper.is_revealed(south_n) and _palette.tiles_ok: - u.st.facade_strip = true - u.st.tex_facade = _palette.tiles.get("wall_center") + u.base.facade_strip = true + u.base.tex_facade = _palette.tiles.get("wall_center") # 秘宝/变异叠加段:真视红框/预读徽章/变异角标/奖励徽章/类型微光组号——glyph/digit_col 局部镜像 @@ -746,21 +746,21 @@ func _paint_cell_overlays(c: Vector2i, u: Node2D, revealed: bool) -> void: var glyph: String = u.glyph var digit_col: Color = u.glyph_color if not revealed: - u.st.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else "" + u.face.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else "" if _treasure_preview.has(c) and glyph == "": var pid = _identity_cb.call(c) if _identity_cb.is_valid() else null if pid != null and REWARD_BADGE.has(String(pid.get("cell_type", ""))): glyph = String(REWARD_BADGE[String(pid.get("cell_type", ""))]) digit_col = COL_GOLD - u.st.badge_glyph = glyph + u.face.badge_glyph = glyph # ②' 变异角标:未开格带伤害型变异 → 危险红角标(已开=威胁已兑现标记退位); # 回调缺位 = 无标记(程序化角标,tex_mutation 纹带槽暂留) if not revealed and _vmark_cb.is_valid(): - u.st.mutation_mark = String(_vmark_cb.call(c)) + u.face.mutation_mark = String(_vmark_cb.call(c)) else: - u.st.mutation_mark = "" - u.st.badge_glyph = _update_badge(c) - u.st.glow_group = int(_glow.get(c, UiKit.GLOW_NONE)) + u.face.mutation_mark = "" + u.face.badge_glyph = _update_badge(c) + u.glow_group = int(_glow.get(c, UiKit.GLOW_NONE)) # 迷雾段:灯光区外未开格全黑/已开格记忆态灰显——纯呈现零逻辑(🔴 契约 1) @@ -803,7 +803,7 @@ func _sync_glow_to_units() -> void: for c in touched: var u: Node2D = _cells.get(c) if u != null: - u.st.glow_group = int(_glow.get(c, UiKit.GLOW_NONE)) + u.glow_group = int(_glow.get(c, UiKit.GLOW_NONE)) func _process(delta: float) -> void: @@ -815,14 +815,14 @@ func _process(delta: float) -> void: if UiKit.motion_scale < 1.0: return # 减动效:呼吸停格(refresh/enable 时已施静态色——静态可辨、无持续运动) var k := 0.5 + 0.5 * sin(_breath_t * 3.0) # 3 rad/s≈0.48Hz——无 >3 次/秒频闪(AC 基线) - _contact_unit.st.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) + _contact_unit.base.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) _contact_unit.queue_redraw() # 带几何写入(refresh / set_dig_glow_enabled(true) 挂点):清旧承载件→按英雄格可挖域写边/角码→施色 func _apply_contact_bands() -> void: if _contact_unit != null and is_instance_valid(_contact_unit) and (_cells.get(_minesweeper.hero_pos()) != _contact_unit if _minesweeper != null else true): - _contact_unit.st.contact_col = Color(0, 0, 0, 0) + _contact_unit.base.contact_col = Color(0, 0, 0, 0) _contact_unit.queue_redraw() _contact_unit = null if _board == null or _minesweeper == null or not _dig_glow: @@ -839,19 +839,19 @@ func _apply_contact_bands() -> void: for d in [Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)]: if _diggable.has(_minesweeper.hero_pos() + d): corners.append(0 if (d.x < 0 and d.y < 0) else (1 if d.x > 0 and d.y < 0 else (2 if d.x < 0 else 3))) - hu.st.contact_bands = bands - hu.st.contact_corners = corners + hu.base.contact_bands = bands + hu.base.contact_corners = corners # 减动效:呼吸停格——refresh/enable 重放时施**静态**色(本件头注「已施静态色」的落実); # 0.5 = 呼吸周期中值(正常态 k 的均值)。否则减动效下每次 refresh 都会按当前相位重施色, # 带色在 refresh 间跳变=持续运动假象,且令 on/off 双帧取证无法隔离单一变量。 var k := 0.5 if UiKit.motion_scale < 1.0 else 0.5 + 0.5 * sin(_breath_t * 3.0) - hu.st.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) + hu.base.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) hu.queue_redraw() func _reset_breath() -> void: if _contact_unit != null and is_instance_valid(_contact_unit): - _contact_unit.st.contact_col = Color(0, 0, 0, 0) + _contact_unit.base.contact_col = Color(0, 0, 0, 0) _contact_unit.queue_redraw() @@ -926,5 +926,5 @@ func cell_rect_probe(c: Vector2i) -> Rect2: if host.size.x <= 0.0 or host.size.y <= 0.0 or vp_size.x <= 0.0 or vp_size.y <= 0.0: return Rect2() var k: Vector2 = host.size / vp_size - var local := Rect2(u.global_position, Vector2(u.st.edge, u.st.edge)) + var local := Rect2(u.global_position, Vector2(u.edge, u.edge)) return Rect2(host.position + local.position * k, local.size * k) diff --git a/game/presentation/views/cell_base_layer.gd b/game/presentation/views/cell_base_layer.gd new file mode 100644 index 00000000..c7465049 --- /dev/null +++ b/game/presentation/views/cell_base_layer.gd @@ -0,0 +1,204 @@ +## VERTICAL SLICE - NOT FOR PRODUCTION +## 格面基座层绘制域件(CellUnit 拆件——单一变化原因=基座层视觉规格): +## 底面/描边/高墙立面/地面投影/接触带微光/顶面倒角的全部绘制入参与绘制命令。 +## 无 class_name(类名缓存面纪律)——经宿主 board_cell_unit preload 引用;宿主 _draw 按层序调 +## draw_base(canvas, rect) 把绘制命令落到宿主画布;setter 经 _owner 回引触发宿主重绘语义逐字。 +extends RefCounted + +# ── D 投影档绘制常量(研究件实测取色——立面高光/双段暗带/地面投影;黑罩 alpha 叠加实测等效)── +const FACADE_HIGHLIGHT_PX := 2.0 # 口沿高光线厚(像素) +const FACADE_NEAR_FRAC := 0.16 # 立面近段高(格边比——研究件 10/64) +const FACADE_FAR_FRAC := 0.25 # 立面远段高(格边比——研究件 16/64) +const FACADE_NEAR_COL := Color(0, 0, 0, 0.60) # 立面近段黑罩(墙顶 132→52 实测 α≈0.61) +const FACADE_FAR_COL := Color(0, 0, 0, 0.82) # 立面远段黑罩(墙顶 132→24 实测 α≈0.82) +const PROJECTION_NEAR_COL := Color(0, 0, 0, 0.28) # 投影贴墙段(地面 66→46 实测 α≈0.28) +const PROJECTION_MID_COL := Color(0, 0, 0, 0.16) # 投影中段(渐隐) +const PROJECTION_EDGE_COL := Color(0, 0, 0, 0.06) # 投影远段(渐隐收边) +# ── E 格高墙面(圣经 v3.5 冻结①——地面格南邻墙→南部 30% 承接墙面:墙顶纹理原亮度 +# 直贴零暗档零阴影;墙脚阴线只画地面/墙面交界;(b)-(c) 交界 +# 无边框由 BoardView 侧跳过墙格北向描边承载)── +const E_FACADE_FRAC := 0.30 # 墙面承接高(格边比 30%) + +var _owner: Node2D = null +var bg_sb: StyleBoxFlat = null # 格底 StyleBox(BoardView 每次 paint 重建;呼吸微光就地改边框) +# ── 接触带微光绘制面(可挖域微光画在英雄格周界空间侧——art-bible §3.2 v2.8; +# 仅英雄格单元承载,BoardView.refresh 按可挖域写边/角码、_process 逐拍更新呼吸色)── +var contact_bands: Array[int] = [] # 正交接触边码 0=N/1=S/2=W/3=E(该侧整条边) +var contact_corners: Array[int] = [] # 斜向共享角码 0=NW/1=NE/2=SW/3=SE(角点+两侧各半条边) +var contact_col := Color(0, 0, 0, 0) # 当前呼吸色(alpha 0 = 不画) +var outline_sides: Array[int] = [] # 墙格地面朝向描边码 0=N/1=S/2=W/3=E(程序侧结构件) +# ── 高墙感 D 投影档(研究件四变体定 D=C+地面投影,ITB 同款高度线索)── +# 南立面 = 墙格南缘内侧「口沿高光线 + 双段暗带」(邻地面格才画,墙群内聚);地面投影 = 北邻墙格 +# 在本已开格北缘投渐隐暗带。参数按研究件 64px 拼版实测比例换算(立面双段 0.16/0.25 格边、投影带 +# 0.22 格边;坐标推导留档 commit)。素材缺位程序化回退同样服役(结构归程序——art-bible §8.5)。 +var facade_south := false: # 南立面开关(BoardView paint 按「南邻已开格」写入) + set(v): + facade_south = v + _owner.queue_redraw() +var proj_band := 0.0: # 地面投影带宽(像素,0 = 不画——BoardView 按北邻墙判定写入) + set(v): + proj_band = v + _owner.queue_redraw() +var bevel := 0.0: # 顶面倒角强度(0=关;档位 0/0.35/0.6/1.0) + set(v): + bevel = v + _owner.queue_redraw() +var tex_base: Texture2D = null # ① 砖面底(宿主迷雾记忆态公开单向读) +var tex_facade: Texture2D = null # ⑥ E 格墙面材质(v3.5——南邻墙顶 center 瓦片同源,BoardView 注入) +var facade_strip := false: # E 格开关(BoardView paint 按「已开格且南邻未开墙」写入——瓦片态专用) + set(v): + facade_strip = v + _owner.queue_redraw() + + +func _init(owner: Node2D) -> void: + _owner = owner + + +## ① 砖面底层域(占位 = StyleBoxFlat:格底色 + 暗化描边 + 3px 圆角;瓦片态描边覆盖层; +## 呼吸微光/主角蓝框由 BoardView 改 bg_sb 后触发重绘。素材缺位回退走 elif 程序化原样,零观感变化) +func draw_base(canvas: CanvasItem, rect: Rect2) -> void: + if tex_base != null: + canvas.draw_texture_rect(tex_base, rect, false) + # 轮廓加强(轮廓线不明显):墙格地面朝向边补 2px 深墨线(§8.5 结构归程序—— + # 素材 1px 墨线在 45/36px 非整数缩放下视觉衰减,程序线保底;墙-墙内界不画,保墙群内聚; + # 坐标推导:各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合) + if not outline_sides.is_empty(): + _draw_outline_sides(canvas, rect) + _draw_south_facade(canvas, rect) + _draw_north_projection(canvas, rect) + _draw_e_facade(canvas, rect) + # 瓦片态描边覆盖层:tex_base 在位时 BoardView 传入的 bg_sb + # 已降级为「透明底 + 描边」载体——呼吸微光/主角蓝框画在瓦片之上(art-bible §1.2 叠层序 + # 不变:底 < 纹带 < 数字 < 封印 < 焦点高亮);素材缺位回退走 elif 程序化原样,零观感变化 + if bg_sb != null: + canvas.draw_style_box(bg_sb, rect) + _draw_contact_or_fallback(canvas, rect) + + +## 接触带微光或平色回退域(英雄格周界空间侧——正交邻墙=整条边;斜向=共享角点+两侧各半条边;光不进墙格): +## 带宽 4px 贴格边内侧(坐标推导:N=[0,0,edge,4] S=[0,edge-4,edge,4] W=[0,0,4,edge] E=[edge-4,0,4,edge]; +## 半条边=沿邻边自角点起 edge/2;角点圆 r=6 贴角点圆心,近白芯 r=2.5 提亮) +func _draw_contact_or_fallback(canvas: CanvasItem, rect: Rect2) -> void: + if contact_col.a > 0.0: + _draw_north_projection(canvas, rect) # 平色回退+微光并发的英雄格补画(瓦片路径已画——同像素重复无观感差) + _draw_e_facade(canvas, rect) # E 格墙面补画(同上——英雄格兼 E 格时瓦片路径已画,重复无害) + _draw_contact_bands(canvas, rect) + elif bg_sb != null: + canvas.draw_style_box(bg_sb, rect) + _draw_south_facade(canvas, rect) # 平色回退路径——D 投影档两件同样服役(结构归程序) + _draw_north_projection(canvas, rect) + _draw_bevel(canvas, rect) + + +## 墙格轮廓域:地面朝向边补 2px 深墨线(各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合) +func _draw_outline_sides(canvas: CanvasItem, rect: Rect2) -> void: + var ow := 2.0 + var oc := UiKit.FOG_OUTLINE + for b in outline_sides: + var or_ := Rect2() + match int(b): + 0: or_ = Rect2(0, 0, rect.size.x, ow) + 1: or_ = Rect2(0, rect.size.x - ow, rect.size.x, ow) + 2: or_ = Rect2(0, 0, ow, rect.size.x) + 3: or_ = Rect2(rect.size.x - ow, 0, ow, rect.size.x) + canvas.draw_rect(or_, oc, true) + + +## 接触带微光域:正交整条边 + 斜向共享角点与两侧半条边 +func _draw_contact_bands(canvas: CanvasItem, rect: Rect2) -> void: + var band := 4.0 + var half := rect.size.x * 0.5 + for b in contact_bands: + var r := Rect2() + match int(b): + 0: r = Rect2(0, 0, rect.size.x, band) + 1: r = Rect2(0, rect.size.x - band, rect.size.x, band) + 2: r = Rect2(0, 0, band, rect.size.x) + 3: r = Rect2(rect.size.x - band, 0, band, rect.size.x) + canvas.draw_rect(r, contact_col, true) + for cc in contact_corners: + _draw_contact_corner(canvas, rect, int(cc), band, half) + + +## 斜向角点域:角点圆 r=6 贴角点圆心 + 近白芯 r=2.5 提亮 + 半条边(角点沿两条邻边各延伸 edge/2—— +## 坐标推导:横带沿 N/S 自角点向板心侧 half;NW/SE 半带 x=y=角点坐标,NE/SE 半带自 edge-half 收进—— +## 正交整边时重叠同色无害) +func _draw_contact_corner(canvas: CanvasItem, rect: Rect2, cc: int, band: float, half: float) -> void: + var pt := Vector2.ZERO + match cc: + 0: pt = Vector2(0, 0) + 1: pt = Vector2(rect.size.x, 0) + 2: pt = Vector2(0, rect.size.x) + 3: pt = Vector2(rect.size.x, rect.size.x) + canvas.draw_circle(pt, 6.0, contact_col) + canvas.draw_circle(pt, 2.5, Color(1, 1, 1, minf(1.0, contact_col.a * 0.95))) + if cc == 0 or cc == 1: + # N 侧横带:NW 自 x=0、NE 自 x=edge-half + canvas.draw_rect(Rect2(pt.x if cc == 0 else pt.x - half, 0.0, half, band), contact_col, true) + if cc == 2 or cc == 3: + # S 侧横带:SW 自 x=0、SE 自 x=edge-half + canvas.draw_rect(Rect2(pt.x if cc == 2 else pt.x - half, rect.size.x - band, half, band), contact_col, true) + if cc == 0 or cc == 2: + # W 侧竖带:NW 自 y=0、SW 自 y=edge-half + canvas.draw_rect(Rect2(0.0, pt.y if cc == 0 else pt.y - half, band, half), contact_col, true) + if cc == 1 or cc == 3: + # E 侧竖带:NE 自 y=0、SE 自 y=edge-half + canvas.draw_rect(Rect2(rect.size.x - band, pt.y if cc == 1 else pt.y - half, band, half), contact_col, true) + + +## 顶面倒角域(光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让 +## 呼吸微光/主角蓝边框保持最外沿可见;强度随档位) +func _draw_bevel(canvas: CanvasItem, rect: Rect2) -> void: + if bevel <= 0.0: + return + var base: Color = bg_sb.bg_color + var lit: Color = base.lightened(0.22 * bevel) + var dark: Color = base.darkened(0.22 * bevel) + var t := 2.0 + canvas.draw_rect(Rect2(t, t, rect.size.x - t * 2.0, t), lit, true) + canvas.draw_rect(Rect2(t, t, t, rect.size.x - t * 2.0), lit, true) + canvas.draw_rect(Rect2(t, rect.size.x - t * 2.0, rect.size.x - t * 2.0, t), dark, true) + canvas.draw_rect(Rect2(rect.size.x - t * 2.0, t, t, rect.size.x - t * 2.0), dark, true) + + +# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿): +# 坐标推导(自格底边起算):远段=[edge−far, edge],近段=[edge−far−near, edge−far], +# 高光线=[edge−far−near−hl, edge−far−near]——与研究件「墙顶面→高光→暗段→更暗段→格界」下行序一致 +func _draw_south_facade(canvas: CanvasItem, rect: Rect2) -> void: + if not facade_south: + return + var far_h := rect.size.x * FACADE_FAR_FRAC + var near_h := rect.size.x * FACADE_NEAR_FRAC + var y_far := rect.size.x - far_h + var y_near := y_far - near_h + var y_hl := y_near - FACADE_HIGHLIGHT_PX + canvas.draw_rect(Rect2(0.0, y_hl, rect.size.x, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点) + canvas.draw_rect(Rect2(0.0, y_near, rect.size.x, near_h), FACADE_NEAR_COL, true) + canvas.draw_rect(Rect2(0.0, y_far, rect.size.x, far_h), FACADE_FAR_COL, true) + + +# 地面投影(D 档件二——北邻墙格在本已开格北缘投渐隐暗带,自北缘向南三段衰减): +# 坐标推导:三等分 proj_band,α 0.28/0.16/0.06(研究件实测贴墙 α≈0.28、带末渐融入地面) +func _draw_north_projection(canvas: CanvasItem, rect: Rect2) -> void: + if proj_band <= 0.0: + return + var seg := proj_band / 3.0 + canvas.draw_rect(Rect2(0.0, 0.0, rect.size.x, seg), PROJECTION_NEAR_COL, true) + canvas.draw_rect(Rect2(0.0, seg, rect.size.x, seg), PROJECTION_MID_COL, true) + canvas.draw_rect(Rect2(0.0, seg * 2.0, rect.size.x, seg), PROJECTION_EDGE_COL, true) + + +# E 格高墙面(圣经 v3.5 冻结①——方向稿 draw_facade 移植):本格已开且南邻为墙 → +# 南部 30% 承接墙面。材质 = 墙顶 center 瓦片顶部 30% 区带**原亮度**直贴(零暗档零阴影); +# 墙脚阴线只画地面/墙面交界(strip 顶缘 1px);墙面底缘(格界处)不画任何线——直接接南侧墙顶 +# (白边导致两截)。仅瓦片态服役(tex_facade 缺位 = 平色回退原语义不动)。 +func _draw_e_facade(canvas: CanvasItem, rect: Rect2) -> void: + if not facade_strip or tex_facade == null: + return + var strip_h := rect.size.x * E_FACADE_FRAC + var y0 := rect.size.x - strip_h + var ts := tex_facade.get_size() + canvas.draw_texture_rect_region(tex_facade, Rect2(0.0, y0, rect.size.x, strip_h), + Rect2(0.0, 0.0, ts.x, ts.y * E_FACADE_FRAC)) + canvas.draw_rect(Rect2(0.0, y0, rect.size.x, 1.0), UiKit.E_FACADE_FOOT_COL, true) diff --git a/game/presentation/views/cell_base_layer.gd.uid b/game/presentation/views/cell_base_layer.gd.uid new file mode 100644 index 00000000..e92421ae --- /dev/null +++ b/game/presentation/views/cell_base_layer.gd.uid @@ -0,0 +1 @@ +uid://c2msg115y3oir diff --git a/game/presentation/views/cell_mark_overlay.gd b/game/presentation/views/cell_mark_overlay.gd new file mode 100644 index 00000000..b6427c72 --- /dev/null +++ b/game/presentation/views/cell_mark_overlay.gd @@ -0,0 +1,145 @@ +## VERTICAL SLICE - NOT FOR PRODUCTION +## 格面标识与顶层叠加绘制域件(CellUnit 拆件——单一变化原因=标识与叠加层视觉规格): +## 变异标记/数字色牌与字形/奖励徽章/封印/路径点迹/焦点高亮/中立移除退色的全部绘制入参与绘制命令。 +## 无 class_name(类名缓存面纪律)——经宿主 board_cell_unit preload 引用;宿主 _draw 按层序调 +## draw_marks/draw_glyph_layer/draw_overlay_under/draw_removed_dim(雾层由宿主夹在 overlay_under 与 removed_dim 之间); +## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 回引 _owner._glow_layer 原样)。 +extends RefCounted + +var _owner: Node2D = null +var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区) + set(v): + badge_glyph = v + if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer): + _owner._glow_layer.set_badge_mask(v != "") + _owner.queue_redraw() +var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝) + set(v): + dot_visible = v + _owner.queue_redraw() +var hl_visible := false: # 焦点高亮(悬停态边框覆盖层) + set(v): + hl_visible = v + _owner.queue_redraw() +var hl_color := UiKit.GOLD: # 高亮色(金目标/封印朱/禁入灰禁红) + set(v): + hl_color = v + _owner.queue_redraw() +var font: Font = null # 字体槽位(null = ThemeDB.fallback_font——BoardView.set_world_font 注入) +# ── 素材槽位(现全 null = 程序化占位服役;art-bible §8.2 格面 32×32 暂定档)── +var tex_mutation: Texture2D = null # ② 变异纹带 +var tex_glyph: Texture2D = null # ③ 数字 +var tex_badge: Texture2D = null # ③' 徽章 +var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark + set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示)) + if tex_seal != v: + tex_seal = v + _owner.queue_redraw() +# 变异角标(程序化,tex_mutation 纹带槽暂留): +# "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记) +# 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写): +# "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,封印照常计费); +# "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形) +var treasure_mark := "": + set(v): + if treasure_mark != v: + treasure_mark = v + _owner.queue_redraw() +var mutation_mark := "": + set(v): + if mutation_mark != v: + mutation_mark = v + _owner.queue_redraw() +var removed_dim := false: # 中立移除退色层(BoardView paint 按排雷玩法 is_removed 写入) + set(v): + removed_dim = v + _owner.queue_redraw() + + +func _init(owner: Node2D) -> void: + _owner = owner + + +## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角 +func draw_marks(canvas: CanvasItem, rect: Rect2) -> void: + if treasure_mark == "mine_reveal": + var inset := rect.size.x * 0.14 + canvas.draw_rect(Rect2(inset, inset, rect.size.x - inset * 2.0, rect.size.x - inset * 2.0), + Color(UiKit.RED_MINE, 0.9), false, 2.0) + if mutation_mark == "flat_damage": + var mw := rect.size.x * 0.28 + var mpts := PackedVector2Array([Vector2(0, rect.size.x - mw), Vector2(0, rect.size.x), Vector2(mw, rect.size.x)]) + canvas.draw_colored_polygon(mpts, Color(UiKit.RED_MINE, 0.85)) + + +## ②③④ 中层域:变异纹带与标记/数字色牌与字形/徽章/封印 +##(glyph/glyph_color/plate 三者住宿主冻结面——探针直读成员,作参数传入) +func draw_glyph_layer(canvas: CanvasItem, rect: Rect2, glyph: String, glyph_color: Color, plate: bool) -> void: + # ② 变异纹带(无程序化纹带——槽位先行,贴图落位即用) + if tex_mutation != null: + canvas.draw_texture_rect(tex_mutation, rect, false) + # ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?") + if glyph != "": + if plate: + _draw_digit_plate(canvas, rect) + if tex_glyph != null: + canvas.draw_texture_rect(tex_glyph, rect, false) + else: + draw_centered_text(canvas, rect, glyph, glyph_color, maxi(18, int(ceil(rect.size.x * 0.5)))) # 字号下限 18(§7.4 冻结公式) + if badge_glyph != "": + if tex_badge != null: + canvas.draw_texture_rect(tex_badge, rect, false) + else: + _draw_badge(canvas, rect, badge_glyph) + # ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图) + if tex_seal != null: + canvas.draw_texture_rect(tex_seal, rect, false) + + +## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌; +## 牌=后备件——雷/「?」维持牌载保读性(牌上深墨族 12:1); +## 牌尺寸 0.60/0.62 格边居中;封印=未开格不垫牌) +func _draw_digit_plate(canvas: CanvasItem, rect: Rect2) -> void: + var pw := rect.size.x * 0.60 + var ph := rect.size.x * 0.62 + var pr := Rect2((rect.size.x - pw) * 0.5, (rect.size.x - ph) * 0.5, pw, ph) + canvas.draw_rect(pr, UiKit.DIGIT_PLATE, true) + canvas.draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0) + + +## ⑤ 路径点迹 + 焦点高亮(悬停态覆盖层,不参与命中)——宿主时序器在其后插雾层 +func draw_overlay_under(canvas: CanvasItem, rect: Rect2) -> void: + if dot_visible: + var ds := 6.0 + canvas.draw_rect(Rect2(Vector2((rect.size.x - ds) * 0.5, (rect.size.x - ds) * 0.5), Vector2(ds, ds)), UiKit.HERO_BLUE, true) + if hl_visible: + # 悬停高亮减淡(金框太明显——绘制时 55% alpha,hl_color 属性与探针断言面不动) + canvas.draw_rect(Rect2(1, 1, rect.size.x - 2.0, rect.size.x - 2.0), Color(hl_color, 0.55), false, 2.0) # 2px 内描边 + + +## ⑦ 中立移除退色层(退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义; +## 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示危险格(无数字无封印印)) +func draw_removed_dim(canvas: CanvasItem, rect: Rect2) -> void: + if removed_dim: + canvas.draw_rect(rect, Color(0.12, 0.10, 0.09, 0.62), true) + + +## 文字位绘制单点(宿主迷雾记忆态复用——同公式同字号;水平居中 + 垂直居中,基线 = (格高 − 字高)/2 + 上伸部) +func draw_centered_text(canvas: CanvasItem, rect: Rect2, text: String, col: Color, fs: int) -> void: + var f := font if font != null else ThemeDB.fallback_font + if f == null: + return + var asc := f.get_ascent(fs) + var desc := f.get_descent(fs) + canvas.draw_string(f, Vector2(0.0, (rect.size.x - (asc + desc)) * 0.5 + asc), text, + HORIZONTAL_ALIGNMENT_CENTER, rect.size.x, fs, col) + + +# 徽章绘制:右下角金(文字贴角内缩 2–3px) +func _draw_badge(canvas: CanvasItem, rect: Rect2, g: String) -> void: + var f := font if font != null else ThemeDB.fallback_font + if f == null: + return + var fs := maxi(10, int(rect.size.x * 0.28)) + var sz := f.get_string_size(g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs) + canvas.draw_string(f, Vector2(rect.size.x - sz.x - 2.0, rect.size.x - 3.0), g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs, UiKit.GOLD) diff --git a/game/presentation/views/cell_mark_overlay.gd.uid b/game/presentation/views/cell_mark_overlay.gd.uid new file mode 100644 index 00000000..0eb0403e --- /dev/null +++ b/game/presentation/views/cell_mark_overlay.gd.uid @@ -0,0 +1 @@ +uid://c60e3su2qd1yq diff --git a/game/presentation/views/standee_body.gd b/game/presentation/views/standee_body.gd index b82c32a7..a5a34489 100644 --- a/game/presentation/views/standee_body.gd +++ b/game/presentation/views/standee_body.gd @@ -2,9 +2,9 @@ ## 站桩本体:B 区主角/敌人无框立绘+底座+血条+顺位/意图徽标的世界精灵(Node2D)。 ## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。 ## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。 -## 内部状态件(u4 子域拆件——契约 9 职责语句「站桩演出状态」):单一变化原因=站桩呈现与演出, -## 成员共同表达站桩的行进态/演出计数/资产挂点。外部直访冻结成员(box/font/slot_row/has_world_hp_bar) -## 与源码锚承载成员(_intent_charge——theme 源锚句所在行逐字保留)住宿主。 +## 内部状态两件(u4 拆件——st=身份命值态 standee_vitals、mo=资产演出态 standee_motion,_init 自建); +## 外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员(_intent_charge——theme +## 源锚句所在行逐字保留)与三个演出探针计数器住宿主。 extends Node2D const CHARGE_SKILL_IDS := ["heavy"] @@ -16,65 +16,28 @@ const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"} const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值) const HIT_SPARK_N := 6 # 命中火花数(打击瞬间可读性一拍) +const _VITALS := preload("res://presentation/views/standee_vitals.gd") +const _MOTION := preload("res://presentation/views/standee_motion.gd") -## 站桩演出状态(全部内部态单点——含 setter 触发宿主重绘语义,_owner 回引保持逐字) -class _StandeeState extends RefCounted: - var _owner: Node2D = null - var _kind := "enemy" - var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位) - var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据) - # 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理 - # (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。 - var _hero_tex := {} - var _hero_state: int = 0 - var _state_revert_tw: Tween - var _order := 1 - var _intent_glyph := "" - var _intent_col := Color.WHITE - var _intent_visible := false - var _intent_scale := 1.0: # 意图弹入动画参数 - set(v): - _intent_scale = v - _owner.queue_redraw() - var _glyph_rot := 0.0: # 立绘前倾角(贴图态无此动画) - set(v): - _glyph_rot = v - _owner.queue_redraw() - var _display_name := "" - var _enemy_id := "" - var _armor := "light" - var _dead := false - var _hp_max := 1.0 - var _hp_cur := 0 # 血量数字显示面(HUD框架 验收「血量数字可读」) - var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积) - set(v): - hp_value = clampf(v, 0.0, 1.0) - _owner.queue_redraw() - var _hp_tween: Tween - var _hp_flash_tw: Tween - var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数) - var _hit_sparks := 0 # 探针出口计数(命中火花累计) - var _flash_count := 0 # 探针出口计数(受击闪白触发次数——主角路由断言面) - - -var st := _StandeeState.new() +var st: _VITALS # 身份与命值态件实例(名字沿用——外部路径串 "st.hp_value" 与约 40 处引用零改) +var mo: _MOTION # 资产与演出态件实例(_init 自建) var box := Vector2(150, 176) # 站桩盒尺寸(实际高随壳层 row 布局注入;外部直访冻结) var font: Font = null # 世界字体槽位(null = ThemeDB.fallback_font——world.world_font 注入;外部直访冻结) var slot_row: StatusSlotRow # 状态效果唯一展示面 = 壳层伴随件(挂 BattleStageView 壳层,本体进世界;外部直访冻结) var _intent_charge := false # 蓄力档(pid ∈ CHARGE_SKILL_IDS——意图三档 ③ 分支判据;源码锚承载成员住宿主) # 血条存在性探针出口(世界直角血条常在;外部直访冻结) var has_world_hp_bar := true +# 演出探针计数器(写入方=宿主演出函数、读取方=宿主 fx_probe 出口——仪器跟着驱动它的函数走) +var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数) +var _hit_sparks := 0 # 探针出口计数(命中火花累计) +var _flash_count := 0 # 探针出口计数(受击闪白触发次数——主角路由断言面) func _init(kind: String = "enemy") -> void: - st._owner = self - st._hero_state = HeroState.IDLE + st = _VITALS.new(self) + mo = _MOTION.new(self) st._kind = kind - - -enum HeroState { IDLE, RESET, ATTACK, HIT } # 主角三态+复位哨兵(外部 String 契约=register/set 形参零变化) -const _HS_NAME := {HeroState.IDLE: "idle", HeroState.RESET: "reset", HeroState.ATTACK: "attack", HeroState.HIT: "hit"} -const _HS_VALUE := {"idle": HeroState.IDLE, "reset": HeroState.RESET, "attack": HeroState.ATTACK, "hit": HeroState.HIT} + mo._hero_state = _MOTION.HeroState.IDLE func _efont() -> Font: @@ -92,7 +55,7 @@ func enemy_id() -> String: # 意图徽标探针出口 func intent_probe() -> Dictionary: - return {"visible": st._intent_visible, "text": st._intent_glyph} + return {"visible": mo._intent_visible, "text": mo._intent_glyph} # 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、 @@ -118,7 +81,7 @@ func set_data(d: Dictionary) -> void: st._enemy_id = String(d.get("enemy_id", "")) st._armor = String(d.get("armor_type", "light")) st._hp_max = float(maxi(1, int(d.get("max_hp", 1)))) - _set_hp(int(d.get("hp", 0))) + st.apply_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", [])) @@ -129,21 +92,11 @@ func set_data(d: Dictionary) -> void: func set_hero(hp: int, max_hp: int) -> void: st._hp_max = float(maxi(1, max_hp)) - _set_hp(hp) + st.apply_hp(hp) if st._dead and hp > 0: _revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义) -# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积 -func _set_hp(hp: int) -> void: - st._hp_cur = clampi(hp, 0, int(st._hp_max)) - var target := float(st._hp_cur) / st._hp_max - if st._hp_tween != null and st._hp_tween.is_valid(): - st._hp_tween.kill() - st._hp_tween = create_tween() - st._hp_tween.tween_property(st, "hp_value", target, UiKit.motion(0.15)) - - # 主角/敌方共用——状态效果唯一展示面(壳层伴随件转发;空态 = 图标行隐藏不占位) func set_effects(effects: Array) -> void: if slot_row == null: @@ -182,12 +135,12 @@ func _set_order(i: int) -> void: func set_pending(ptype: String, pid: String) -> void: if st._dead: _intent_charge = false - st._intent_visible = false + mo._intent_visible = false queue_redraw() return if ptype == "" or ptype == "basic": _intent_charge = false - st._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4) + mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4) queue_redraw() return var glyph := "⚔" # 攻击系(damage) @@ -201,60 +154,15 @@ func set_pending(ptype: String, pid: String) -> void: _intent_charge = pid in CHARGE_SKILL_IDS if _intent_charge: col = UiKit.GOLD_INK # 重击类蓄力档(深金墨——浅垫牌上 ≥4.5:1) - st._intent_glyph = glyph - st._intent_col = col - st._intent_visible = true - _play_intent_motion() + mo._intent_glyph = glyph + mo._intent_col = col + mo._intent_visible = true + mo.play_intent_motion(_intent_charge) -# 意图三档动作动画(§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕 -# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落; -# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。 -func _play_intent_motion() -> void: - var tw := create_tween() - tw.tween_property(st, "_intent_scale", 1.6, UiKit.motion(0.10)) - tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.16)) - tw.parallel().tween_property(st, "_glyph_rot", 4.0, UiKit.motion(0.10)) - tw.tween_property(st, "_glyph_rot", 0.0, UiKit.motion(0.14)) - if _intent_charge: - tw.tween_property(st, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推 - tw.tween_interval(UiKit.motion(0.40)) # 定格微顿 - tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.18)) - else: - tw.tween_property(st, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举) - tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.13)) - - -# 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。 -# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2× -# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。 +# 资产挂点转调壳(签名外形零变——box 由宿主传入件内) func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void: - if tex == null: - if st._portrait_sprite != null: - st._portrait_sprite.visible = false - queue_redraw() - return - st._portrait_tex = tex - if st._portrait_sprite == null: - st._portrait_sprite = Sprite2D.new() - st._portrait_sprite.centered = false - st._portrait_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊 - add_child(st._portrait_sprite) - st._portrait_sprite.texture = tex - st._portrait_sprite.flip_h = flip - st._portrait_sprite.visible = true - _place_portrait() - - -func _place_portrait() -> void: - # 立绘盒 = (0,44)-(box.x, box.y-36);48 源 ×2 整数档居中 - if st._portrait_sprite == null or st._portrait_tex == null: - return - var box_h := box.y - 44.0 - 36.0 - var w := st._portrait_tex.get_width() * 2.0 - var h := st._portrait_tex.get_height() * 2.0 - st._portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5) - st._portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - h) * 0.5) + mo.set_portrait_texture(tex, flip, box) func mark_dead() -> void: @@ -268,8 +176,8 @@ func mark_dead() -> void: tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08)) _spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s) _intent_charge = false - st._intent_visible = false # 死亡残位意图退位 - _set_hp(0) + mo._intent_visible = false # 死亡残位意图退位 + st.apply_hp(0) if slot_row != null: slot_row.visible = false # 状态行随 mark_dead 同拍隐藏 @@ -279,7 +187,7 @@ func mark_dead() -> void: # 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机) func _spawn_death_shards() -> void: - st._death_shards = DEATH_SHARD_N + _death_shards = DEATH_SHARD_N var center := _portrait_rect().get_center() for i in DEATH_SHARD_N: _spawn_death_shard(i, center) @@ -305,7 +213,7 @@ func _spawn_death_shard(i: int, center: Vector2) -> void: # 演出探针出口(fx_probe——死亡碎裂/命中火花/闪白计数断言面) func fx_probe() -> Dictionary: - return {"death_shards": st._death_shards, "hit_sparks": st._hit_sparks, "flash_count": st._flash_count} + return {"death_shards": _death_shards, "hit_sparks": _hit_sparks, "flash_count": _flash_count} # 命中瞬间打击特效:立绘盒中心六枚短火花放射(程序化色块,确定性方向 @@ -319,7 +227,7 @@ func play_hit_impact() -> void: ## 单火花域(循环体提函数——放射弹道+渐隐+回收) func _spawn_hit_spark(i: int, center: Vector2) -> void: - st._hit_sparks += 1 + _hit_sparks += 1 var spark := ColorRect.new() spark.color = UiKit.GOLD_LIT if i % 2 == 0 else UiKit.WARM spark.size = Vector2(4, 3) @@ -340,7 +248,7 @@ func is_dead() -> bool: # 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行) func flash_hit() -> void: - st._flash_count += 1 + _flash_count += 1 if st._hp_flash_tw != null and st._hp_flash_tw.is_valid(): st._hp_flash_tw.kill() st._hp_flash_tw = create_tween() @@ -353,8 +261,7 @@ func _revive() -> void: modulate = Color(1, 1, 1) rotation_degrees = 0.0 if st._kind == "hero": - st._hero_state = HeroState.RESET # 强制回 idle 态(绕过同态早退) - set_hero_state("idle") + mo.reset_state() if slot_row != null: slot_row.visible = true queue_redraw() @@ -372,45 +279,24 @@ func _portrait_glyph() -> String: # 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面) func portrait_probe() -> Dictionary: - return {"has_tex": st._portrait_sprite != null and st._portrait_sprite.texture != null} + return {"has_tex": mo._portrait_sprite != null and mo._portrait_sprite.texture != null} # ---- 主角三态切换(攻击演出→attack/受击→hit/结算回 idle;§8.4 帧数档挂点) ---- -# 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理) +# 组合点注册转调壳(签名外形零变) func register_hero_state(state: String, tex: Texture2D) -> void: - if tex != null: - st._hero_tex[state] = tex + mo.register_hero_state(state, tex) -# 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。 -# 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。 +# 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内) func set_hero_state(state: String) -> void: - var target: int = _HS_VALUE.get(state, -1) - if st._kind != "hero" or target == st._hero_state: - return - if not st._hero_tex.has(state) and state != "idle": - return - st._hero_state = target - if st._portrait_sprite != null and st._hero_tex.has(state): - st._portrait_sprite.texture = st._hero_tex[state] - if st._state_revert_tw != null and st._state_revert_tw.is_valid(): - st._state_revert_tw.kill() - if state != "idle": - st._state_revert_tw = create_tween() - st._state_revert_tw.tween_interval(UiKit.motion(0.35)) - st._state_revert_tw.tween_callback(_settle_hero_idle) + mo.apply_hero_state(state, st._kind == "hero") -func _settle_hero_idle() -> void: - st._hero_state = HeroState.IDLE - if st._portrait_sprite != null and st._hero_tex.has("idle"): - st._portrait_sprite.texture = st._hero_tex["idle"] - - -# 三态探针出口(fx_probe——当前态名+注册态集合) +# 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变) func hero_state_probe() -> Dictionary: - return {"state": _HS_NAME.get(st._hero_state, "idle"), "registered": st._hero_tex.keys()} + return mo.state_probe() func _portrait_color() -> Color: @@ -535,7 +421,7 @@ func _draw_hp_bar(f: Font) -> void: ## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退 func _draw_portrait(f: Font) -> void: - if st._portrait_sprite != null and st._portrait_sprite.texture != null and st._portrait_sprite.visible: + if mo._portrait_sprite != null and mo._portrait_sprite.texture != null and mo._portrait_sprite.visible: return var prect := _portrait_rect() var glyph := _portrait_glyph() @@ -543,7 +429,7 @@ func _draw_portrait(f: Font) -> void: # 坐标系注记:draw_set_transform 原点已置于 center——draw_string 的 # pos 是**变换后的局部坐标**,须传相对 center 的偏移(-w/2 水平居中、+0.35h 基线下移); # 传绝对 center 会成双重偏移、把字形画到盒外。 - draw_set_transform(center, deg_to_rad(st._glyph_rot), Vector2.ONE) + draw_set_transform(center, deg_to_rad(mo._glyph_rot), Vector2.ONE) var ts := f.get_string_size(glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42) f.draw_string(get_canvas_item(), Vector2(-ts.x * 0.5, ts.y * 0.35), glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42, _portrait_color()) @@ -552,18 +438,17 @@ func _draw_portrait(f: Font) -> void: ## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画 func _draw_intent_badge(f: Font) -> void: - if not st._intent_visible: + if not mo._intent_visible: return var icenter := Vector2(box.x * 0.5, 86.0) - draw_set_transform(icenter, 0.0, Vector2(st._intent_scale, st._intent_scale)) - var its := f.get_string_size(st._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15) + draw_set_transform(icenter, 0.0, Vector2(mo._intent_scale, mo._intent_scale)) + var its := f.get_string_size(mo._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15) # 对比度垫牌(四色墨直压立绘纹理/暗舞台底 1.5-3.3:1 违 §7.2 # 「文字禁直压纹理底」;垫 PANEL_DATA 素牌后 atk 5.3/buf 4.8/debuff 4.7/charge 4.9 全 ≥4.5:1; # atk/debuff 零换墨、buf 换 HERO_BLUE_DEEP、charge 换 GOLD_INK;随弹入 transform 同步缩放) draw_rect(Rect2(-its.x * 0.5 - 4.0, -its.y * 0.75, its.x + 8.0, its.y * 1.1), UiKit.PANEL_DATA, true) - f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), st._intent_glyph, - HORIZONTAL_ALIGNMENT_LEFT, -1, 15, st._intent_col) # 局部坐标(同上注记——双重偏移修复) - draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE) + f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo._intent_glyph, + HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复) ## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2 diff --git a/game/presentation/views/standee_motion.gd b/game/presentation/views/standee_motion.gd new file mode 100644 index 00000000..e162ac4e --- /dev/null +++ b/game/presentation/views/standee_motion.gd @@ -0,0 +1,128 @@ +## VERTICAL SLICE - NOT FOR PRODUCTION +## 站桩资产与演出态件(StandeeBody 拆件——单一变化原因=美术素材落位规则与演出编排设计): +## 站桩的资产挂点与一次性演出——立绘精灵落位与换装、主角三态注册与 0.35s 短驻回归、意图徽标弹入动画。 +## 无 class_name(类名缓存面纪律)——经宿主 standee_body preload 引用(mo 实例); +## 两个属性 setter 经 _owner 回引触发宿主重绘语义逐字;tween 经 _owner 创建(同一 Node 生命绑定)。 +extends RefCounted + +enum HeroState { IDLE, RESET, ATTACK, HIT } # 主角三态+复位哨兵(外部 String 契约=register/set 形参零变化) +const _HS_NAME := {HeroState.IDLE: "idle", HeroState.RESET: "reset", HeroState.ATTACK: "attack", HeroState.HIT: "hit"} +const _HS_VALUE := {"idle": HeroState.IDLE, "reset": HeroState.RESET, "attack": HeroState.ATTACK, "hit": HeroState.HIT} + +var _owner: Node2D = null +var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位) +var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据) +# 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理 +# (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。 +var _hero_tex := {} +var _hero_state: int = 0 +var _state_revert_tw: Tween +var _intent_glyph := "" +var _intent_col := Color.WHITE +var _intent_visible := false +var _intent_scale := 1.0: # 意图弹入动画参数 + set(v): + _intent_scale = v + _owner.queue_redraw() +var _glyph_rot := 0.0: # 立绘前倾角(贴图态无此动画) + set(v): + _glyph_rot = v + _owner.queue_redraw() + + +func _init(owner: Node2D) -> void: + _owner = owner + + +# 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。 +# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2× +# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。(box = 宿主冻结 var,由宿主传入) +func set_portrait_texture(tex: Texture2D, flip: bool, box: Vector2) -> void: + if tex == null: + if _portrait_sprite != null: + _portrait_sprite.visible = false + _owner.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 # 像素帧放大防糊 + _owner.add_child(_portrait_sprite) + _portrait_sprite.texture = tex + _portrait_sprite.flip_h = flip + _portrait_sprite.visible = true + _place_portrait(box) + + +func _place_portrait(box: Vector2) -> void: + # 立绘盒 = (0,44)-(box.x, box.y-36);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) + + +# 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理) +func register_hero_state(state: String, tex: Texture2D) -> void: + if tex != null: + _hero_tex[state] = tex + + +# 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。 +# 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。 +#(is_hero = 宿主 vitals._kind == "hero" 参判传入——三连早退条件序逐字) +func apply_hero_state(state: String, is_hero: bool) -> void: + var target: int = _HS_VALUE.get(state, -1) + if not is_hero or target == _hero_state: + return + if not _hero_tex.has(state) and state != "idle": + return + _hero_state = target + if _portrait_sprite != null and _hero_tex.has(state): + _portrait_sprite.texture = _hero_tex[state] + if _state_revert_tw != null and _state_revert_tw.is_valid(): + _state_revert_tw.kill() + if state != "idle": + _state_revert_tw = _owner.create_tween() + _state_revert_tw.tween_interval(UiKit.motion(0.35)) + _state_revert_tw.tween_callback(_settle_hero_idle) + + +func _settle_hero_idle() -> void: + _hero_state = HeroState.IDLE + if _portrait_sprite != null and _hero_tex.has("idle"): + _portrait_sprite.texture = _hero_tex["idle"] + + +# 复位强制绕同态早退两拍封装(原 _revive 内 hero 分支——先置 RESET 哨兵再回 idle) +func reset_state() -> void: + _hero_state = HeroState.RESET # 强制回 idle 态(绕过同态早退) + apply_hero_state("idle", true) + + +# 意图三档动作动画(§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕 +# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落; +# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。 +#(charge = 宿主 _intent_charge 分档布尔参判传入) +func play_intent_motion(charge: bool) -> void: + var tw := _owner.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)) + if charge: + tw.tween_property(self, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推 + tw.tween_interval(UiKit.motion(0.40)) # 定格微顿 + tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.18)) + else: + tw.tween_property(self, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举) + tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.13)) + + +# 三态探针出口(fx_probe——当前态名+注册态集合;宿主 hero_state_probe 读件投影,外形零变) +func state_probe() -> Dictionary: + return {"state": _HS_NAME.get(_hero_state, "idle"), "registered": _hero_tex.keys()} diff --git a/game/presentation/views/standee_motion.gd.uid b/game/presentation/views/standee_motion.gd.uid new file mode 100644 index 00000000..a0a02b35 --- /dev/null +++ b/game/presentation/views/standee_motion.gd.uid @@ -0,0 +1 @@ +uid://cdfo2jgp2w7ei diff --git a/game/presentation/views/standee_vitals.gd b/game/presentation/views/standee_vitals.gd new file mode 100644 index 00000000..593cd38d --- /dev/null +++ b/game/presentation/views/standee_vitals.gd @@ -0,0 +1,36 @@ +## VERTICAL SLICE - NOT FOR PRODUCTION +## 站桩身份与命值态件(StandeeBody 拆件——单一变化原因=战斗结算快照投影的字段形态): +## 站桩是谁、活没活、血量几何的唯一账面——身份五元组(种类/名/id/护甲/顺位)+存活位+血量账与两条血量系 tween 槽。 +## 无 class_name(类名缓存面纪律)——经宿主 standee_body preload 引用(st 实例名沿用); +## hp_value 属性 setter 经 _owner 回引触发宿主重绘语义逐字。 +extends RefCounted + +var _owner: Node2D = null +var _kind := "enemy" +var _display_name := "" +var _enemy_id := "" +var _armor := "light" +var _order := 1 +var _dead := false +var _hp_max := 1.0 +var _hp_cur := 0 # 血量数字显示面(HUD框架 验收「血量数字可读」) +var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积) + set(v): + hp_value = clampf(v, 0.0, 1.0) + _owner.queue_redraw() +var _hp_tween: Tween +var _hp_flash_tw: Tween + + +func _init(owner: Node2D) -> void: + _owner = owner + + +# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积 +func apply_hp(hp: int) -> void: + _hp_cur = clampi(hp, 0, int(_hp_max)) + var target := float(_hp_cur) / _hp_max + if _hp_tween != null and _hp_tween.is_valid(): + _hp_tween.kill() + _hp_tween = _owner.create_tween() + _hp_tween.tween_property(self, "hp_value", target, UiKit.motion(0.15)) diff --git a/game/presentation/views/standee_vitals.gd.uid b/game/presentation/views/standee_vitals.gd.uid new file mode 100644 index 00000000..b10fb30f --- /dev/null +++ b/game/presentation/views/standee_vitals.gd.uid @@ -0,0 +1 @@ +uid://bjd7t5nmepbey diff --git a/game/probe/ui_input_probe.gd b/game/probe/ui_input_probe.gd index fddfb61a..9e2285b0 100644 --- a/game/probe/ui_input_probe.gd +++ b/game/probe/ui_input_probe.gd @@ -231,8 +231,8 @@ func _ready() -> void: if bv.hover_probe() != seal_t: _problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv.hover_probe(), seal_t]) var u1 = bv.cells_probe()[seal_t] - if not u1.st.hl_visible or u1.st.hl_color != UiKit.GOLD: - _problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.st.hl_visible), str(u1.st.hl_color)]) + if not u1.face.hl_visible or u1.face.hl_color != UiKit.GOLD: + _problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.face.hl_visible), str(u1.face.hl_color)]) if bv.mouse_default_cursor_shape != Input.CURSOR_CROSS: _problems.append("case① 挖掘光标未切十字(%d)" % bv.mouse_default_cursor_shape) # ---- case② 悬停禁入:hover_forbidden 信号 + 灰禁红框 ---- @@ -240,14 +240,14 @@ func _ready() -> void: await get_tree().process_frame if _forbids.is_empty() or _forbids[_forbids.size() - 1] != forbid_t: _problems.append("case② hover_forbidden 未达(%s)" % [str(_forbids)]) - if bv.cells_probe()[forbid_t].st.hl_color != UiKit.RED_FORBID: - _problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].st.hl_color)) + if bv.cells_probe()[forbid_t].face.hl_color != UiKit.RED_FORBID: + _problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].face.hl_color)) # ---- case③ 悬停已开格:路径预览点迹 + 移动光标 ---- _push_motion(_pos(bv, reveal_t)) await get_tree().process_frame var dots := 0 for c in bv.cells_probe(): - if bv.cells_probe()[c].st.dot_visible: + if bv.cells_probe()[c].face.dot_visible: dots += 1 if dots < 1: _problems.append("case③ 路径预览点迹未亮(dots=0)") @@ -266,8 +266,8 @@ func _ready() -> void: # 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ---- var seal_unit = bv.cells_probe().get(seal_t) if bv._palette.seal_tex != null: - if seal_unit == null or seal_unit.st.tex_seal != bv._palette.seal_tex: - _problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.st.tex_seal)]) + if seal_unit == null or seal_unit.face.tex_seal != bv._palette.seal_tex: + _problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.face.tex_seal)]) elif seal_unit == null or seal_unit.glyph != "封": _problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)]) # ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ---- @@ -371,12 +371,12 @@ func _ready() -> void: if mark_t.x >= 0: bv.set_variant_mark_resolver(func(_c): return "flat_damage") bv.refresh() - if String(bv.cells_probe()[mark_t].st.mutation_mark) != "flat_damage": - _problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].st.mutation_mark)) + if String(bv.cells_probe()[mark_t].face.mutation_mark) != "flat_damage": + _problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].face.mutation_mark)) bv.set_variant_mark_resolver(Callable()) bv.refresh() - if String(bv.cells_probe()[mark_t].st.mutation_mark) != "": - _problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].st.mutation_mark)) + if String(bv.cells_probe()[mark_t].face.mutation_mark) != "": + _problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].face.mutation_mark)) else: _problems.append("case⑭ 无未开格可验(板面异常)") # ---- case⑮ 视野迷雾(三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位;