iter52-卡3:迭代52卡③(presentation层封装修复): A1三类42字段公开化(280处改址)+A2拆件子件公开面(vitals14/motion14/board_hover七口)+B族组合根交接面(18服务+12句柄口,束参两处,on_*保私有)+C族杂项(UiKit三工厂/构造交付)——presentation私取sweep恒零(V1-V4,105checks),main.gd例外键32与ui_kit例外键23落基线(§3.2误测0/0反哺点),探针测试22处引用迁移,渲染golden与探针套件零转红; 验收档中

This commit is contained in:
DKChan committed 2026-11-05 03:04:21 +08:00
1 parent f2c3a3887d
commit 67024cb9ec
42 files changed
+1391 -679

No files matched your search

+85 -84
View File
@@ -1,6 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 战斗/冒险终局 overlay(模态):判定行+三行账+明细回看入口+继续/返回,失败侧暮蓝氛围。
## 摘要文本经 open_victory/open_defeat 由组合根装配喂入——本类零文案装配。
## 内部数据类 _EndOverlayState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
class_name BattleEndOverlay
extends Control
@@ -13,20 +14,20 @@ const COL_WARM := UiKit.WARM
## 内部状态件(u6 子域拆件——契约 9 职责语句「战末呈现与演出态」):单一变化原因=终局画面呈现与
## 演出计数。探针直读冻结成员(_btn/_gp_name/_gp_row/_gp_val)住宿主不进状态件。
class _EndOverlayState extends RefCounted:
var _dim: ColorRect
var _seal: Panel
var _verdict: Label
var _rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏)
var _ks_row: HBoxContainer
var _ks_name: Label
var _ks_val: Label
var _bonus_row_l: HBoxContainer
var _bonus_name: Label
var _bonus_val: Label
var _cele: TextureRect # 庆祝立绘(idle 占位浮动)
var _board_rect := Rect2()
var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收)
var _fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键)
var dim: ColorRect
var seal: Panel
var verdict: Label
var rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏)
var ks_row: HBoxContainer
var ks_name: Label
var ks_val: Label
var bonus_row_l: HBoxContainer
var bonus_name: Label
var bonus_val: Label
var cele: TextureRect # 庆祝立绘(idle 占位浮动)
var board_rect := Rect2()
var crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收)
var fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键)
var st := _EndOverlayState.new()
@@ -40,10 +41,10 @@ func _init() -> void:
# 半透明浮层文字不可读 → 实底石板皮面板形态
#(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝——open_* 就地换色),信息与
# 按钮全部上板;金蜡封 = 面板头部静态徽记(坠落演出落点被面板遮挡)。
# 骨架经 UiKit.modal_shell;_dim 引用摘出供 open_* 换氛围色。
# 骨架经 UiKit.modal_shell;dim 引用摘出供 open_* 换氛围色。
var shell := UiKit.modal_shell(self, Color(COL_WARM, 0.0), Vector2(520, 0), 12,
UiKit.panel_skinned(Vector4(16, 14, 16, 14)))
st._dim = shell.dim
st.dim = shell.dim
var box: VBoxContainer = shell.box
_build_header(box)
_build_ledger(box)
@@ -62,20 +63,20 @@ func _build_header(box: VBoxContainer) -> void:
header.add_theme_constant_override("separation", 12)
header.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_child(header)
st._seal = Panel.new()
st._seal.mouse_filter = Control.MOUSE_FILTER_IGNORE
st.seal = Panel.new()
st.seal.mouse_filter = Control.MOUSE_FILTER_IGNORE
var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4)
st._seal.add_theme_stylebox_override("panel", sb)
st._seal.custom_minimum_size = Vector2(52, 52)
st._seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER
header.add_child(st._seal)
st._seal.add_child(_seal_img())
st._verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
st._verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
st._verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER
header.add_child(st._verdict)
st._cele = _cele_rect()
header.add_child(st._cele)
st.seal.add_theme_stylebox_override("panel", sb)
st.seal.custom_minimum_size = Vector2(52, 52)
st.seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER
header.add_child(st.seal)
st.seal.add_child(_seal_img())
st.verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
st.verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
st.verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER
header.add_child(st.verdict)
st.cele = _cele_rect()
header.add_child(st.cele)
## 蜡封图域(32px 像素过滤居中)
@@ -110,30 +111,30 @@ func _cele_rect() -> TextureRect:
## 三行账域(两列对齐账行——审计差距④对齐 demo ⑧:项目名左/数值右,定宽 360)
func _build_ledger(box: VBoxContainer) -> void:
st._rows_box = VBoxContainer.new()
st._rows_box.add_theme_constant_override("separation", 6)
st._rows_box.visible = false
box.add_child(st._rows_box)
st.rows_box = VBoxContainer.new()
st.rows_box.add_theme_constant_override("separation", 6)
st.rows_box.visible = false
box.add_child(st.rows_box)
_gp_row = _ledger_row()
_gp_name = _ledger_name(UiKit.FS_SUB, UiKit.GOLD_INK, true) # GOLD_DEEP 对结算板 2.67:1 越带,换深金墨 5.21:1
_gp_val = _ledger_value(UiKit.FS_SUB, UiKit.GOLD_INK, true) # 同上
_gp_row.add_child(_gp_name)
_gp_row.add_child(_gp_val)
st._rows_box.add_child(_gp_row)
st._ks_row = _ledger_row()
st._ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false)
st._ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true)
st._ks_row.add_child(st._ks_name)
st._ks_row.add_child(st._ks_val)
st._ks_row.visible = false
st._rows_box.add_child(st._ks_row)
st._bonus_row_l = _ledger_row()
st._bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false)
st._bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true)
st._bonus_row_l.add_child(st._bonus_name)
st._bonus_row_l.add_child(st._bonus_val)
st._bonus_row_l.visible = false
st._rows_box.add_child(st._bonus_row_l)
st.rows_box.add_child(_gp_row)
st.ks_row = _ledger_row()
st.ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false)
st.ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true)
st.ks_row.add_child(st.ks_name)
st.ks_row.add_child(st.ks_val)
st.ks_row.visible = false
st.rows_box.add_child(st.ks_row)
st.bonus_row_l = _ledger_row()
st.bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false)
st.bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true)
st.bonus_row_l.add_child(st.bonus_name)
st.bonus_row_l.add_child(st.bonus_val)
st.bonus_row_l.visible = false
st.rows_box.add_child(st.bonus_row_l)
box.add_child(HSeparator.new())
@@ -147,19 +148,19 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
var bonus_summary: String = opts.get("bonus_summary", "")
var gp_gain: int = opts.get("gp_gain", -1)
var gp_total: int = opts.get("gp_total", -1)
st._dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo
st._verdict.text = verdict_line
st._seal.visible = true # 胜利徽记(面板头部静态)
st._seal.modulate = Color.WHITE
st.dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo
st.verdict.text = verdict_line
st.seal.visible = true # 胜利徽记(面板头部静态)
st.seal.modulate = Color.WHITE
_set_ledger_row(_gp_name, _gp_val, gp_summary, true) # GP 行=固定名「战利品 GP」(滚动/整串均只入值列)
_set_ledger_row(st._ks_name, st._ks_val, ks_summary)
st._ks_row.visible = ks_summary != ""
_set_ledger_row(st._bonus_name, st._bonus_val, bonus_summary)
st._bonus_row_l.visible = bonus_summary != ""
st._rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != ""
_set_ledger_row(st.ks_name, st.ks_val, ks_summary)
st.ks_row.visible = ks_summary != ""
_set_ledger_row(st.bonus_name, st.bonus_val, bonus_summary)
st.bonus_row_l.visible = bonus_summary != ""
st.rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != ""
_btn.text = continue_label
st._board_rect = board_rect
st._cele.visible = true
st.board_rect = board_rect
st.cele.visible = true
_play_celebrate()
visible = true
if UiKit.victory_fx_style == "dust":
@@ -173,23 +174,23 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void:
# 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍)
st._dim.color = Color(COL_DUSK, 0.6) # 同族压暗
st._verdict.text = verdict_line
st._board_rect = board_rect
st.dim.color = Color(COL_DUSK, 0.6) # 同族压暗
st.verdict.text = verdict_line
st.board_rect = board_rect
visible = true
st._seal.visible = true
st._seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧)
st._cele.visible = false
st._rows_box.visible = false # 失败侧不呈现账行
st.seal.visible = true
st.seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧)
st.cele.visible = false
st.rows_box.visible = false # 失败侧不呈现账行
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
_play_seal_recall(8)
func close() -> void:
visible = false
st._seal.visible = false
st._seal.modulate = Color.WHITE
st._cele.visible = false
st.seal.visible = false
st.seal.modulate = Color.WHITE
st.cele.visible = false
var tw: Tween = get_meta(&"celebrate_tw", null) # 循环句柄随节点存(跨 open/close,close 必杀)
if tw != null and tw.is_valid():
tw.kill()
@@ -202,7 +203,7 @@ func is_open() -> bool:
func verdict_text() -> String:
return st._verdict.text
return st.verdict.text
# 两列账行工厂(审计差距④——名左/值右,定宽 360)
@@ -249,8 +250,8 @@ func _fx_center() -> Vector2:
# 封印化光点收回(失败——插过的封印逐面化光点收回占位,§2.5)
func _play_seal_recall(count: int) -> void:
st._fx_counts["seal"] = count
var base := st._board_rect.get_center() if st._board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
st.fx_counts["seal"] = count
var base := st.board_rect.get_center() if st.board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
for i in count:
_spawn_seal_dot(i, base)
@@ -260,8 +261,8 @@ func _spawn_seal_dot(i: int, base: Vector2) -> void:
var dot := ColorRect.new()
dot.color = COL_GOLD
dot.size = Vector2(6, 6)
dot.position = base + Vector2((float(i % 5) - 2.0) * st._board_rect.size.x * 0.14 if st._board_rect.size.x > 0 else 0.0,
(float(i % 3) - 1.0) * st._board_rect.size.y * 0.18)
dot.position = base + Vector2((float(i % 5) - 2.0) * st.board_rect.size.x * 0.14 if st.board_rect.size.x > 0 else 0.0,
(float(i % 3) - 1.0) * st.board_rect.size.y * 0.18)
dot.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(dot)
var tw := dot.create_tween()
@@ -277,7 +278,7 @@ func _spawn_seal_dot(i: int, base: Vector2) -> void:
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
func _play_crest_assemble() -> void:
_clear_crest_bricks()
st._fx_counts["crest"] = 10
st.fx_counts["crest"] = 10
var c := _fx_center()
for i in 10:
_spawn_crest_brick(i, c)
@@ -293,7 +294,7 @@ func _spawn_crest_brick(i: int, c: Vector2) -> void:
var ring := c + Vector2(cos(ang), sin(ang)) * 64.0
brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场
add_child(brick)
st._crest_bricks.append(brick)
st.crest_bricks.append(brick)
var btw := brick.create_tween()
btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段)
btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
@@ -302,7 +303,7 @@ func _spawn_crest_brick(i: int, c: Vector2) -> void:
# 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载)
func _play_dust_sadle() -> void:
st._fx_counts["dust"] = 8
st.fx_counts["dust"] = 8
var base := _fx_center()
for i in 8:
_spawn_dust_mote(i, base)
@@ -330,16 +331,16 @@ func _play_celebrate() -> void:
var prev: Tween = get_meta(&"celebrate_tw", null)
if prev != null and prev.is_valid():
prev.kill()
var tw := st._cele.create_tween()
var tw := st.cele.create_tween()
set_meta(&"celebrate_tw", tw)
tw.set_loops()
tw.tween_property(st._cele, "position:y", st._cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tw.tween_property(st._cele, "position:y", st._cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tw.tween_property(st.cele, "position:y", st.cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tw.tween_property(st.cele, "position:y", st.cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
# 尘金粒子(备选演出——金屑自面板上带飘落闪灭;确定性散布守 S2)
func _play_victory_dust() -> void:
st._fx_counts["dust"] += 10
st.fx_counts["dust"] += 10
var c := _fx_center()
for i in 10:
_spawn_victory_mote(i, c)
@@ -372,15 +373,15 @@ func _roll_gp(gp_gain: int, gp_total: int) -> void:
func _clear_crest_bricks() -> void:
for b in st._crest_bricks:
for b in st.crest_bricks:
if is_instance_valid(b):
b.queue_free()
st._crest_bricks.clear()
st.crest_bricks.clear()
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/封印光收回三断言面)
func fx_probe() -> Dictionary:
return {"crest_shards": st._fx_counts["crest"], "dust_motes": st._fx_counts["dust"], "seal_dots": st._fx_counts["seal"]}
return {"crest_shards": st.fx_counts["crest"], "dust_motes": st.fx_counts["dust"], "seal_dots": st.fx_counts["seal"]}
func _emit_continue_pressed() -> void:
+36 -1
View File
@@ -10,12 +10,14 @@ 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")
const _SEAL_LAYER := preload("res://presentation/views/seal_mark_layer.gd")
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)
_sync_seal_layer()
queue_redraw()
var cell := Vector2i.ZERO # 板坐标(诊断观测面)
# ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示)
@@ -41,16 +43,49 @@ var fog_state := 0: # 探针直读冻结
set(v):
if fog_state != v:
fog_state = v
_sync_seal_layer() # 封印随格隐没(§6.5——雾态 !=0 整层隐藏)
queue_redraw()
# ── 封印印记(卡④ 法阵化——着色器按格边参数化绘制,替代 tex_seal 贴图整格铺放)──
# BoardView paint 按格态写 seal_on(封印格=未开且未移除);子件 on 才建、off 撤、edge 变更重注入。
var seal_on := false:
set(v):
if seal_on != v:
seal_on = v
_sync_seal_layer()
var _glow_layer: Node2D = null
var _seal_layer: Node2D = null # 封印印记子件(seal_on 才建——作用集=封印格)
var base = null # 格面基座层绘制域件(_init 自建)
var face = null # 格面标识与叠加层绘制域件(_init 自建)
# 封印子件生命周期单点:on 才建、off 撤;visible = seal_on 且非雾态(§6.5 随格隐没)
func _sync_seal_layer() -> void:
if seal_on and _seal_layer == null:
_seal_layer = _SEAL_LAYER.new()
add_child(_seal_layer)
if _seal_layer != null and not is_instance_valid(_seal_layer):
_seal_layer = null
return
if _seal_layer == null:
return
if not seal_on:
_seal_layer.queue_free()
_seal_layer = null
return
_seal_layer.setup(edge, fog_state == 0)
func _init() -> void:
base = _BASE_LAYER.new(self)
face = _MARK_OVERLAY.new(self)
face = _MARK_OVERLAY.new(self, _set_badge_mask)
# 徽章遮罩下发口(构造交付 Callable——glow 层引用自持本件,face 零私取回引)
func _set_badge_mask(on: bool) -> void:
if _glow_layer != null and is_instance_valid(_glow_layer):
_glow_layer.set_badge_mask(on)
func _draw() -> void:
+21 -4
View File
@@ -33,7 +33,24 @@ func _init(bundle: Dictionary) -> void:
_cb_chord = bundle["chord"]
func _hover_at(pos: Vector2) -> void:
# ---- 公开操作面(pres封装 A2——宿主经此驱动,字段保持私有)----
# 每战重绑(排雷玩法对象随战更替——宿主 rebuild 同拍调用)
func rebind(minesweeper: Variant) -> void:
_minesweeper = minesweeper
# 硬复位悬停锚(无 unhover 视觉清理——宿主 rebuild 前提下视觉随重建清零)
func reset() -> void:
_hover = Vector2i(-9, -9)
# 悬停现值读口(宿主呈现查询)
func hover_pos() -> Vector2i:
return _hover
func hover_at(pos: Vector2) -> void:
var c: Vector2i = _cb_cell_at.call(pos)
if c.x < 0:
if _hover.x >= 0:
@@ -44,13 +61,13 @@ func _hover_at(pos: Vector2) -> void:
_on_cell_hover(c)
func _on_shell_mouse_exited() -> void:
func on_shell_mouse_exited() -> void:
if _hover.x >= 0:
_on_cell_unhover(_hover)
# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重
func _detect_chord(c: Vector2i) -> void:
func detect_chord(c: Vector2i) -> void:
var now := Time.get_ticks_msec()
if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS:
_cb_chord.call(c)
@@ -70,7 +87,7 @@ func _on_cell_unhover(c: Vector2i) -> void:
_clear_hover_visuals(c)
func _refresh_hover_after_paint() -> void:
func refresh_after_paint() -> void:
# 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变)
if _hover.x >= 0 and _cells.has(_hover):
_apply_hover_visuals(_hover)
+12 -15
View File
@@ -143,7 +143,7 @@ func board() -> BoardData:
func _ready() -> void:
_apply_custom_cursors()
mouse_exited.connect(_hover_comp._on_shell_mouse_exited)
mouse_exited.connect(_hover_comp.on_shell_mouse_exited)
# 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2);
@@ -246,7 +246,7 @@ func _refresh_slab() -> void:
func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_board = board
_minesweeper = minesweeper
_hover_comp._minesweeper = _minesweeper # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象)
_hover_comp.rebind(_minesweeper) # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象)
_palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置)
for child in _world.get_children():
child.queue_free()
@@ -257,7 +257,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_contact_unit = null
_hero_sprite.last = Vector2i(-99, -99)
_r0.clear()
_hover_comp._hover = Vector2i(-9, -9)
_hover_comp.reset()
for c in _board.compute_r0():
_r0[c] = true
# 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark;
@@ -306,7 +306,7 @@ func refresh() -> void:
for y in _board.height:
for x in _board.width:
_paint_cell(Vector2i(x, y), hero)
_hover_comp._refresh_hover_after_paint()
_hover_comp.refresh_after_paint()
_apply_contact_bands()
_update_hero(hero) # 英雄精灵 diff 驱动
@@ -461,7 +461,7 @@ func _gui_input(ev: InputEvent) -> void:
if _minesweeper == null:
return
if ev is InputEventMouseMotion:
_hover_comp._hover_at(ev.position)
_hover_comp.hover_at(ev.position)
elif ev is InputEventMouseButton and ev.pressed:
var c := _cell_at(ev.position)
if c.x >= 0:
@@ -476,7 +476,7 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
action_requested.emit(c, "move")
else:
action_requested.emit(c, "dig")
_hover_comp._detect_chord(c)
_hover_comp.detect_chord(c)
elif ev.button_index == MOUSE_BUTTON_RIGHT:
if not _minesweeper.has_seal(c):
action_requested.emit(c, "seal_place")
@@ -536,13 +536,13 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var seal: bool = _minesweeper.has_seal(c)
var removed: bool = _minesweeper.is_removed(c) # 中立移除格(退色降档)
var is_hero: bool = c == hero
# 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal
# 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。
u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null
# 封印印记下发(卡④ 法阵化):封印格=未开且未移除 → CellUnit 封印子件绘制(着色器参数化);
# 其余格撤子件。层序与迷雾门控在 CellUnit 侧承载(seal_mark_layer/fog_state 联动)。
u.seal_on = seal and not revealed
if removed:
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结)
seal = false
u.face.tex_seal = null
u.seal_on = false
# 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面
#(u.base/u.face 域件与 u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载)
var bg: Color = _paint_cell_ink(c, u, revealed, seal)
@@ -577,10 +577,7 @@ func _paint_cell_ink(c: Vector2i, u: Node2D, revealed: bool, seal: bool) -> Colo
digit_col = _digit_ink(d) # 数字墨三档色温(§4.1)
else:
if seal:
bg = COL_BRICK
if _palette.seal_tex == null:
glyph = "封"
digit_col = COL_SEAL
bg = COL_BRICK # 封印印记由 CellUnit 封印子件绘制(卡④ 法阵化——缺图字形回退随贴图路径退役)
else:
bg = COL_BRICK
u.glyph = glyph
@@ -825,7 +822,7 @@ func diggable_probe() -> Dictionary:
# 悬停格只读出口(case① 悬停到达断言消费面——读件投影,返回语义零变化)
func hover_probe() -> Vector2i:
return _hover_comp._hover
return _hover_comp.hover_pos()
# 迷雾参数只读快照(case⑮ 参数漂移断言消费面)
+7 -12
View File
@@ -3,15 +3,16 @@
## 变异标记/数字色牌与字形/奖励徽章/封印/路径点迹/焦点高亮/中立移除退色的全部绘制入参与绘制命令。
## 无 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 原样)。
## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 经构造交付 Callable 下发——pres封装 C)。
extends RefCounted
var _owner: Node2D = null
var _mask_set: Callable # 徽章遮罩下发口(构造交付——宿主 CellUnit 自持 glow 层引用,本件零回引宿主私有面)
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 != "")
if _mask_set.is_valid():
_mask_set.call(v != "")
_owner.queue_redraw()
var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝)
set(v):
@@ -30,11 +31,6 @@ var font: Font = null # 字体槽位(null = ThemeDB.fallback_font
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" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记)
# 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写):
@@ -56,8 +52,9 @@ var removed_dim := false: # 中立移除退色层(BoardView paint
_owner.queue_redraw()
func _init(owner: Node2D) -> void:
func _init(owner: Node2D, mask_set: Callable) -> void:
_owner = owner
_mask_set = mask_set
## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角
@@ -91,9 +88,7 @@ func draw_glyph_layer(canvas: CanvasItem, rect: Rect2, glyph: String, glyph_colo
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)
# ④ 封印:印记由 CellUnit 封印子件(seal_mark_layer)参数化绘制——本域件零封印绘制(卡④ 法阵化)
## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌;
+66 -65
View File
@@ -1,6 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 开发调试浮窗:全局可开(组合根启动即开+两屏角标再开),当局数据只读区块为本局种子唯一查看位。
## 扩展调试项按区块往下追加(超高滚动);模拟报告文案装配归 HudPresenter——本类零动效零文案装配。
## 内部数据类 _DebugState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
class_name DebugOverlay
extends Control
@@ -12,22 +13,22 @@ signal closed
## 内部状态件(u7 子域拆件——契约 9 职责语句「调试浮窗运行态」):单一变化原因=调试面板状态与
## 当局数据投影。探针直读冻结成员(seed_edit/_sim_btn/_dbg_btn)住宿主;
## st._run_data/st._run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。
## st.run_data/st.run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。
class _DebugState extends RefCounted:
var _db
var _base_atk := 0
var _base_hp := 0
var _atk: HSlider
var _hp: HSlider
var _atk_val: Label
var _hp_val: Label
var _eff: Label
var _sim_count: SpinBox
var _sim_out: RichTextLabel
var _sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰)
var _ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用)
var _run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入)
var _run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe)
var db
var base_atk := 0
var base_hp := 0
var atk: HSlider
var hp: HSlider
var atk_val: Label
var hp_val: Label
var eff: Label
var sim_count: SpinBox
var sim_out: RichTextLabel
var sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰)
var ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用)
var run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入)
var run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe)
var st := _DebugState.new()
@@ -37,9 +38,9 @@ var _dbg_btn: Button # 两层调试入口钮(对局中禁用承
func _init(db_ref, base_atk: int, base_hp: int) -> void:
st._db = db_ref
st._base_atk = base_atk
st._base_hp = base_hp
st.db = db_ref
st.base_atk = base_atk
st.base_hp = base_hp
_build()
_sync_labels()
visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律)
@@ -48,78 +49,78 @@ func _init(db_ref, base_atk: int, base_hp: int) -> void:
# ---- 对外语义面(组合根/探针)----
func atk_pct() -> int:
return int(st._atk.value)
return int(st.atk.value)
func hp_pct() -> int:
return int(st._hp.value)
return int(st.hp.value)
func set_sliders(atk: int, hp: int) -> void:
st._atk.value = atk
st._hp.value = hp
st.atk.value = atk
st.hp.value = hp
func reset_sliders() -> void:
st._atk.value = 0
st._hp.value = 0
st.atk.value = 0
st.hp.value = 0
_sync_labels()
func effect_text() -> String:
return st._eff.text
return st.eff.text
func set_sim_count(n: int) -> void:
st._sim_count.value = n
st.sim_count.value = n
func is_running() -> bool:
return st._sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑)
return st.sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑)
func set_sim_running(running: bool) -> void:
st._sim_running = running
st.sim_running = running
_sync_sim_button()
# 对局中上下文锁定(组合根 _sync_debug_overlay 喂入):批量模拟与两层调试入口
# 在活跃对局并存态不开放(长程异步模拟/并发对局编排与活跃结算并存态破坏状态机)
func set_context_locked(locked: bool) -> void:
st._ctx_locked = locked
st.ctx_locked = locked
_sync_sim_button()
# 当局数据只读投影喂入(六项——组合根 _debug_run_data 构造;纯标签重查零动效)
func update_run_data(data: Dictionary) -> void:
st._run_data = data.duplicate()
for key in st._run_labels:
(st._run_labels[key] as Label).text = str(data.get(key, "—"))
st.run_data = data.duplicate()
for key in st.run_labels:
(st.run_labels[key] as Label).text = str(data.get(key, "—"))
# 当局数据快照读面(探针值对面——ui_tune_probe 断言六项与只读访问器逐项一致)
func run_data_probe() -> Dictionary:
return st._run_data.duplicate()
return st.run_data.duplicate()
# 两钮同判据(模拟运行 / 对局上下文锁定对「模拟」「两层调试」两入口等价)——
# 判据单点:两 setter 都经此同步,禁在调用点各写一份
func _sync_sim_button() -> void:
_sim_btn.disabled = st._sim_running or st._ctx_locked
_sim_btn.text = "模拟中…" if st._sim_running else "运行模拟"
_dbg_btn.disabled = st._sim_running or st._ctx_locked
_sim_btn.disabled = st.sim_running or st.ctx_locked
_sim_btn.text = "模拟中…" if st.sim_running else "运行模拟"
_dbg_btn.disabled = st.sim_running or st.ctx_locked
func set_sim_progress(text: String) -> void:
st._sim_out.text = text
st.sim_out.text = text
func set_sim_report(text: String) -> void:
st._sim_out.text = text
st.sim_out.text = text
func output_text() -> String:
return st._sim_out.get_parsed_text()
return st.sim_out.get_parsed_text()
# ---- 探针接缝(真实翻译出口——按钮 pressed 信号路径,非直调组合根)----
@@ -147,11 +148,11 @@ func _reset_btn_pressed() -> void:
func effective_atk() -> int:
return UiKit.scaled(st._base_atk, atk_pct())
return UiKit.scaled(st.base_atk, atk_pct())
func effective_max_hp() -> int:
return UiKit.scaled(st._base_hp, hp_pct())
return UiKit.scaled(st.base_hp, hp_pct())
# ---- 内部构建(标题/关闭固定于浮窗骨架,可扩展列表区居中滚动)----
@@ -185,7 +186,7 @@ func _build_intro(box: VBoxContainer) -> void:
var title := UiKit.label("调试",UiKit.FS_TITLE, true, UiKit.WARM)
box.add_child(title)
var desc := UiKit.label(
"开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st._base_atk, st._base_hp],
"开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st.base_atk, st.base_hp],
13, false, UiKit.WARM_SOFT)
box.add_child(desc)
box.add_child(HSeparator.new())
@@ -207,7 +208,7 @@ func _build_run_data(list: VBoxContainer) -> void:
cap.custom_minimum_size = Vector2(52, 0)
run_grid.add_child(cap)
var val := UiKit.label("—",UiKit.FS_HINT, false, UiKit.WARM)
st._run_labels[item[0]] = val
st.run_labels[item[0]] = val
run_grid.add_child(val)
list.add_child(HSeparator.new())
@@ -241,13 +242,13 @@ func _build_seed_row(list: VBoxContainer) -> void:
## 区块②角色数值调整板(攻击/生命两滑轨+生效值行+应用/重置钮)
func _build_slider_block(list: VBoxContainer) -> void:
var atk_row := _slider_row(list, "攻击加成")
st._atk = atk_row.slider
st._atk_val = atk_row.value_label
st.atk = atk_row.slider
st.atk_val = atk_row.value_label
var hp_row := _slider_row(list, "生命加成")
st._hp = hp_row.slider
st._hp_val = hp_row.value_label
st._eff = UiKit.label("",UiKit.FS_HINT, false)
list.add_child(st._eff)
st.hp = hp_row.slider
st.hp_val = hp_row.value_label
st.eff = UiKit.label("",UiKit.FS_HINT, false)
list.add_child(st.eff)
var btn_row := HBoxContainer.new()
list.add_child(btn_row)
var apply_btn := UiKit.button("应用并关闭")
@@ -264,21 +265,21 @@ func _build_sim_block(list: VBoxContainer) -> void:
var sim_row := HBoxContainer.new()
list.add_child(sim_row)
sim_row.add_child(UiKit.label("批量模拟第一关",UiKit.FS_BODY, false))
st._sim_count = SpinBox.new()
st._sim_count.min_value = 10
st._sim_count.max_value = 200
st._sim_count.step = 10
st._sim_count.value = 30
st._sim_count.custom_minimum_size = Vector2(90, 0)
sim_row.add_child(st._sim_count)
st.sim_count = SpinBox.new()
st.sim_count.min_value = 10
st.sim_count.max_value = 200
st.sim_count.step = 10
st.sim_count.value = 30
st.sim_count.custom_minimum_size = Vector2(90, 0)
sim_row.add_child(st.sim_count)
sim_row.add_child(UiKit.label("次(理性玩家策略,含面板当前加成)",UiKit.FS_HINT, false))
_sim_btn = UiKit.button("运行模拟")
_sim_btn.pressed.connect(_emit_simulate_requested)
sim_row.add_child(_sim_btn)
st._sim_out = RichTextLabel.new()
st._sim_out.bbcode_enabled = true
st._sim_out.custom_minimum_size = Vector2(0, 300)
list.add_child(st._sim_out)
st.sim_out = RichTextLabel.new()
st.sim_out.bbcode_enabled = true
st.sim_out.custom_minimum_size = Vector2(0, 300)
list.add_child(st.sim_out)
list.add_child(HSeparator.new())
@@ -316,14 +317,14 @@ func _slider_row(box: VBoxContainer, title: String) -> Dictionary:
func _sync_labels() -> void:
st._atk_val.text = "+%d%%" % atk_pct()
st._hp_val.text = "+%d%%" % hp_pct()
st._eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [
st._base_atk, effective_atk(), st._base_hp, effective_max_hp()]
st.atk_val.text = "+%d%%" % atk_pct()
st.hp_val.text = "+%d%%" % hp_pct()
st.eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [
st.base_atk, effective_atk(), st.base_hp, effective_max_hp()]
func _emit_simulate_requested() -> void:
simulate_requested.emit(int(st._sim_count.value))
simulate_requested.emit(int(st.sim_count.value))
func _emit_diagnose_requested() -> void:
+106 -105
View File
@@ -1,6 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 关卡画面·独立屏:全屏不透明独占呈现(激活期战斗屏宿主由组合根整体隐藏),节点图选路呈现。
## 前进唯一通道=图上点候选节点(route_chosen 外发)——零自动推进;放弃入口可见性归组合根(仅冒险模式开)。
## 内部数据类 _MapState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
class_name LevelMapScreen
extends Control
@@ -32,23 +33,23 @@ const CONFIRM_WINDOW_S := 3.0 # 放弃确认态窗口(
const COL_HP_BAR_BACK := UiKit.HP_BAR_BACK # 迷你血条深底(对暗面板可辨;值单点收 UiKit)
## 内部状态件(u2 子域拆件——契约 9 职责轴):_MapState=选路屏运行态与图绘制态(单一变化原因=
## 选路呈现)。_layer_decor 域字典承载换层装饰三件(图标贴图/石纹面/tint 层号——装饰域归并形态)。
## 选路呈现)。layer_decor 域字典承载换层装饰三件(图标贴图/石纹面/tint 层号——装饰域归并形态)。
## 探针直读冻结成员(_cand_ids/_hover_id/_content_size/_layout_cache/abandon_btn/status_badge)住宿主。
class _MapState extends RefCounted:
var _runner = null
var _title: Label
var _canvas: Control
var _level_label: Label
var _gp_value: Label
var _hp_value: Label
var _hp_bar: Control
var _attr_value: Label
var _bonus_row: HBoxContainer
var _bonus_value: Label
var _feat_value: Label # 特性持有行(组合根投喂逐件文案)
var _abandon_timer := ArmedTimer.new() # 放弃确认窗(_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S)
var _drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记录(_draw_graph 逐帧在实际绘制点重建——display_probe 消费面)
var _layer_decor := {"node_icons": {}, "noise_rect": null, "tint_level": 0} # 换层装饰域(节点图标贴图 load_tex 守卫缺位 null→字形回退/屏底石纹面/当前已应用 tint 层号 0=未应用)
var runner = null
var title: Label
var canvas: Control
var level_label: Label
var gp_value: Label
var hp_value: Label
var hp_bar: Control
var attr_value: Label
var bonus_row: HBoxContainer
var bonus_value: Label
var feat_value: Label # 特性持有行(组合根投喂逐件文案)
var abandon_timer := ArmedTimer.new() # 放弃确认窗(_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S)
var drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记录(_draw_graph 逐帧在实际绘制点重建——display_probe 消费面)
var layer_decor := {"node_icons": {}, "noise_rect": null, "tint_level": 0} # 换层装饰域(节点图标贴图 load_tex 守卫缺位 null→字形回退/屏底石纹面/当前已应用 tint 层号 0=未应用)
var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集;探针直读冻结——住宿主)
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它;探针直读冻结)
@@ -60,18 +61,18 @@ var _map := _MapState.new()
func _init() -> void:
_map._abandon_timer.expired.connect(_reset_abandon_confirm)
_map.abandon_timer.expired.connect(_reset_abandon_confirm)
# 节点图标贴图装载(load_tex 守卫,缺位回退现行字形)
for t in TEX_NODE_ICONS:
_map._layer_decor.node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t])
_map.layer_decor.node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t])
# 独立屏底(spec Layout——demo #v-map 暖棕竖向
# 渐变(#4A3B2A 0%→#3A2E22 40%)+石纹弱质感 0.14+暗角聚焦;底纹缺位回退纯色)
var base := UiKit._gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT)
var base := UiKit.gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT)
add_child(base)
var tex := UiKit.backdrop_tile()
if tex != null:
base.add_child(tex)
_map._layer_decor.noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档)
_map.layer_decor.noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档)
base.add_child(UiKit.vignette_rect())
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
@@ -97,8 +98,8 @@ func _build_top_bar(box: VBoxContainer) -> void:
lvl_plate.add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1, Vector4(12, 3, 12, 3)))
top.add_child(lvl_plate)
_map._level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM)
lvl_plate.add_child(_map._level_label)
_map.level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM)
lvl_plate.add_child(_map.level_label)
var top_sp := Control.new()
top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(top_sp)
@@ -109,31 +110,31 @@ func _build_top_bar(box: VBoxContainer) -> void:
var gp_row := HBoxContainer.new()
gp_row.add_theme_constant_override("separation", 6)
gp_box.add_child(gp_row)
_map._gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD)
gp_row.add_child(_map._gp_value)
_map.gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD)
gp_row.add_child(_map.gp_value)
status_badge = UiKit.button("状态", 0.0)
status_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT)
status_badge.tooltip_text = "查看本局冒险进度(只读)"
status_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER
top.add_child(status_badge)
_map._title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM)
_map._title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_map._title)
_map.title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM)
_map.title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_map.title)
## 节点图画布域(图上点击 = 唯一提交通道——布局决策 2;mouse_filter STOP 接悬停/点击)
func _build_canvas(box: VBoxContainer) -> void:
_map._canvas = Control.new()
_map._canvas.custom_minimum_size = Vector2(620, 240)
_map._canvas.mouse_filter = Control.MOUSE_FILTER_STOP
_map._canvas.draw.connect(_draw_graph.bind(_map._canvas))
_map._canvas.gui_input.connect(_on_canvas_input)
box.add_child(_map._canvas)
_map.canvas = Control.new()
_map.canvas.custom_minimum_size = Vector2(620, 240)
_map.canvas.mouse_filter = Control.MOUSE_FILTER_STOP
_map.canvas.draw.connect(_draw_graph.bind(_map.canvas))
_map.canvas.gui_input.connect(_on_canvas_input)
box.add_child(_map.canvas)
## 底部状态栏域(合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④;
## 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权);
## _hp_value/_attr_value/_bonus_value/_feat_value 投喂键全部保留。
## hp_value/attr_value/bonus_value/feat_value 投喂键全部保留。
## 底栏定高纪律(对齐 demo——单条 ~85px 紧凑亮条;任何子件不再能把栏撑爆:
## 特性持有行限宽截断+tooltip 全文))
func _build_status_bar(box: VBoxContainer) -> void:
@@ -150,18 +151,18 @@ func _build_status_bar(box: VBoxContainer) -> void:
var life_box := VBoxContainer.new()
strip.add_child(life_box)
life_box.add_child(UiKit.label("生命",UiKit.FS_CAPTION, false, UiKit.INK_HINT))
_map._hp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
life_box.add_child(_map._hp_value)
_map._hp_bar = Control.new()
_map._hp_bar.custom_minimum_size = Vector2(130, 7)
_map._hp_bar.draw.connect(_draw_hp_bar.bind(_map._hp_bar))
life_box.add_child(_map._hp_bar)
_map.hp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
life_box.add_child(_map.hp_value)
_map.hp_bar = Control.new()
_map.hp_bar.custom_minimum_size = Vector2(130, 7)
_map.hp_bar.draw.connect(_draw_hp_bar.bind(_map.hp_bar))
life_box.add_child(_map.hp_bar)
strip.add_child(_vdiv())
var attr_box := VBoxContainer.new()
strip.add_child(attr_box)
attr_box.add_child(UiKit.label("属性",UiKit.FS_CAPTION, false, UiKit.INK_HINT))
_map._attr_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
attr_box.add_child(_map._attr_value)
_map.attr_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
attr_box.add_child(_map.attr_value)
strip.add_child(_vdiv())
_build_bonus_group(strip)
var spacer := Control.new()
@@ -193,21 +194,21 @@ func _build_face_rect() -> TextureRect:
## 本关加成框域(零加成隐藏——spec 布局决策 4)+ 流派件持有行(组合根投喂;限宽单行截断+tooltip 全文)
func _build_bonus_group(strip: HBoxContainer) -> void:
_map._bonus_row = HBoxContainer.new()
strip.add_child(_map._bonus_row)
_map.bonus_row = HBoxContainer.new()
strip.add_child(_map.bonus_row)
var bonus_box := VBoxContainer.new()
_map._bonus_row.add_child(bonus_box)
_map.bonus_row.add_child(bonus_box)
var bonus_inner := VBoxContainer.new()
bonus_box.add_child(bonus_inner)
var bonus_cap := UiKit.label("属性值提升",UiKit.FS_CAPTION, false, UiKit.INK_HINT) # 深墨(亮板可读)
bonus_inner.add_child(bonus_cap)
_map._bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上)
bonus_inner.add_child(_map._bonus_value)
_map._feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT)
_map._feat_value.custom_minimum_size = Vector2(240, 0)
_map._feat_value.clip_text = true
_map._feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
bonus_box.add_child(_map._feat_value)
_map.bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上)
bonus_inner.add_child(_map.bonus_value)
_map.feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT)
_map.feat_value.custom_minimum_size = Vector2(240, 0)
_map.feat_value.clip_text = true
_map.feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
bonus_box.add_child(_map.feat_value)
# 竖分隔线(底栏分组线——亮面板上低对比细线)
@@ -221,10 +222,10 @@ static func _vdiv() -> ColorRect:
func _draw_hp_bar(bar: Control) -> void:
# 迷你血条(深底 UiKit.HP_BAR_BACK #28221C + 小人蓝填充——与战斗站桩血条同语言,
# 「蓝只说己方」§4.2)。比例 = hero_hp / effective_max_hp
if _map._runner == null:
if _map.runner == null:
return
var max_hp := maxi(1, _map._runner.effective_max_hp())
var ratio := clampf(float(_map._runner.hero_hp) / float(max_hp), 0.0, 1.0)
var max_hp := maxi(1, _map.runner.effective_max_hp())
var ratio := clampf(float(_map.runner.hero_hp) / float(max_hp), 0.0, 1.0)
var full := Rect2(Vector2.ZERO, bar.size)
bar.draw_rect(full, COL_HP_BAR_BACK)
if ratio > 0.0:
@@ -234,8 +235,8 @@ func _draw_hp_bar(bar: Control) -> void:
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
# 特性持有投影(组合根投喂逐件文案;空文案=行隐藏)
func set_feat_holdings(text: String) -> void:
_map._feat_value.text = text
_map._feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文)
_map.feat_value.text = text
_map.feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文)
func set_abandon_available(v: bool) -> void:
@@ -245,8 +246,8 @@ func set_abandon_available(v: bool) -> void:
# 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。
# 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。
func _on_abandon_pressed() -> void:
if not _map._abandon_timer.is_armed():
_map._abandon_timer.arm(CONFIRM_WINDOW_S)
if not _map.abandon_timer.is_armed():
_map.abandon_timer.arm(CONFIRM_WINDOW_S)
abandon_btn.text = "再点一次确认放弃——不可逆"
if ArmedTimer.motion_still():
abandon_btn.modulate = Color(1, 1, 1, 0.75) # 减动效静态基色(脉冲层不再驱动)
@@ -256,45 +257,45 @@ func _on_abandon_pressed() -> void:
func _reset_abandon_confirm() -> void:
_map._abandon_timer.disarm()
_map.abandon_timer.disarm()
abandon_btn.modulate = Color(1, 1, 1, 1)
abandon_btn.text = "放弃" # 文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed)
func _process(delta: float) -> void:
if not _map._abandon_timer.is_armed():
if not _map.abandon_timer.is_armed():
return
_map._abandon_timer.tick(delta)
if not _map._abandon_timer.is_armed():
_map.abandon_timer.tick(delta)
if not _map.abandon_timer.is_armed():
return # 窗口到点回落(expired 信号已驱动 _reset_abandon_confirm)
# 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪——档判据单点 motion_still)
if ArmedTimer.motion_still():
abandon_btn.modulate = Color(1, 1, 1, 0.75)
else:
var k := 0.5 + 0.5 * sin(_map._abandon_timer.elapsed * TAU)
var k := 0.5 + 0.5 * sin(_map.abandon_timer.elapsed * TAU)
abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k)
# 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新)
func open_choices(cands: Array, runner: LevelRunner) -> void:
var was_visible := visible # 进入转场判据(就地刷新/模态栈恢复不重放淡入)
_map._runner = runner
_map.runner = runner
_hover_id = -1
_reset_abandon_confirm()
var visited: Array[int] = []
if runner != null:
visited = runner.visited_ids()
if cands.size() > 1:
_map._title.text = TITLE_FORK
_map.title.text = TITLE_FORK
elif visited.is_empty():
_map._title.text = TITLE_ENTRY # 入口停点(尚未走出第一步)
_map.title.text = TITLE_ENTRY # 入口停点(尚未走出第一步)
else:
_map._title.text = TITLE_SINGLE
_map.title.text = TITLE_SINGLE
_cand_ids = []
for cand in cands:
_cand_ids.append(_id_of(cand))
_fit_canvas(_map._runner.nodes())
_apply_level_tint(int(_map._runner.level_row.get("level_index", 0)))
_fit_canvas(_map.runner.nodes())
_apply_level_tint(int(_map.runner.level_row.get("level_index", 0)))
visible = true
if not was_visible:
# 独立屏淡入(spec Transitions:约 200ms;alpha 淡入非明度变化——art-bible §2 明度恒定)
@@ -321,7 +322,7 @@ func _fit_canvas(nodes: Array) -> void:
_content_size = Vector2(LAYER_BASE_X + maxl * LAYER_STEP_X + NODE_HALF.x + 8.0,
SLOT_BASE_Y + maxslot * SLOT_STEP_Y + NODE_HALF.y + 24.0) # +24 = 类型文字标签行
_content_size = _content_size.max(Vector2(620, 256)) # 下限(抬高容纳标签行)
_map._canvas.custom_minimum_size = _content_size
_map.canvas.custom_minimum_size = _content_size
# 换层色板渐变挂点(art-bible §2.7 下探过渡切片档——关卡结算确认→下一关→独立屏淡入链路上的
@@ -332,39 +333,39 @@ const BOARD_TINTS := {1: Color(1, 1, 1), 2: Color(0.84, 0.99, 1.28), 3: Color(0.
func _apply_level_tint(level_index: int) -> void:
if _map._layer_decor.noise_rect == null or level_index == _map._layer_decor.tint_level:
if _map.layer_decor.noise_rect == null or level_index == _map.layer_decor.tint_level:
return
_map._layer_decor.tint_level = level_index
_map.layer_decor.tint_level = level_index
var target: Color = BOARD_TINTS.get(level_index, Color(1, 1, 1))
var tw := create_tween()
tw.tween_property(_map._layer_decor.noise_rect, "modulate",
tw.tween_property(_map.layer_decor.noise_rect, "modulate",
Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持
# 状态栏投喂(命令式纪律——组合根经 level_map_status_payload 取成品后喂入;零推送信号订阅、零核心态拼装)
func apply_status(payload: Dictionary) -> void:
_map._level_label.text = String(payload["level_label"])
_map._gp_value.text = String(payload["gp_text"])
_map._hp_value.text = String(payload["hp_text"])
_map.level_label.text = String(payload["level_label"])
_map.gp_value.text = String(payload["gp_text"])
_map.hp_value.text = String(payload["hp_text"])
if bool(payload["hp_bar_redraw"]):
_map._hp_bar.queue_redraw()
_map._attr_value.text = String(payload["attr_text"])
_map._bonus_value.text = String(payload["bonus_text"])
_map.hp_bar.queue_redraw()
_map.attr_value.text = String(payload["attr_text"])
_map.bonus_value.text = String(payload["bonus_text"])
# 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings)
_map._bonus_row.visible = bool(payload["bonus_row_visible"]) or _map._feat_value.text != ""
_map.bonus_row.visible = bool(payload["bonus_row_visible"]) or _map.feat_value.text != ""
# ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ----
func _on_canvas_input(ev: InputEvent) -> void:
if _map._runner == null or not visible:
if _map.runner == null or not visible:
return
if ev is InputEventMouseMotion:
var nid := _candidate_at(ev.position) # gui_input 事件坐标 = 接收控件局部坐标(push_input 同成立)
if nid != _hover_id:
_hover_id = nid
_map._canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW
_map._canvas.queue_redraw()
_map.canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW
_map.canvas.queue_redraw()
elif ev is InputEventMouseButton and ev.button_index == MOUSE_BUTTON_LEFT and ev.pressed:
var nid2 := _candidate_at(ev.position)
if nid2 >= 0:
@@ -373,10 +374,10 @@ func _on_canvas_input(ev: InputEvent) -> void:
# 候选命中:位置 = 布局公式(与 _draw_graph 同源),矩形 = 节点方块 + 外扩容差;非候选格不响应
func _candidate_at(local_pos: Vector2) -> int:
if _map._runner == null:
if _map.runner == null:
return -1
var nodes: Array[Variant] = _map._runner.nodes()
var pos := _layout(nodes, _map._canvas)
var nodes: Array[Variant] = _map.runner.nodes()
var pos := _layout(nodes, _map.canvas)
for nid in _cand_ids:
var node := _node_by_id(nid, nodes)
if node.is_empty():
@@ -391,7 +392,7 @@ func _candidate_at(local_pos: Vector2) -> int:
func _submit_choice(nid: int) -> void:
# 独立屏分支:战斗/Boss 节点 = 出屏切回战斗屏;奖励/恢复/商店节点 =
# 本屏保持激活——兑付覆盖层由节点事件在本屏域内弹出(模态栈遮挡恢复语义复用),兑付确认后回图刷新
var node := _node_by_id(nid, _map._runner.nodes())
var node := _node_by_id(nid, _map.runner.nodes())
var t := String(node.get("type", ""))
if t != "reward" and t != "rest" and t != "shop":
visible = false
@@ -400,13 +401,13 @@ func _submit_choice(nid: int) -> void:
# 探针复用:按候选序号走图上命中同路径(候选节点中心坐标 → 命中检测 → 提交——布局决策 2 探针面改造)
func press_candidate(index: int) -> void:
if index < 0 or index >= _cand_ids.size() or _map._runner == null:
if index < 0 or index >= _cand_ids.size() or _map.runner == null:
return
var nodes: Array[Variant] = _map._runner.nodes()
var nodes: Array[Variant] = _map.runner.nodes()
var node := _node_by_id(int(_cand_ids[index]), nodes)
if node.is_empty():
return
var pos: Vector2 = _layout(nodes, _map._canvas)[node]
var pos: Vector2 = _layout(nodes, _map.canvas)[node]
var nid := _candidate_at(pos) # 节点中心必命中自身(命中面含节点方块)
if nid >= 0:
_submit_choice(nid)
@@ -414,7 +415,7 @@ func press_candidate(index: int) -> void:
func _id_of(node: Dictionary) -> int:
# 编号显示经行进器读面(编号空间解析单一出口——单链 = 下标 / 图 = node_depth)
return _map._runner.node_id_of(node)
return _map.runner.node_id_of(node)
# ---- 节点呈现决策面(信息迷雾 v2.1:类型仅在可见集内出示,Boss 标记不入雾)----
@@ -440,9 +441,9 @@ static func node_display(node: Dictionary, nid: int, type_vis: Array,
# ---- 整图自绘(程序化占位——分层布局:层 = 入口 BFS 深度,同层垂直排布)----
func _draw_graph(canvas: Control) -> void:
if _map._runner == null or not visible:
if _map.runner == null or not visible:
return
_map._drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面)
_map.drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面)
var ctx := _draw_context(canvas)
_draw_passage_layer(canvas, ctx)
for node in ctx["nodes"]:
@@ -451,16 +452,16 @@ func _draw_graph(canvas: Control) -> void:
## 绘制上下文束(单次取数——布局/字体/编号空间/可见集,绘制域共享只读面)
func _draw_context(canvas: Control) -> Dictionary:
var nodes: Array[Variant] = _map._runner.nodes()
var nodes: Array[Variant] = _map.runner.nodes()
return {
"nodes": nodes,
"pos": _layout(nodes, canvas),
"font": get_theme_default_font(), # 绘制路径字体单次取用(三处 draw_string 共享——零语义差)
"current_id": _map._runner.current_node_id(),
"current_id": _map.runner.current_node_id(),
# 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析
"visited": _map._runner.visited_ids(),
"cand_ids": _map._runner.next_candidate_ids(),
"type_vis": _map._runner.type_visible_ids(), # 本停点类型可见集(每帧一取——纯函数零状态)
"visited": _map.runner.visited_ids(),
"cand_ids": _map.runner.next_candidate_ids(),
"type_vis": _map.runner.type_visible_ids(), # 本停点类型可见集(每帧一取——纯函数零状态)
}
@@ -525,7 +526,7 @@ func _draw_room(canvas: Control, node: Dictionary, ctx: Dictionary) -> void:
var label_rec := _draw_room_label(canvas, rect, disp, nid, ctx)
rec["draw_label"] = bool(label_rec.get("draw_label", false))
rec["label_text"] = String(label_rec.get("label_text", ""))
_map._drawn_disp[nid] = rec
_map.drawn_disp[nid] = rec
## 节点图标域(20px 烘焙 1:1 消费;双色墨制=乘法调制——当前金底深墨,暗底系 WARM 原色;
@@ -536,7 +537,7 @@ func _draw_room_icon(canvas: Control, node: Dictionary, disp: Dictionary, ctx: D
var p: Vector2 = ctx["pos"][node]
var col := _room_color(node, ctx)
var t := String(node.get("type", ""))
var icon: Texture2D = _map._layer_decor.node_icons.get(t)
var icon: Texture2D = _map.layer_decor.node_icons.get(t)
if icon != null:
var tint := UiKit.ICON_TINT_ON_GOLD if col == COL_ROOM_CURRENT else Color(1.0, 1.0, 1.0)
canvas.draw_texture_rect(icon, Rect2(p - Vector2(NODE_ICON_PX, NODE_ICON_PX) / 2.0,
@@ -580,20 +581,20 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary:
# ---- 探针只读出口(形态仿 fx_probe 先例:只读快照) ----
# 选路布局只读快照(node → 局部位置;画布固定取现行 _map._canvas,调用侧零私有域触达)
# 选路布局只读快照(node → 局部位置;画布固定取现行 _map.canvas,调用侧零私有域触达)
func layout_probe(nodes: Array) -> Dictionary:
return _layout(nodes, _map._canvas)
return _layout(nodes, _map.canvas)
# 路由画布只读快照(origin=全局原点;min_size=最小拟合尺寸——画布拟合断言消费面)
func canvas_probe() -> Dictionary:
return {"origin": _map._canvas.get_global_position(), "min_size": _map._canvas.custom_minimum_size}
return {"origin": _map.canvas.get_global_position(), "min_size": _map.canvas.custom_minimum_size}
# 类型可见性绘制只读记录(id → 本帧实际绘制事实:图标/文字/描边/文案;_draw_graph 在实际绘制点写入——
# 接线层断言消费点:绕开决策面直接画,这里就露出「雾下也画了」)
func display_probe() -> Dictionary:
return _map._drawn_disp
return _map.drawn_disp
# 分层(layout/fit 共享件):layer = 入口 BFS 深度;同层 slot 垂直排布;未达节点兜底排末层
@@ -637,7 +638,7 @@ func _entry(nodes: Array) -> Dictionary:
func _out_ids(node: Dictionary, _nodes: Array) -> Array:
return _map._runner.node_out_ids(node)
return _map.runner.node_out_ids(node)
func _node_by_id(nid: int, nodes: Array) -> Dictionary:
@@ -0,0 +1,50 @@
// VERTICAL SLICE - NOT FOR PRODUCTION
// 封印印记(卡④ 法阵化批)——环+十字+环外压暗光圈,canvas_item 距离场实时绘制(无贴图缩放,
// 格边 26–46px 连续可变下任意格边清晰)。
// 事实源:art-bible v3.41 §3.2「封印印记」(形/画法/光圈/层序纪律)+ §8.2「封印印记参数表」;
// gdd/ux/battle.md「棋盘格尺寸公式」(格边 clampi 26–46)。
// 纪律:参数五枚+色全由 gd 侧逐次注入(参数单点 = ui_kit SEAL_* 五常量——禁在此改值,
// 下方默认值仅为兜底且与 §8.2 同值);光圈不出格(半径夹 cell_px/2);
// 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动 = 接触带微光);印记与光圈同用一色(光圈只改 alpha)。
// 坐标口径:VERTEX 为格件本件局部像素坐标(0..cell_px),不依赖 UV(同 cell_type_glow 口径)。
shader_type canvas_item;
render_mode blend_mix;
uniform float cell_px : hint_range(8.0, 128.0) = 46.0;
uniform float ring_frac = 0.82;
uniform float wall_frac = 0.11;
uniform float arm_frac = 0.156;
uniform float halo_frac = 0.09;
uniform float halo_alpha : hint_range(0.0, 1.0) = 0.50;
uniform float halo_gamma = 1.6;
uniform vec4 mark_col : source_color = vec4(0.0, 0.0, 0.0, 1.0);
varying vec2 v_local;
void vertex() {
v_local = VERTEX;
}
void fragment() {
vec2 d2 = v_local - vec2(cell_px * 0.5);
float r = length(d2);
float ring_r = 0.5 * cell_px * ring_frac;
float wall = wall_frac * 2.0 * ring_r;
float arm = arm_frac * 2.0 * ring_r;
float halo_w = 0.5 * cell_px * halo_frac;
// 印记体:环带(ring_r-wall/2 ≤ r ≤ ring_r)或十字(轴向带内且 r < ring_r——四端顶到环内沿)
bool in_ring = (r <= ring_r) && (r >= ring_r - wall * 0.5);
bool in_arm = (r < ring_r) && ((abs(d2.x) < arm * 0.5) || (abs(d2.y) < arm * 0.5));
float a = 0.0;
if (in_ring || in_arm) {
a = 1.0;
} else if (r > ring_r) {
// 环外压暗光圈:自环外缘向外衰减(半径夹 cell_px/2——不出格、不外溢邻格)
float rc = min(r, cell_px * 0.5);
float t = clamp((rc - ring_r) / max(halo_w, 0.001), 0.0, 1.0);
a = halo_alpha * pow(1.0 - t, halo_gamma);
}
COLOR = vec4(mark_col.rgb, mark_col.a * a);
}
@@ -0,0 +1 @@
uid://bqgctor0wpjt7
@@ -0,0 +1,42 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## SealMarkLayer——封印印记的独占承载子件(挂 BoardCellUnit 之下、1:1 贴格;复用 cell_type_glow_layer
## 的子件模式:材质独占、全幅 draw_rect、非零 alpha 触发——不复制其函数体,微光层件零改动)。
## 层序:印记层在 CellUnit 子树内(低于 BoardView 层的小人精灵——hero 精灵同层后挂天然在上);
## 迷雾态(fog_state != 0)整层隐藏由宿主 fog_state setter 联动承载(§6.5「封印随格隐没」)。
## 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动=接触带微光);参数与色全由 gd 注入
##(单点 = ui_kit SEAL_* 常量——本件禁内联色值与参数字面值)。
extends Node2D
const _SEAL_SHADER := preload("res://presentation/views/seal_mark.gdshader")
var _mat: ShaderMaterial = null
var _edge := 0.0
# 作用集侧唯一入口(on=false 仅隐不撤——子件生命周期归宿主 _sync_seal_layer)
func setup(edge: float, on: bool) -> void:
_edge = edge
visible = on
if not on:
return
if _mat == null:
_mat = ShaderMaterial.new()
_mat.shader = _SEAL_SHADER
material = _mat
_mat.set_shader_parameter("cell_px", edge)
_mat.set_shader_parameter("ring_frac", UiKit.SEAL_RING_FRAC)
_mat.set_shader_parameter("wall_frac", UiKit.SEAL_WALL_FRAC)
_mat.set_shader_parameter("arm_frac", UiKit.SEAL_ARM_FRAC)
_mat.set_shader_parameter("halo_frac", UiKit.SEAL_HALO_FRAC)
_mat.set_shader_parameter("halo_alpha", UiKit.SEAL_HALO_ALPHA)
_mat.set_shader_parameter("halo_gamma", UiKit.SEAL_HALO_GAMMA)
_mat.set_shader_parameter("mark_col", UiKit.SEAL_BLACK)
queue_redraw()
func _draw() -> void:
if _edge <= 0.0:
return
# 一格全幅:片元着色器按距离场产出环+十字+环外光圈。
# 输入 alpha 取 1/255(非零)——全透明几何在部分批次路径下会被整条跳过,着色器就跑不到。
draw_rect(Rect2(Vector2.ZERO, Vector2(_edge, _edge)), Color(1, 1, 1, 1.0 / 255.0))
@@ -0,0 +1 @@
uid://oyfwx5p78pud
+2 -2
View File
@@ -85,7 +85,7 @@ func _build_fullscreen_row(box: VBoxContainer) -> void:
var fs_lab := UiKit.label("全屏",UiKit.FS_BODY, false, UiKit.INK_LABEL)
fs_lab.custom_minimum_size = Vector2(140, 0) # 同上
fs_row.add_child(fs_lab)
_fs_btn = UiKit._switch_toggle(false)
_fs_btn = UiKit.switch_toggle(false)
_fs_btn.toggled.connect(_on_fullscreen_toggled)
fs_row.add_child(_fs_btn)
_fs_st = UiKit.label("关",UiKit.FS_HINT, true, UiKit.INK_LABEL)
@@ -102,7 +102,7 @@ func _build_motion_row(box: VBoxContainer) -> void:
var motion_lab := UiKit.label("结算演出动画",UiKit.FS_BODY, false, UiKit.INK_LABEL)
motion_lab.custom_minimum_size = Vector2(140, 0) # 同上
motion_row.add_child(motion_lab)
_motion_btn = UiKit._switch_toggle(true)
_motion_btn = UiKit.switch_toggle(true)
_motion_btn.toggled.connect(_on_motion_toggled)
motion_row.add_child(_motion_btn)
_motion_st = UiKit.label("完整",UiKit.FS_HINT, true, UiKit.INK_LABEL)
+50 -57
View File
@@ -3,8 +3,9 @@
## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。
## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。
## 内部状态两件(u4 拆件——st=身份命值态 standee_vitals、mo=资产演出态 standee_motion,_init 自建);
## 外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员(_intent_charge——theme
## 源锚句所在行逐字保留)与三个演出探针计数器住宿主。
## 子件往来走公开面(pres封装 A2——身份经 configure_identity 交付、死亡/意图/闪白走行为方法、
## 绘制读数走公开读法);外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员
## (_intent_charge——theme 源锚句所在行逐字保留)与三个演出探针计数器住宿主。
extends Node2D
const CHARGE_SKILL_IDS := ["heavy"]
@@ -36,8 +37,8 @@ var _flash_count := 0 # 探针出口计数(受击闪白触发次
func _init(kind: String = "enemy") -> void:
st = _VITALS.new(self)
mo = _MOTION.new(self)
st._kind = kind
mo._hero_state = _MOTION.HeroState.IDLE
st.configure_identity(kind, "", "", "light")
mo.reset_state()
func _efont() -> Font:
@@ -45,17 +46,17 @@ func _efont() -> Font:
func display_name() -> String:
return st._display_name
return st.display_name()
# 敌单位身份出口(d12 站桩解析主键——同名敌按 id 精确匹配;主角站桩恒空串)
func enemy_id() -> String:
return st._enemy_id
return st.enemy_id()
# 意图徽标探针出口
func intent_probe() -> Dictionary:
return {"visible": mo._intent_visible, "text": mo._intent_glyph}
return {"visible": mo.intent_visible(), "text": mo.intent_glyph()}
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
@@ -63,10 +64,10 @@ func intent_probe() -> Dictionary:
# name_font_size = 名字行字号档实读(迭代 46 卡B——提档 FS_HINT 14 的几何单点出口))
func name_line_probe() -> Dictionary:
var f := _efont()
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
var tex := UiKit.load_tex(_armor_tex_path(st.armor()))
var plate := _name_plate_rect(f, tex)
return {"name": st._display_name,
"armor_icon": _armor_tex_path(st._armor) if tex != null else String(ARMOR_ICONS.get(st._armor, "◇")),
return {"name": st.display_name(),
"armor_icon": _armor_tex_path(st.armor()) if tex != null else String(ARMOR_ICONS.get(st.armor(), "◇")),
"armor_tex_bound": tex != null,
"plate": plate, "baseline_y": _name_baseline(plate).y,
"plinth_top_y": _pedestal_rect().position.y,
@@ -79,23 +80,21 @@ func name_line_probe() -> Dictionary:
# data = 战斗结算 presentation_snapshot enemies[] 投影:{order, display_name, hp, max_hp, enemy_id,
# pending_skill/pending_type/pending_name, effects[](该敌自己名下增益——各立绘各读各的,增益状态 v1.5 个体投影)}
func set_data(d: Dictionary) -> void:
st._display_name = String(d.get("display_name", "敌人"))
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))))
st.configure_identity(st.kind(), String(d.get("display_name", "敌人")), String(d.get("enemy_id", "")), String(d.get("armor_type", "light")))
st.set_max_hp(float(maxi(1, int(d.get("max_hp", 1)))))
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", []))
queue_redraw()
if st._dead and int(d.get("hp", 0)) > 0:
if st.is_dead() and int(d.get("hp", 0)) > 0:
_revive()
func set_hero(hp: int, max_hp: int) -> void:
st._hp_max = float(maxi(1, max_hp))
st.set_max_hp(float(maxi(1, max_hp)))
st.apply_hp(hp)
if st._dead and hp > 0:
if st.is_dead() and hp > 0:
_revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义)
@@ -128,21 +127,21 @@ func set_effects_cleared_feedback() -> void:
func _set_order(i: int) -> void:
# 顺位徽标 = 行主序生成序(固定顺位——敌人系统 顺位标记义务)
st._order = i
st.set_order(i)
queue_redraw()
# 摇定动作渲染(三档可见性):pending_type = 战斗结算 快照投影(basic/damage/buff/debuff;
# "" = 未摇定/死亡残位)。不显示技能名——类型级图标 + 一次性动作动画。
func set_pending(ptype: String, pid: String) -> void:
if st._dead:
if st.is_dead():
_intent_charge = false
mo._intent_visible = false
mo.hide_intent()
queue_redraw()
return
if ptype == "" or ptype == "basic":
_intent_charge = false
mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
mo.hide_intent() # 普通攻击零表示(完全不可预测——§2.4)
queue_redraw()
return
var glyph := "⚔" # 攻击系(damage)
@@ -156,9 +155,7 @@ 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)
mo._intent_glyph = glyph
mo._intent_col = col
mo._intent_visible = true
mo.show_intent(glyph, col)
mo.play_intent_motion(_intent_charge)
@@ -168,9 +165,9 @@ func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
func mark_dead() -> void:
if st._dead:
if st.is_dead():
return
st._dead = true
st.set_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))
@@ -178,7 +175,7 @@ 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
mo._intent_visible = false # 死亡残位意图退位
mo.hide_intent() # 死亡残位意图退位
st.apply_hp(0)
if slot_row != null:
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏
@@ -245,24 +242,20 @@ func _spawn_hit_spark(i: int, center: Vector2) -> void:
func is_dead() -> bool:
return st._dead
return st.is_dead()
# 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行)
func flash_hit() -> void:
_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()
st._hp_flash_tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
st._hp_flash_tw.tween_property(self, "modulate", Color(1, 1, 1) if not st._dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
st.play_hp_flash() # tween 逻辑归位 vitals 自持(pres封装 A2——宿主零自建 tween)
func _revive() -> void:
st._dead = false
st.set_dead(false)
modulate = Color(1, 1, 1)
rotation_degrees = 0.0
if st._kind == "hero":
if st.kind() == "hero":
mo.reset_state()
if slot_row != null:
slot_row.visible = true
@@ -272,16 +265,16 @@ func _revive() -> void:
func _portrait_glyph() -> String:
# 字形回退仅作缺位兜底(八敌真图落位;相对体量语义由 48px 档统一比例承载)。
# 缺位态:主角「人」/死亡「✕」/其余「敌」。
if st._kind == "hero":
if st.kind() == "hero":
return "人"
if st._dead:
if st.is_dead():
return "✕"
return "敌"
# 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面)
func portrait_probe() -> Dictionary:
var sp: Sprite2D = mo._portrait_sprite
var sp: Sprite2D = mo.portrait_sprite()
if sp == null or sp.texture == null:
return {"has_tex": false, "display_size": Vector2.ZERO, "display_scale": 0.0}
var src := Vector2(sp.texture.get_width(), sp.texture.get_height())
@@ -297,7 +290,7 @@ func register_hero_state(state: String, tex: Texture2D) -> void:
# 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内)
func set_hero_state(state: String) -> void:
mo.apply_hero_state(state, st._kind == "hero")
mo.apply_hero_state(state, st.kind() == "hero")
# 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变)
@@ -306,7 +299,7 @@ func hero_state_probe() -> Dictionary:
func _portrait_color() -> Color:
return UiKit.HERO_BLUE if st._kind == "hero" else UiKit.RED_DEEP
return UiKit.HERO_BLUE if st.kind() == "hero" else UiKit.RED_DEEP
# 立绘盒矩形(世界局部):(0,44)-(box.x, box.y-36)——贴图与字形共用
@@ -337,9 +330,9 @@ func _armor_tex_path(armor: String) -> String:
func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2:
var icon_w := 24.0
if tex == null:
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")),
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st.armor(), "◇")),
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var name_w := f.get_string_size(st.display_name(), HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var total_w := icon_w + 3.0 + name_w
return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0)
@@ -362,7 +355,7 @@ func _draw() -> void:
_draw_hp_bar(f)
_draw_portrait(f)
_draw_intent_badge(f)
if st._kind == "enemy":
if st.kind() == "enemy":
_draw_armor_name_line(f)
@@ -406,8 +399,8 @@ func _draw_plinth_stretched(tex: Texture2D, ped: Rect2) -> void:
## 10px 条带垂直居中;数字=目标值即时刷新,条 tween 只管填充宽度;死残位 0/max 照常可读)
func _draw_hp_bar(f: Font) -> void:
var bar_x := 8.0
if st._kind == "enemy":
var order_txt := String("①②③"[clampi(st._order - 1, 0, 2)])
if st.kind() == "enemy":
var order_txt := String("①②③"[clampi(st.order() - 1, 0, 2)])
f.draw_string(get_canvas_item(), Vector2(bar_x, 12), order_txt,
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.GOLD_BRIGHT)
bar_x += 20.0
@@ -416,7 +409,7 @@ func _draw_hp_bar(f: Font) -> void:
var fill_w := bar_w * st.hp_value
if fill_w > 0.0:
draw_rect(Rect2(bar_x, 2, fill_w, 10), UiKit.HP_BAR_FILL) # 直角填充(危险红——§4.2 血条显式句)
var hp_txt := "%d/%d" % [st._hp_cur, int(st._hp_max)]
var hp_txt := "%d/%d" % [st.hp_cur(), int(st.hp_max())]
var hts: Vector2 = f.get_string_size(hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11)
# 对比度垫牌(白 α.95 直压蓝条 2.67:1 违 §7.2;
# 中央垫 HP_BAR_BACK 深素牌 = 14.3:1;牌宽=字宽+8〔白名单取 8〕,高=条带全高;§4.1 数字色牌原则)
@@ -428,7 +421,7 @@ func _draw_hp_bar(f: Font) -> void:
## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退
func _draw_portrait(f: Font) -> void:
if mo._portrait_sprite != null and mo._portrait_sprite.texture != null and mo._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()
@@ -436,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(mo._glyph_rot), Vector2.ONE)
draw_set_transform(center, deg_to_rad(mo.intent_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())
@@ -445,24 +438,24 @@ func _draw_portrait(f: Font) -> void:
## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画
func _draw_intent_badge(f: Font) -> void:
if not mo._intent_visible:
if not mo.intent_visible():
return
var icenter := Vector2(box.x * 0.5, 86.0)
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)
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), mo._intent_glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复)
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
## 「文字禁直接压地台」素底落法——牌底沿 = 地台顶,几何单点 _name_plate_rect);护甲图标 =
## 24×24 PNG(甲类 1×,缺位回退字形,色随甲档亮档族);名字 = 暖白(暗底双色墨制)
func _draw_armor_name_line(f: Font) -> void:
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
var tex := UiKit.load_tex(_armor_tex_path(st.armor()))
var plate := _name_plate_rect(f, tex)
draw_rect(plate, UiKit.HP_BAR_BACK, true)
var baseline := _name_baseline(plate)
@@ -470,14 +463,14 @@ func _draw_armor_name_line(f: Font) -> void:
if tex != null:
draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false)
else:
var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇"))
var armor_txt := String(ARMOR_ICONS.get(st.armor(), "◇"))
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制)
match st._armor:
match st.armor():
"ward":
col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档
"heavy":
col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col)
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name,
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st.display_name(),
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM) # v3.5 暗底双色墨制
+38
View File
@@ -147,3 +147,41 @@ func play_intent_motion(charge: bool) -> void:
# 三态探针出口(fx_probe——当前态名+注册态集合;宿主 hero_state_probe 读件投影,外形零变)
func state_probe() -> Dictionary:
return {"state": _HS_NAME.get(_hero_state, "idle"), "registered": _hero_tex.keys()}
# ---- 意图徽标行为与绘制读口(pres封装 A2——宿主经此读写,字段保持私有)----
# 意图徽标显隐/设定(宿主 set_pending/mark_dead 三处同型归位——显示后由宿主拍 play_intent_motion)
func show_intent(glyph: String, col: Color) -> void:
_intent_glyph = glyph
_intent_col = col
_intent_visible = true
func hide_intent() -> void:
_intent_visible = false
# 绘制路径读投影(宿主 _draw 每帧调用——逐 getter 免逐帧字典分配)
func intent_visible() -> bool:
return _intent_visible
func intent_glyph() -> String:
return _intent_glyph
func intent_col() -> Color:
return _intent_col
func intent_scale() -> float:
return _intent_scale
func intent_rot() -> float:
return _glyph_rot
func portrait_sprite() -> Sprite2D:
return _portrait_sprite
+63
View File
@@ -26,6 +26,69 @@ func _init(owner: Node2D) -> void:
_owner = owner
# ---- 公开交付与读投影面(pres封装 A2——宿主经此读写,字段保持私有)----
# 身份四元组一次交付(生成期/配置期同口——宿主 _init 与 set_data 共用)
func configure_identity(kind: String, display_name: String, enemy_id: String, armor: String) -> void:
_kind = kind
_display_name = display_name
_enemy_id = enemy_id
_armor = armor
func set_max_hp(v: float) -> void:
_hp_max = v
func set_order(i: int) -> void:
_order = i
func set_dead(v: bool) -> void:
_dead = v
func is_dead() -> bool:
return _dead
func kind() -> String:
return _kind
func armor() -> String:
return _armor
func display_name() -> String:
return _display_name
func enemy_id() -> String:
return _enemy_id
func order() -> int:
return _order
func hp_cur() -> int:
return _hp_cur
func hp_max() -> float:
return _hp_max
# 受击闪白归位(宿主 flash_hit 转调——tween 句柄留本件自持,目标=宿主 modulate)
func play_hp_flash() -> void:
if _hp_flash_tw != null and _hp_flash_tw.is_valid():
_hp_flash_tw.kill()
_hp_flash_tw = _owner.create_tween()
_hp_flash_tw.tween_property(_owner, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
_hp_flash_tw.tween_property(_owner, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积
func apply_hp(hp: int) -> void:
_hp_cur = clampi(hp, 0, int(_hp_max))
-4
View File
@@ -16,7 +16,6 @@ extends RefCounted
# dungeon 套(禁同板混渲染);dungeon 套亦缺位 → 程序化平色回退(fresh checkout 冒烟不炸)。
const TILE_WALL_NAMES := ["wall_center", "wall_center_crack", "wall_center_speckle", "wall_n", "wall_s", "wall_w", "wall_nw", "wall_sw", "wall_hrun", "wall_vrun", "wall_end_n", "wall_end_s", "wall_end_w", "wall_lone", "wall_in_nw", "wall_in_sw"]
const TILE_WALL_FMT := "res://assets/art/board/terrain_%s_%s.png" # %s=件名 %s=board_type
const TILE_SEAL_MARK := "res://assets/art/board/seal_mark.png" # 封印印记纹理(独立可选件)
const TILE_FLOOR_FMT := "res://assets/art/board/terrain_floor_%s_%d.png" # %s=board_type 1..%d 变体
const TILE_FLOOR_VARIANTS := 5 # 地面瓦片变体数(词表 §一 floor_1..5)
@@ -45,7 +44,6 @@ const WALL_CENTER_PICK := ["wall_center", "wall_center", "wall_center", "wall_ce
var tiles: Dictionary[String, Texture2D] = {} # 词表键 → Texture2D(镜像件已预翻转)
var floor_tiles: Array[Texture2D] = [] # 地面瓦片五变体(下标 0..4 = floor_1..5)
var tiles_ok := false # false = 素材缺位/未装载——调用方走程序化平色原路径
var seal_tex: Texture2D = null # 封印印记纹理(缺盘=null=字形回退——独立可选件不连瓦片整批)
var tiles_board_type := "dungeon" # 当前装载瓦片套的 board_type(rebuild 按板切换)
var digit_dark_ink := true # 数字墨族按板自适应(true=深墨三档)。缺盘平色回退态 #B39A78 裸土
# 为中明度底——深墨族 CR 4.96 达标、亮墨族仅 2.2,故缺盘默认深墨;瓦片装载后按实测分位数覆写
@@ -107,8 +105,6 @@ func load_board_tiles(board_type: String = "dungeon") -> void:
floor_tiles = floors
tiles_board_type = board_type
tiles_ok = true
# 封印印记纹理(独立可选件:缺盘=字形回退,不触发瓦片整批回落语义)
seal_tex = UiKit.load_tex(TILE_SEAL_MARK)
# 整批回落:请求套缺位 → 回落 dungeon 套(禁同板混渲染);dungeon 本身缺位 =
+11 -3
View File
@@ -32,6 +32,14 @@ const GOLD_DONE := Color8(104, 86, 44) # 已过节点暗金·关卡画面
const SEAL_BLACK := Color8(0, 0, 0) # 封印黑 #000000——封印宣告色(紫色与微光蓝族色相冲突故弃;
# 黑色无色相,天然避开禁入带——与妨害紫 281° 各留 12-14° 间距)
# 封印印记参数(art-bible §8.2「封印印记参数表」单点对照——改值须本表与 §3.2 同批修订,禁在此改值):
const SEAL_RING_FRAC := 0.82 # 环外径 = 0.82 × 格边
const SEAL_WALL_FRAC := 0.11 # 环壁厚 = 0.11 × 环径
const SEAL_ARM_FRAC := 0.156 # 十字臂宽 = 0.156 × 环径
const SEAL_HALO_FRAC := 0.09 # 环外光圈宽 = 0.09 × 格边(§8.2 条款定值——层件注入源,实现决定记录在案)
const SEAL_HALO_ALPHA := 0.50 # 环外光圈峰值 alpha
const SEAL_HALO_GAMMA := 1.6 # 环外光圈向外衰减指数
const RED_DEEP := Color8(150, 60, 48) # 深朱(敌卡框/敌像)
const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开危险格"雷"字)
const RED_FORBID := Color8(120, 60, 50) # 禁止框朱
@@ -353,7 +361,7 @@ static func vignette_rect() -> TextureRect:
# 程序纹理循环带,确定性生成零随机。光柱采用连续 alpha:
# 带内 alpha 0→0.05→0 横向过渡+纵向衰减上亮下暗;
# 纹理 filter=显式 LINEAR 白名单(与暗角/地图渐变同血统)
static func _beams_rect() -> TextureRect:
static func beams_rect() -> TextureRect:
var img := Image.create(560, 560, false, Image.FORMAT_RGBA8)
_paint_beams(img)
var tr := TextureRect.new()
@@ -390,7 +398,7 @@ static func _paint_beams(img: Image) -> void:
# 竖向渐变底(demo #v-map linear-gradient(180deg, top 0%, low 40%)——stop 后恒 low)
static func _gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect:
static func gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect:
var g := Gradient.new()
g.offsets = PackedFloat32Array([0.0, stop])
g.colors = PackedColorArray([top, low])
@@ -863,7 +871,7 @@ class SwitchToggle:
_knob.size = Vector2(18, 16)
static func _switch_toggle(initial: bool) -> SwitchToggle:
static func switch_toggle(initial: bool) -> SwitchToggle:
return SwitchToggle.new(initial)