From 87b23a810b288d6eff9c53da76730e7eb162866e Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 26 Sep 2026 04:56:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(presentation):=20B=20=E5=8C=BA=E6=88=98?= =?UTF-8?q?=E6=96=97=E8=88=9E=E5=8F=B0=E5=A3=B3=E6=94=B9=E9=80=A0=EF=BC=88?= =?UTF-8?q?Phase=20B=20T2=EF=BC=89=E2=80=94=E2=80=94BattleStageView=20Pane?= =?UTF-8?q?lContainer=E2=86=92Control+Node2D=20=E8=88=9E=E5=8F=B0=E5=8D=8A?= =?UTF-8?q?=E5=8C=BA=EF=BC=88SubViewportContainer/SubViewport/StageWorld?= =?UTF-8?q?=20=E6=96=B0=E4=BB=B6=EF=BC=89+Control=20=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=8D=8A=E5=8C=BA=E4=B8=89=E4=BB=B6=E4=BF=9D=E7=95=99+set=5Fwo?= =?UTF-8?q?rld=5Ffont/world=5Fto=5Fshell=20=E6=8C=82=E7=82=B9+C6=20?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E5=AE=9E=E8=AF=81=E8=BF=87+=E5=A4=96?= =?UTF-8?q?=E6=A1=86=E5=87=A0=E4=BD=95=E9=9B=B6=E6=BC=82=E7=A7=BB+?= =?UTF-8?q?=E5=8F=B0=E8=B4=A6=20170?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/views/battle_stage_view.gd | 65 +++++++++++++++++++- game/presentation/views/stage_world.gd | 21 +++++++ game/presentation/views/stage_world.gd.uid | 1 + production/session-state/active.md | 3 +- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 game/presentation/views/stage_world.gd create mode 100644 game/presentation/views/stage_world.gd.uid diff --git a/game/presentation/views/battle_stage_view.gd b/game/presentation/views/battle_stage_view.gd index de06b01d..e287414e 100644 --- a/game/presentation/views/battle_stage_view.gd +++ b/game/presentation/views/battle_stage_view.gd @@ -4,10 +4,19 @@ # 底部结算明细带(布局决策 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 同款)。 # 高内聚边界:容器+组合视图——站桩槽位管理、轮次指示、结算行演出分发改由 setter 面承接; # 结算流水格式化归 BattleLogView,核心状态→文案装配归 hud_presenter.gd。 class_name BattleStageView -extends PanelContainer +extends Control + +const _STAGE_WORLD := preload("res://presentation/views/stage_world.gd") +const _CONTENT_MARGINS := Rect2(6, 4, 6, 4) # 原 PanelContainer stylebox 内容边距(子面几何零漂移) var hero_standee: StandeeView var round_label: Label @@ -15,14 +24,46 @@ var stage_center: Label var enemy_box: HBoxContainer var log_view: BattleLogView var _cards: Array = [] +var _svp_container: SubViewportContainer +var _viewport: SubViewport +var world: Node2D # StageWorld(preload 脚本实例——Node2D 世界根/字体挂点;T3 站桩迁入) func _init(db_ref) -> void: custom_minimum_size = Vector2(0, 252) + # 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载) + var bg := Panel.new() var sb := UiKit.panel_flat(UiKit.PANEL_STAGE, UiKit.PANEL_BORDER, 0, 1, Vector4(6, 4, 6, 4)) - add_theme_stylebox_override("panel", sb) + bg.add_theme_stylebox_override("panel", sb) + bg.set_anchors_preset(Control.PRESET_FULL_RECT) + bg.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(bg) + + # ── 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.set_anchors_preset(Control.PRESET_FULL_RECT) + _svp_container.offset_left = _CONTENT_MARGINS.position.x + _svp_container.offset_top = _CONTENT_MARGINS.position.y + _svp_container.offset_right = -_CONTENT_MARGINS.size.x + _svp_container.offset_bottom = -_CONTENT_MARGINS.size.y + add_child(_svp_container) + _viewport = SubViewport.new() + _viewport.transparent_bg = true # 露出面板底色(与旧空隙同观感) + _svp_container.add_child(_viewport) + world = _STAGE_WORLD.new() + _viewport.add_child(world) + + # ── Control 文本半区(round_label / stage_center / log_view 原样保留——C1 永久 Control; + # hero_standee/enemy_box 仍为 Control 站桩,T3 迁世界后仅伴随件留壳层)── var vb := VBoxContainer.new() vb.add_theme_constant_override("separation", 4) + vb.set_anchors_preset(Control.PRESET_FULL_RECT) + vb.offset_left = _CONTENT_MARGINS.position.x + vb.offset_top = _CONTENT_MARGINS.position.y + vb.offset_right = -_CONTENT_MARGINS.size.x + vb.offset_bottom = -_CONTENT_MARGINS.size.y add_child(vb) round_label = UiKit.label("第 — 轮 · —", 15, true) @@ -58,6 +99,26 @@ func _init(db_ref) -> void: vb.add_child(log_view) +# SubViewport 主题/字体显式注入挂点(C6——Phase A BoardView.set_world_font 同款; +# T3 起随站桩本体下发 world_font) +func set_world_font(f: Font) -> void: + world.world_font = f + + +# 两套坐标空间换算单点(C4——台账 160 纪律延伸):SubViewport 世界内 = 局部排布; +# 壳层/fx 层(结算演出 SettlementFxView 在 SubViewport 之外)= 全局含容器偏移。 +# 站桩重建/容器重排挂点调用(站桩无逐帧位移,零每帧成本)。 +func world_to_shell(p: Vector2) -> Vector2: + return _svp_container.global_position + p + + +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 + + # 敌方站桩投影更新:数量不变 = 就地更新(死亡灰化/受击闪烁等站桩上动画不被逐行刷新重置——禁瞬消纪律); # 数量变化 = 新战斗域,重建站桩槽(轮内不增删,无回流) func set_enemies(cards_data: Array) -> void: diff --git a/game/presentation/views/stage_world.gd b/game/presentation/views/stage_world.gd new file mode 100644 index 00000000..173af733 --- /dev/null +++ b/game/presentation/views/stage_world.gd @@ -0,0 +1,21 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 战斗舞台 Node2D 迁移批 Phase B 2026-09-26(循环 1 T2/T3):StageWorld——B 区战斗舞台 Node2D +# 世界根(SubViewport 内站桩本体容器)。ADR-0006 分工:Node2D = D 区棋盘 + B 区战斗舞台; +# 与 BoardWorld(Phase A)同构。职责单点:主题/字体显式注入槽位(SubViewport 不自动继承主场景 +# Control 主题——ADR-0003 Decision 4;set_world_font 同款挂点,null = ThemeDB.fallback_font 与 +# 旧 Label 缺省主题同源)+ 站桩本体装载面(T3 迁入)。无 class_name(类名缓存面纪律—— +# BattleStageView 经 preload 使用)。 +# 坐标空间钉死(台账 160 纪律延伸):世界内 = 局部排布坐标(SubViewport 逻辑分辨率 = +# 壳层像素 1:1,无缩放系数——C2/C5);壳层/fx 层 = 全局含容器偏移,换算单点在 +# BattleStageView.world_to_shell()。 +extends Node2D + +var world_font: Font = null # 字体注入槽位(null = ThemeDB.fallback_font——BattleStageView.set_world_font 注入) + +# C6 headless 可验判据(ADR-0003 Decision 4):注入字体非空 + 中文字形 has_char + 采样文本宽度 > 0。 +# 实际光栅观感归批末目检清单。 +func font_probe() -> Dictionary: + var f: Font = world_font if world_font != null else ThemeDB.fallback_font + var has_char: bool = f.has_char("人".unicode_at(0)) + var width: float = f.get_string_size("兑现演出区", HORIZONTAL_ALIGNMENT_LEFT, -1, 14).x + return {"font": f, "has_char": has_char, "width": width} diff --git a/game/presentation/views/stage_world.gd.uid b/game/presentation/views/stage_world.gd.uid new file mode 100644 index 00000000..23bfac8b --- /dev/null +++ b/game/presentation/views/stage_world.gd.uid @@ -0,0 +1 @@ +uid://dqo6k0y6ncmov diff --git a/production/session-state/active.md b/production/session-state/active.md index f565b2ba..27fde002 100644 --- a/production/session-state/active.md +++ b/production/session-state/active.md @@ -456,4 +456,5 @@ 164. **高墙瓦片 v9.3 定稿交付+备选案清场批(2026-09-26,本会话——用户验收 v9.3 后指令「直接移入游戏本体+备选案打包挪走」;零代码、素材不入 git,worktree 不适用)**:**定稿交付**=21 枚入 `game/assets/art/board/`(16 唯一墙瓦片〔center/center_crack/center_speckle/n/s/w/nw/sw/hrun/vrun/end_n/end_s/end_w/lone/in_nw/in_sw〕+5 地面瓦片变体,`terrain_*_dungeon.png`;东/NE/SE/端头E/凹角NE/SE=运行时 flip_h 镜像不出文件,旧 beast-cave 砖零触碰)。**终版路线**=材质归模型(用户 Gemini 生图①岩石/④泥土→周期对齐无缝裁剪+BOX 降采样 32×32+环绕混合+量化 12 色)+结构归程序(v9 墨线纪律:全开缘 1px 深墨线直线零抖动/北缘内侧 1px 就地提亮/南缘立面窄暗条 11px/凹角角点+立面侧 1px 齐平收头——v9.1 悬空/v9.2 突出/v9.3 齐平三轮定位,前后对比+逐行像素审计存 `_work/corner_cmp_v91_v93_5x.png`)。**术语共识(用户立)**:未开格=墙瓦片,已开格=地面瓦片。**备选案清场**=v1–v9.2 全迭代+被否素材(含 v7 程序砌石/v3 生图 5×5 表)打包 `备选案-terrain-walls-v1-v9_2-2026-09-26.zip`(383 项 7.5MB,用户挪出库),`_work/` 仅留生图①④源图+风格参考+终版证据+定稿件。**复盘机制落记忆**(pixel-structure-verify-discipline):先抄后造/验收口径写像素级视觉判据/±1px 一律坐标推导/修复默认附前后对比。**遗留**:underdark/deep_underdark 换色(AI 纹理色调映射)+实机查表合成器(BoardWorld 接线,含镜像翻转)归后续批;任务书验收单逐条结果以本批 v9.3 终测为准(图底分离 67.6/无死黑/墨线周界闭合/覆盖层可读)。 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。 \ No newline at end of file +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。 \ No newline at end of file