feat(ux): T4 关卡画面实装——level-map 三拍板落地+停点标题+GP 徽章+图上命中探针改造

- 三停点标题定稿(布局决策1):入口='出发——选择首个节点'/单链='继续前进'/岔路='选择下一节点'——台账143「继续前进(战斗房)」措辞矛盾清偿
- 图上点击选路唯一通道(布局决策2):候选按钮整列退役;悬停预承诺=亮金描边加粗4px+通道增亮+指针;命中=布局公式矩形+6px 容差,非候选格不响应
- press_candidate 探针面改造:候选序号→节点中心坐标→同一命中检测→提交(探针走真实命中路径);ui_probe/ui_click_probe 选路段注记同步
- 放弃二次确认(布局决策3):首击转确认态(文案'再点一次确认放弃——不可逆'+危险红脉冲约3秒超时回落/减动效静态),二击提交 abandon_requested;单击不提交=ui_probe 战斗中按下被拒语义保持
- 顶栏:关卡名(level_row.display_name)+GP 常驻徽章(◈+runner.gp,直角细线数据框);底栏状态组(HP 当前/上限+迷你血条/攻击·AC 有效值+加成角标/本关加成三段零加成隐藏)——open_choices 命令式刷新纪律
- 画布字形走 get_theme_default_font(随包字体);旧按钮路径 grep 收净零死引用
- 轻闸:冒烟 557/0+四探针全绿(click_probe 全冒险段经新命中路径含岔路交替)
This commit is contained in:
DKChan committed 2026-09-28 03:23:16 +08:00
1 parent a0ed48fa88
commit b07d11fc18
4 files changed
+252 -59

No files matched your search

+1 -1
View File
@@ -561,7 +561,7 @@ func _on_node_continue() -> void:
func _on_choices_presented(candidates: Array) -> void: func _on_choices_presented(candidates: Array) -> void:
# 零自动推进的停点呈现:整幅节点图 + 候选按钮(战斗胜利/兑付确认统一到达此处) # 零自动推进的停点呈现:整幅节点图 + 图上点击选路(候选按钮退役 2026-09-28——战斗胜利/兑付确认统一到达此处)
if runner == null: if runner == null:
return return
log_view.append_bb(Narrator.route_line(candidates, runner)) log_view.append_bb(Narrator.route_line(candidates, runner))
+248 -55
View File
@@ -1,13 +1,19 @@
# VERTICAL SLICE - NOT FOR PRODUCTION # VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-17(分支节点图批——关卡节点地图 v1.0 图版选路呈现) # Date: 2026-09-17(分支节点图批——关卡节点地图 v1.0 图版选路呈现)
# 节点图选路覆盖层(模态):兑付确认后由组合根打开——整幅节点图(方块地牢观感:节点 = 地牢房间 # 2026-09-28 关卡画面实装批(M-1 迭代 1 T4——level-map.md 三拍板落地):
# 方块、连线 = 通道)+ 当前位置高亮 + 可选下一跳亮边 + 候选按钮。零自动推进的玩家停点语义: # ① 三停点标题定稿(布局决策 1——清偿台账 143:「继续前进(战斗房)」入口措辞矛盾退役)
# 唯一前进通道 = 点候选按钮 → route_chosen(node_id) → 组合根转 runner.pick_next_node。 # ② 图上点击选路唯一通道(布局决策 2)——候选按钮整列退役;候选节点亮金描边 = 可点目标,
# 高内聚边界:只读 runner 成文出口(nodes()/visited_ids()/node_index/next_candidate_ids())做渲染, # 悬停预承诺(描边加粗+指针)后点击提交;press_candidate 探针入口改走图上命中同路径
# 无任何写面;图形为程序化占位质量(占位纪律)。 # ③ 放弃冒险二次确认(布局决策 3)——首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击提交
# 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——台账 119;推翻 冒险结构.md「MVP 无界面入口」 # ④ 顶栏语境:关卡名(level_row.display_name)+ GP 常驻徽章(2026-09-26 二轮拍板去向落地面)
# 后置条款):选路停点 = 天然非战斗期(接口契约:战斗中放弃一律拒绝),按钮常驻本层底部; # ⑤ 底部状态栏(布局决策 4):生命/属性/本关加成三组常读(零加成隐藏),放弃按钮右端异位
# 可见性归组合根(只在冒险模式开——放弃的语义对象是"整局冒险";调试两层无节点图天然不可达)。 # 节点图选路覆盖层(模态):兑付确认后由组合根打开。零自动推进的玩家停点语义:唯一前进通道 =
# 图上点候选节点 → route_chosen(node_id) → 组合根转 runner.pick_next_node。
# 高内聚边界:只读 runner 成文出口(nodes()/visited_ids()/node_index/next_candidate_ids()/level_row/
# hero_hp/gp/effective_*/bonus)做渲染,无任何写面;图形为程序化占位质量(占位纪律)。
# 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——台账 119):选路停点 = 天然非战斗期(接口契约:
# 战斗中放弃一律拒绝),按钮常驻底栏右端;可见性归组合根(只在冒险模式开——放弃的语义对象是
# "整局冒险";调试两层无节点图天然不可达)。
class_name RouteOverlay class_name RouteOverlay
extends Control extends Control
@@ -20,12 +26,30 @@ const COL_ROOM_FUTURE := UiKit.DARK_UPCOMING # 未达——暗
const COL_PASSAGE := UiKit.STONE # 通道——石灰 const COL_PASSAGE := UiKit.STONE # 通道——石灰
const COL_PASSAGE_LIT := UiKit.GOLD_LIT # 候选通道——亮金 const COL_PASSAGE_LIT := UiKit.GOLD_LIT # 候选通道——亮金
const COL_BOSS_BORDER := UiKit.BOSS_PURPLE # Boss 边框——紫赤(色相独占本色;旗朱退役 2026-09-26) const COL_BOSS_BORDER := UiKit.BOSS_PURPLE # Boss 边框——紫赤(色相独占本色;旗朱退役 2026-09-26)
const COL_ABANDON := UiKit.RED_MINE # 破坏性动作红(危险红族)
const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王", "shop": "商"} # 商 = GP+商店房实施批 2026-09-21 const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王", "shop": "商"} # 商 = GP+商店房实施批 2026-09-21
const TITLE_ENTRY := "出发——选择首个节点" # 入口停点(布局决策 1——台账 143 遗留清偿)
const TITLE_SINGLE := "继续前进" # 单链停点
const TITLE_FORK := "选择下一节点" # 岔路停点
const NODE_HALF := Vector2(26, 18) # 节点方块半宽高(52×36 占位画布参数——样张批终稿)
const HIT_PAD := 6.0 # 图上命中外扩(指宽容差)
const CONFIRM_WINDOW_S := 3.0 # 放弃确认态窗口(秒——超时回落)
var _runner = null var _runner = null
var _btn_box: VBoxContainer
var _title: Label 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 _cand_ids: Array = [] # 本停点候选 id 集(图上命中合法集)
var _hover_id := -1 # 悬停预承诺候选(-1 = 无)
var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开) var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开)
var _abandon_armed := false # 二次确认态(首击置位、二击提交、超时/重开回落)
var _confirm_left := 0.0 # 确认态剩余秒数(_process 倒计时+脉冲驱动)
func _init() -> void: func _init() -> void:
@@ -39,77 +63,244 @@ func _init() -> void:
var box := VBoxContainer.new() var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 10) box.add_theme_constant_override("separation", 10)
center.add_child(box) center.add_child(box)
_title = UiKit.label("选择下一跳", 18, true) # 顶栏语境(spec Layout:左关卡名 / 右 GP 常驻徽章——直角细线数据框)
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 12)
box.add_child(top)
_level_label = UiKit.label("", 16, true)
_level_label.add_theme_color_override("font_color", UiKit.WARM)
_level_label.custom_minimum_size.x = 420
top.add_child(_level_label)
var gp_box := PanelContainer.new()
gp_box.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1))
top.add_child(gp_box)
var gp_row := HBoxContainer.new()
gp_row.add_theme_constant_override("separation", 6)
gp_box.add_child(gp_row)
_gp_value = UiKit.label("", 16, true)
_gp_value.add_theme_color_override("font_color", UiKit.GOLD)
gp_row.add_child(_gp_value)
_title = UiKit.label(TITLE_FORK, 18, true)
_title.add_theme_color_override("font_color", UiKit.WARM) _title.add_theme_color_override("font_color", UiKit.WARM)
_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_title) box.add_child(_title)
var canvas := Control.new() # 节点图画布(图上点击 = 唯一提交通道——布局决策 2;mouse_filter STOP 接悬停/点击)
canvas.custom_minimum_size = Vector2(620, 240) _canvas = Control.new()
canvas.draw.connect(_draw_graph.bind(canvas)) _canvas.custom_minimum_size = Vector2(620, 240)
box.add_child(canvas) _canvas.mouse_filter = Control.MOUSE_FILTER_STOP
_btn_box = VBoxContainer.new() _canvas.draw.connect(_draw_graph.bind(_canvas))
_btn_box.add_theme_constant_override("separation", 6) _canvas.gui_input.connect(_on_canvas_input)
box.add_child(_btn_box) box.add_child(_canvas)
abandon_btn = UiKit.button("🏳 放弃冒险(同失败结算——已获大奖励保留)") # 底部状态栏(布局决策 4:生命/属性/本关加成三组 + 放弃按钮右端异位)
abandon_btn.add_theme_color_override("font_color", UiKit.RED_MINE) # 破坏性动作红(危险红族) var bottom := HBoxContainer.new()
abandon_btn.pressed.connect(_emit_abandon) bottom.add_theme_constant_override("separation", 10)
box.add_child(bottom)
bottom.add_child(_status_box("生命", true))
bottom.add_child(_status_box("属性", false))
# 本关加成框(零加成隐藏——spec 布局决策 4)
_bonus_row = HBoxContainer.new()
_bonus_row.add_theme_constant_override("separation", 10)
var bonus_box := PanelContainer.new()
bonus_box.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1))
_bonus_row.add_child(bonus_box)
var bonus_inner := VBoxContainer.new()
bonus_box.add_child(bonus_inner)
bonus_inner.add_child(UiKit.label("本关加成", 12, false))
_bonus_value = UiKit.label("", 15, true)
bonus_inner.add_child(_bonus_value)
bottom.add_child(_bonus_row)
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
bottom.add_child(spacer)
abandon_btn = UiKit.button("放弃冒险(同失败结算——已获大奖励保留)")
abandon_btn.add_theme_color_override("font_color", COL_ABANDON) # 破坏性动作红(危险红族)
abandon_btn.pressed.connect(_on_abandon_pressed)
abandon_btn.visible = false abandon_btn.visible = false
box.add_child(abandon_btn) bottom.add_child(abandon_btn)
visible = false visible = false
# 状态数据框工厂(直角细线数据框——pattern 5 族;hp_box = 带迷你血条)
func _status_box(caption: String, with_bar: bool) -> PanelContainer:
var panel := PanelContainer.new()
panel.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1))
var inner := VBoxContainer.new()
panel.add_child(inner)
inner.add_child(UiKit.label(caption, 12, false))
if with_bar:
_hp_bar = Control.new()
_hp_bar.custom_minimum_size = Vector2(120, 6)
_hp_bar.draw.connect(_draw_hp_bar.bind(_hp_bar))
inner.add_child(_hp_bar)
var value := UiKit.label("", 15, true)
inner.add_child(value)
if caption == "生命":
_hp_value = value
else:
_attr_value = value
return panel
func _draw_hp_bar(bar: Control) -> void:
# 迷你血条(占位:暗底+金填充——比例 = hero_hp / effective_max_hp)
if _runner == null:
return
var max_hp := maxi(1, _runner.effective_max_hp())
var ratio := clampf(float(_runner.hero_hp) / float(max_hp), 0.0, 1.0)
var full := Rect2(Vector2.ZERO, bar.size)
bar.draw_rect(full, UiKit.DARK_UPCOMING)
if ratio > 0.0:
bar.draw_rect(Rect2(Vector2.ZERO, Vector2(bar.size.x * ratio, bar.size.y)), UiKit.GOLD)
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险) # 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
func set_abandon_available(v: bool) -> void: func set_abandon_available(v: bool) -> void:
abandon_btn.visible = v abandon_btn.visible = v
func _emit_abandon() -> void: # 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。
# 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。
func _on_abandon_pressed() -> void:
if not _abandon_armed:
_abandon_armed = true
_confirm_left = CONFIRM_WINDOW_S
abandon_btn.text = "再点一次确认放弃——不可逆"
return
_reset_abandon_confirm()
abandon_requested.emit() abandon_requested.emit()
# 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染) func _reset_abandon_confirm() -> void:
_abandon_armed = false
_confirm_left = 0.0
abandon_btn.modulate = Color(1, 1, 1, 1)
abandon_btn.text = "放弃冒险(同失败结算——已获大奖励保留)"
func _process(delta: float) -> void:
if not _abandon_armed:
return
_confirm_left -= delta
if _confirm_left <= 0.0:
_reset_abandon_confirm()
return
# 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪)
if UiKit.motion_scale < 1.0:
abandon_btn.modulate = Color(1, 1, 1, 0.75)
else:
var k := 0.5 + 0.5 * sin(_confirm_left * TAU)
abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k)
# 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新)
func open_choices(cands: Array, runner) -> void: func open_choices(cands: Array, runner) -> void:
_runner = runner _runner = runner
for child in _btn_box.get_children(): _hover_id = -1
child.queue_free() _reset_abandon_confirm()
_title.text = "选择下一跳(岔路口)" if cands.size() > 1 else "继续前进" var visited: Array = []
if runner != null:
visited = runner.visited_ids()
if cands.size() > 1:
_title.text = TITLE_FORK
elif visited.is_empty():
_title.text = TITLE_ENTRY # 入口停点(尚未走出第一步——台账 143 措辞矛盾清偿)
else:
_title.text = TITLE_SINGLE
_cand_ids = []
for cand in cands: for cand in cands:
var nid := _id_of(cand) _cand_ids.append(_id_of(cand))
var t := String(cand.get("type", "")) _refresh_status()
var label := "节点 %d · %s" % [nid, _type_name(t)] if cands.size() > 1 else "继续前进(%s)" % _type_name(t)
var btn := UiKit.button(label)
btn.pressed.connect(_emit_choice.bind(nid))
_btn_box.add_child(btn)
visible = true visible = true
# 探针复用:按候选序号点击(真实按钮路径) # 状态栏刷新(命令式纪律——组合根打开本屏/兑付确认回执后重查;零推送信号订阅)
func press_candidate(index: int) -> void: func _refresh_status() -> void:
var btns: Array = _btn_box.get_children() if _runner == null:
if index >= 0 and index < btns.size(): return
btns[index].pressed.emit() _level_label.text = String(_runner.level_row.get("display_name", ""))
_gp_value.text = "◈ %d" % int(_runner.gp)
var max_hp: int = _runner.effective_max_hp()
_hp_value.text = "HP %d/%d" % [int(_runner.hero_hp), int(max_hp)]
_hp_bar.queue_redraw()
var atk_bonus := int(_runner.bonus.get("atk", 0))
var ac_bonus := int(_runner.bonus.get("armor_class", 0))
var atk_txt := "%d" % _runner.effective_atk()
if atk_bonus > 0:
atk_txt += " +%d" % atk_bonus # 加成角标(ASCII 加号——防像素字体缺 ⁺ 字形出豆腐块)
var ac_txt := "%d" % _runner.effective_ac()
if ac_bonus > 0:
ac_txt += " +%d" % ac_bonus
_attr_value.text = "攻击 %s · 护甲 AC %s" % [atk_txt, ac_txt]
var hp_bonus := int(_runner.bonus.get("max_hp", 0))
var parts: Array = []
if atk_bonus > 0:
parts.append("攻 +%d" % atk_bonus)
if hp_bonus > 0:
parts.append("上限 +%d" % hp_bonus)
if ac_bonus > 0:
parts.append("AC +%d" % ac_bonus)
_bonus_value.text = " · ".join(parts)
_bonus_row.visible = not parts.is_empty()
func _emit_choice(nid: int) -> void: # ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ----
func _on_canvas_input(ev: InputEvent) -> void:
if _runner == null or not visible:
return
if ev is InputEventMouseMotion:
var nid := _candidate_at(_canvas.get_local_mouse_position())
if nid != _hover_id:
_hover_id = nid
_canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW
_canvas.queue_redraw()
elif ev is InputEventMouseButton and ev.button_index == MOUSE_BUTTON_LEFT and ev.pressed:
var nid2 := _candidate_at(_canvas.get_local_mouse_position())
if nid2 >= 0:
_submit_choice(nid2)
# 候选命中:位置 = 布局公式(与 _draw_graph 同源),矩形 = 节点方块 + 外扩容差;非候选格不响应
func _candidate_at(local_pos: Vector2) -> int:
if _runner == null:
return -1
var nodes: Array = _runner.nodes()
var pos := _layout(nodes, _canvas)
for nid in _cand_ids:
var node := _node_by_id(nid, nodes)
if node.is_empty():
continue
var np: Vector2 = pos[node]
var rect := Rect2(np - NODE_HALF - Vector2(HIT_PAD, HIT_PAD), (NODE_HALF + Vector2(HIT_PAD, HIT_PAD)) * 2.0)
if rect.has_point(local_pos):
return nid
return -1
func _submit_choice(nid: int) -> void:
visible = false visible = false
route_chosen.emit(nid) route_chosen.emit(nid)
# 探针复用:按候选序号走图上命中同路径(候选节点中心坐标 → 命中检测 → 提交——布局决策 2 探针面改造)
func press_candidate(index: int) -> void:
if index < 0 or index >= _cand_ids.size() or _runner == null:
return
var nodes: Array = _runner.nodes()
var node := _node_by_id(int(_cand_ids[index]), nodes)
if node.is_empty():
return
var pos: Vector2 = _layout(nodes, _canvas)[node]
var nid := _candidate_at(pos) # 节点中心必命中自身(命中面含节点方块)
if nid >= 0:
_submit_choice(nid)
func _id_of(node: Dictionary) -> int: func _id_of(node: Dictionary) -> int:
# 编号显示经行进器读面(编号空间解析单一出口——单链 = 下标 / 图 = node_depth) # 编号显示经行进器读面(编号空间解析单一出口——单链 = 下标 / 图 = node_depth)
return _runner.node_id_of(node) return _runner.node_id_of(node)
func _type_name(t: String) -> String:
match t:
"battle": return "战斗房"
"reward": return "奖励点"
"rest": return "恢复点"
"boss": return "Boss 房"
"shop": return "商店房"
return t
# ---- 整图自绘(程序化占位——分层布局:层 = 入口 BFS 深度,同层垂直排布)---- # ---- 整图自绘(程序化占位——分层布局:层 = 入口 BFS 深度,同层垂直排布)----
func _draw_graph(canvas: Control) -> void: func _draw_graph(canvas: Control) -> void:
@@ -121,7 +312,7 @@ func _draw_graph(canvas: Control) -> void:
# 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析 # 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析
var visited: Array = _runner.visited_ids() var visited: Array = _runner.visited_ids()
var cand_ids: Array = _runner.next_candidate_ids() var cand_ids: Array = _runner.next_candidate_ids()
# 连线(通道)先行画(方块覆盖其上) # 连线(通道)先行画(方块覆盖其上);悬停预承诺 = 所属通道再增亮(加粗)
for node in nodes: for node in nodes:
var from: Vector2 = pos[node] var from: Vector2 = pos[node]
for tid in _out_ids(node, nodes): for tid in _out_ids(node, nodes):
@@ -130,8 +321,9 @@ func _draw_graph(canvas: Control) -> void:
continue continue
var to: Vector2 = pos[target] var to: Vector2 = pos[target]
var lit: bool = _id_of(node) == current_id and cand_ids.has(tid) var lit: bool = _id_of(node) == current_id and cand_ids.has(tid)
var hovered: bool = lit and _hover_id == tid
var col := COL_PASSAGE_LIT if lit else COL_PASSAGE var col := COL_PASSAGE_LIT if lit else COL_PASSAGE
var width := 3.0 if lit else 1.5 var width := 4.0 if hovered else (3.0 if lit else 1.5)
_draw_passage(canvas, from, to, col, width) _draw_passage(canvas, from, to, col, width)
# 房间方块 # 房间方块
for node in nodes: for node in nodes:
@@ -143,13 +335,14 @@ func _draw_graph(canvas: Control) -> void:
col = COL_ROOM_DONE col = COL_ROOM_DONE
if nid == current_id: if nid == current_id:
col = COL_ROOM_CURRENT col = COL_ROOM_CURRENT
var rect := Rect2(p - Vector2(26, 18), Vector2(52, 36)) var rect := Rect2(p - NODE_HALF, NODE_HALF * 2.0)
canvas.draw_rect(rect, col) canvas.draw_rect(rect, col)
if t == "boss": if t == "boss":
canvas.draw_rect(rect, COL_BOSS_BORDER, false, 2.0) canvas.draw_rect(rect, COL_BOSS_BORDER, false, 2.0)
elif cand_ids.has(nid): elif cand_ids.has(nid):
canvas.draw_rect(rect, COL_PASSAGE_LIT, false, 2.0) # 悬停预承诺 = 亮金描边加粗(4px),常态候选 = 亮金描边 2px(spec States 悬停预承诺行)
var font := ThemeDB.fallback_font canvas.draw_rect(rect, COL_PASSAGE_LIT, false, 4.0 if _hover_id == nid else 2.0)
var font := get_theme_default_font()
var label := String(NODE_LABELS.get(t, "?")) var label := String(NODE_LABELS.get(t, "?"))
canvas.draw_string(font, p + Vector2(-6, 2), label, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, UiKit.INK_ROUTE) canvas.draw_string(font, p + Vector2(-6, 2), label, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, UiKit.INK_ROUTE)
canvas.draw_string(font, p + Vector2(10, 14), str(nid), HORIZONTAL_ALIGNMENT_LEFT, -1, 10, UiKit.WARM_ROUTE_SUB) canvas.draw_string(font, p + Vector2(10, 14), str(nid), HORIZONTAL_ALIGNMENT_LEFT, -1, 10, UiKit.WARM_ROUTE_SUB)
@@ -216,8 +409,8 @@ func _node_by_id(nid: int, nodes: Array) -> Dictionary:
# 通道画法:房间方块边缘到边缘的水平-折线(占位观感——地牢通道) # 通道画法:房间方块边缘到边缘的水平-折线(占位观感——地牢通道)
func _draw_passage(canvas: Control, from: Vector2, to: Vector2, col: Color, width: float) -> void: func _draw_passage(canvas: Control, from: Vector2, to: Vector2, col: Color, width: float) -> void:
var a := from + Vector2(26, 0) var a := from + Vector2(NODE_HALF.x, 0)
var b := to - Vector2(26, 0) var b := to - Vector2(NODE_HALF.x, 0)
if b.x < a.x: if b.x < a.x:
return return
var mid_x := (a.x + b.x) / 2.0 var mid_x := (a.x + b.x) / 2.0
+2 -2
View File
@@ -88,7 +88,7 @@ func _ready() -> void:
var t1 := Time.get_ticks_msec() var t1 := Time.get_ticks_msec()
main._start_adventure_from_menu() main._start_adventure_from_menu()
await get_tree().process_frame await get_tree().process_frame
# 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战 # 停点前移批 2026-09-23:关卡开始 = 入口选路停点——press_candidate 图上命中路径选入口候选进首战(候选按钮退役 2026-09-28)
#(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装) #(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装)
if main.route_overlay.visible: if main.route_overlay.visible:
main.route_overlay.press_candidate(0) main.route_overlay.press_candidate(0)
@@ -114,7 +114,7 @@ func _ready() -> void:
_flags_placed.clear() _flags_placed.clear()
continue continue
if main.route_overlay.visible: if main.route_overlay.visible:
# 节点图选路口(零自动推进停点)——真实按钮路径交替选候选(岔路使用行为) # 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造)
main.route_overlay.press_candidate(_route_clicks % 2) main.route_overlay.press_candidate(_route_clicks % 2)
_route_clicks += 1 _route_clicks += 1
_flags_placed.clear() _flags_placed.clear()
+1 -1
View File
@@ -62,7 +62,7 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
# ---- 入口选路停点(停点前移批 2026-09-23:关卡开始即出节点图——level_started 后、首战前弹选路层; # ---- 入口选路停点(停点前移批 2026-09-23:关卡开始即出节点图——level_started 后、首战前弹选路层;
# 真实按钮路径 pick 入口单候选进首战)---- # press_candidate 图上命中路径 pick 入口单候选进首战——候选按钮退役 2026-09-28)----
if not main.route_overlay.visible: if not main.route_overlay.visible:
problems.append("关卡开始未弹入口选路停点(route_overlay 应可见)") problems.append("关卡开始未弹入口选路停点(route_overlay 应可见)")
elif main.runner == null or not main.runner.awaiting_choice() \ elif main.runner == null or not main.runner.awaiting_choice() \