From eec93e09a8dfe1fa780ed8666621f41c033c61e2 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 30 Oct 2026 08:49:52 +0800 Subject: [PATCH] =?UTF-8?q?iter45-=E5=8D=A1=E2=91=A1T1:=20=E6=82=AC?= =?UTF-8?q?=E5=81=9C=E5=9F=9F=E6=8B=86=E4=BB=B6=20board=5Fhover=E2=80=94?= =?UTF-8?q?=E2=80=9411=E5=87=BD=E6=95=B0=E6=97=8F+=5Fhover/=5Flast=5Fclick?= =?UTF-8?q?=E4=B8=A4=E6=88=90=E5=91=98=E8=BF=81=E5=87=BA(=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E4=BD=93=E9=80=90=E5=AD=97,=E4=B8=89=E5=A4=84?= =?UTF-8?q?=E6=9C=BA=E6=A2=B0=E6=94=B9=E5=86=99:=5Fcell=5Fat=E2=86=92=5Fcb?= =?UTF-8?q?=5Fcell=5Fat.call=E5=B8=A6=E7=B1=BB=E5=9E=8B=E6=A0=87=E6=B3=A8/?= =?UTF-8?q?hover=5Fforbidden=E4=B8=8Echord=5Fattempted.emit=E2=86=92?= =?UTF-8?q?=E6=B3=A8=E5=85=A5Callable.call/mouse=5Fdefault=5Fcursor=5Fshap?= =?UTF-8?q?e=E5=86=99=E5=85=A5=E2=86=92=5Fcb=5Fcursor.call),COL=5FHL=5FTAR?= =?UTF-8?q?GET/CURSOR=5FDIG/CURSOR=5FMOVE=5FSEM/CHORD=5FWINDOW=5FMS?= =?UTF-8?q?=E9=9A=8F=E5=87=BD=E6=95=B0=E8=BF=81=E5=B8=B8=E9=87=8F=E3=80=81?= =?UTF-8?q?COL=5FSEAL=E5=8F=8C=E4=BE=A7=E6=9C=8D=E5=BD=B9=E5=90=84?= =?UTF-8?q?=E8=87=AAalias=20UiKit=E5=8D=95=E6=BA=90,=E4=B8=89=E5=85=A5?= =?UTF-8?q?=E7=AB=99=E5=8F=A3(=5Fgui=5Finput/=5Fon=5Fcell=5Finput/click=5F?= =?UTF-8?q?cell)=E4=B8=8Ehover=5Fprobe=E8=AF=BB=E9=9D=A2=E7=95=99=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=E5=A7=94=E6=89=98,=5Fr0=E7=95=99=E5=AE=BF=E4=B8=BB;?= =?UTF-8?q?=E6=9E=84=E9=80=A0=3D=E5=BC=95=E7=94=A8=E6=9D=9F=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E6=B3=A8=E5=85=A5(cells/minesweeper/diggable=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=E5=BC=95=E7=94=A8+=E6=A0=BC=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E6=8D=A2=E7=AE=97/=E5=85=89=E6=A0=87=E5=86=99=E5=85=A5/?= =?UTF-8?q?=E4=B8=A4=E4=BF=A1=E5=8F=B7=E5=8F=91=E5=B0=84Callable)=E9=98=B2?= =?UTF-8?q?max-parameters=E6=96=B0=E9=94=AE,minesweeper=E9=87=8D=E7=BB=91?= =?UTF-8?q?=E7=94=B1rebuild=E5=90=8C=E6=8B=8D=E5=9B=9E=E5=86=99=E4=BB=B6?= =?UTF-8?q?=E6=88=90=E5=91=98=E3=80=94=E8=A1=A5=E4=BB=B6=20V1=E3=80=95;?= =?UTF-8?q?=E9=AA=8C=E8=AF=81:probe=5Fgate=20--click=20--full=E5=85=A8?= =?UTF-8?q?=E7=BB=BF+render=5Fgate=20golden=208=E5=B1=8FOK;=E9=94=AE?= =?UTF-8?q?=E8=B4=A6board=5Fview=E7=B1=BB=E5=8F=98=E9=87=8F32=E2=86=9231(?= =?UTF-8?q?=E5=8F=AA=E9=99=8D=E4=B8=8D=E5=8D=87)/=E5=85=AC=E5=BC=80?= =?UTF-8?q?=E6=96=B9=E6=B3=9527=E6=8C=81=E5=B9=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/views/board_hover.gd | 130 ++++++++++++++++++++++ game/presentation/views/board_view.gd | 142 ++++++------------------- 2 files changed, 160 insertions(+), 112 deletions(-) create mode 100644 game/presentation/views/board_hover.gd diff --git a/game/presentation/views/board_hover.gd b/game/presentation/views/board_hover.gd new file mode 100644 index 00000000..44da7b45 --- /dev/null +++ b/game/presentation/views/board_hover.gd @@ -0,0 +1,130 @@ +## VERTICAL SLICE - NOT FOR PRODUCTION +## 悬停预承诺呈现域件(BoardView 拆件——单一变化原因=悬停反馈语义): +## 三语义光标、目标格边框、BFS 路径预览、chord 双击首遇锚的唯一承载(自 board_view 收编 11 函数族)。 +## 无 class_name(类名缓存面纪律)——经宿主 board_view preload 引用;构造=引用束注入 +## (cells/minesweeper/diggable 共享引用+格命中换算/壳级光标写入/hover_forbidden 与 chord 发射注入 Callable—— +## 输入经宿主三入站口委托,禁反向持宿主引用);_r0 出生展开区成员留宿主。 +extends RefCounted + +const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金) +const COL_SEAL := UiKit.SEAL_BLACK # 封印黑(与宿主 :658 双侧服役——各自 alias UiKit 单源) +const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体 +const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名避开 Control.CURSOR_MOVE 原生成员遮蔽) +const CHORD_WINDOW_MS := 350 + +var _cells := {} # 共享引用(宿主容器同对象——rebuild 只 clear 不重绑) +var _minesweeper = null # 排雷玩法 只读查询引用(宿主 rebuild 重绑时同拍回写本成员) +var _diggable := {} # 共享引用(可挖域——呼吸微光作用集同源) +var _cb_cell_at: Callable # 格命中换算(宿主 _cell_at——整图公式单点留宿主) +var _cb_cursor: Callable # 壳级光标写入(宿主 mouse_default_cursor_shape 单点) +var _cb_forbidden: Callable # hover_forbidden 信号发射(首遇 toast 去重归组合根) +var _cb_chord: Callable # chord_attempted 信号发射(chord 肌肉记忆提示锚) +var _hover := Vector2i(-9, -9) +var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000} + + +func _init(bundle: Dictionary) -> void: + _cells = bundle["cells"] + _minesweeper = bundle["minesweeper"] + _diggable = bundle["diggable"] + _cb_cell_at = bundle["cell_at"] + _cb_cursor = bundle["cursor"] + _cb_forbidden = bundle["forbidden"] + _cb_chord = bundle["chord"] + + +func _hover_at(pos: Vector2) -> void: + var c: Vector2i = _cb_cell_at.call(pos) + if c.x < 0: + if _hover.x >= 0: + _on_cell_unhover(_hover) + elif c != _hover: + if _hover.x >= 0: + _on_cell_unhover(_hover) + _on_cell_hover(c) + + +func _on_shell_mouse_exited() -> void: + if _hover.x >= 0: + _on_cell_unhover(_hover) + + +# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重 +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) + _last_click.ms = -100000 + return + _last_click = {"cell": c, "ms": now} + + +func _on_cell_hover(c: Vector2i) -> void: + _hover = c + _apply_hover_visuals(c) + + +func _on_cell_unhover(c: Vector2i) -> void: + if _hover == c: + _hover = Vector2i(-9, -9) + _clear_hover_visuals(c) + + +func _refresh_hover_after_paint() -> void: + # 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变) + if _hover.x >= 0 and _cells.has(_hover): + _apply_hover_visuals(_hover) + + +func _apply_hover_visuals(c: Vector2i) -> void: + if _minesweeper == null or not _cells.has(c): + return + _clear_hover_visuals(c) + var u: Node2D = _cells[c] + var revealed: bool = _minesweeper.is_revealed(c) + var seal: bool = _minesweeper.has_seal(c) + var adjacent: bool = _diggable.has(c) + if revealed: + u.face.hl_visible = true + u.face.hl_color = COL_HL_TARGET # 移动目标格边框(金) + _set_cursor_shape(c, CURSOR_MOVE_SEM) + _show_path_preview(c) + elif adjacent: + u.face.hl_visible = true + u.face.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖) + _set_cursor_shape(c, CURSOR_DIG) + elif seal: + u.face.hl_visible = true + u.face.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供) + _set_cursor_shape(c, Input.CURSOR_FORBIDDEN) + else: + u.face.hl_visible = true + u.face.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道) + _set_cursor_shape(c, Input.CURSOR_FORBIDDEN) + _cb_forbidden.call(c) + + +func _clear_hover_visuals(c: Vector2i) -> void: + if not _cells.has(c): + return + _cells[c].face.hl_visible = false + _hide_path_preview() + + +func _set_cursor_shape(c: Vector2i, shape: int) -> void: + _cb_cursor.call(shape) # 壳级光标(悬停格语义决定——单格悬停恒一) + + +func _show_path_preview(c: Vector2i) -> void: + # 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面) + if _minesweeper == null: + return + var path: Array[Variant] = _minesweeper.compute_move_path(c) + for pc in path: + if _cells.has(pc): + _cells[pc].face.dot_visible = true + + +func _hide_path_preview() -> void: + for c in _cells: + _cells[c].face.dot_visible = false diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index ea4502b8..a271ca4b 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -12,6 +12,7 @@ const _CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd") const _BOARD_WORLD := preload("res://presentation/views/board_world.gd") const _TILE_PALETTE := preload("res://presentation/views/tile_palette.gd") const _HERO_SPRITE := preload("res://presentation/views/hero_sprite.gd") +const _BOARD_HOVER := preload("res://presentation/views/board_hover.gd") const COL_BRICK := UiKit.BRICK # 未开——掩尘完整地砖(色板原语 #C9AF83) const COL_FLOOR := UiKit.FLOOR # 已开——裸土 #B39A78(碎裂裸露地面) @@ -20,17 +21,13 @@ const COL_HERO := UiKit.HERO_BLUE # 小人边框——小人蓝 const COL_SEAL := UiKit.SEAL_BLACK # 封印黑 #000000 const COL_GOLD := UiKit.GOLD # 奖励金 #F0A828 const COL_CONTACT := UiKit.CONTACT_GLOW # 接触带微光基色(蓝族浅一档·近白蓝——§3.2 v2.8) -const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金) const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 九型键) "attack": "攻", "defense": "守", "heal": "医", "blast": "爆", "weaken": "弱", "pierce": "穿", "magic": "魔", "action": "行", # 行 = 行动点型(ap_up/ap_drain 两行) "gp": "金", # 金 = GP 型(gp_gain/gp_loss 两行;金色系 ~40° 相位,禁入带紫赤 300–345° 外,取色 UiKit.GOLD 徽标色单点) } const BADGE_PLACEHOLDER := "?" # 未知图标键占位纹样(HUD框架:禁静默空图标) -const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体 const CURSOR_SEAL := Input.CURSOR_POINTING_HAND # 封印——封印形自绘纹理载体 -const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名避开 Control.CURSOR_MOVE 原生成员遮蔽) -const CHORD_WINDOW_MS := 350 const SEP := 2 # 格间距(格尺寸公式与命中换算共用) # 棋盘立体参数 @@ -67,8 +64,6 @@ var _contact_unit: Node2D = null # 接触带承载单元(=英雄格 CellUnit var _install_tw: Tween # 入场淡入 tween(rebuild 重入先杀) var _ripple_tws: Array[Tween] = [] # R0 涟漪逐格 tween(rebuild 重入全杀) var _dig_glow := false # 挖掘期才允许唯一在动元素(art-bible §2.1) -var _hover := Vector2i(-9, -9) -var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000} var _r0 := {} # 出生展开区 R₀(地图战斗资源 mapping 资格域 = S∖R₀——徽章查询跳过,防 E19 诊断噪音) var _desat_mat: ShaderMaterial var _desat_tween: Tween @@ -77,6 +72,7 @@ var _cell := 46.0 # 当前格边长(set_cell_area 按容器尺寸重 # ── 拆出件(状态单点在件内)── var _palette = _TILE_PALETTE.new() # 瓦片件:装载缓存+查表+明度/墨族静态算法 var _hero_sprite = _HERO_SPRITE.new() # 英雄精灵件:_init 挂为本件子级(tween 需在树上) +var _hover_comp: _BOARD_HOVER # 悬停预承诺呈现域件:三语义光标/目标格边框/路径预览/chord 首遇锚(_init 建为子件——HeroSprite 挂载先例) # ── 件成员同名转发(探针直读面零改动:读 _palette.seal_tex、读写 _hero_sprite.tex_idle;其余为随迁状态单点再导出)── # ---- 视野迷雾(纯呈现层;现行事实源=HUD框架「视野迷雾」节 v0.13,渲染规格=art-bible §6.5 v3.5)---- @@ -120,6 +116,25 @@ func _init() -> void: _hero_sprite.tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE) _hero_sprite.tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK) _palette.load_board_tiles() # 高墙/地面瓦片整批守卫装载(缺位静默回退——英雄同款纪律,禁刷屏) + _hover_comp = _BOARD_HOVER.new({ + "cells": _cells, "minesweeper": _minesweeper, "diggable": _diggable, + "cell_at": _cell_at, "cursor": _set_shell_cursor, + "forbidden": _emit_hover_forbidden, "chord": _emit_chord_attempted, + }) + + +# ---- 悬停域注入面(件回调宿主的壳级单点——Callable 目标)---- + +func _set_shell_cursor(shape: int) -> void: + mouse_default_cursor_shape = shape # 壳级光标(悬停格语义决定——单格悬停恒一) + + +func _emit_hover_forbidden(c: Vector2i) -> void: + hover_forbidden.emit(c) ## 非相邻未开格悬停(首遇 toast 归组合根去重) + + +func _emit_chord_attempted(c: Vector2i) -> void: + chord_attempted.emit(c) ## 双击快开尝试(chord 肌肉记忆提示锚) func board() -> BoardData: @@ -128,7 +143,7 @@ func board() -> BoardData: func _ready() -> void: _apply_custom_cursors() - mouse_exited.connect(_on_shell_mouse_exited) + mouse_exited.connect(_hover_comp._on_shell_mouse_exited) # 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2); @@ -224,6 +239,7 @@ func _refresh_slab() -> void: func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void: _board = board _minesweeper = minesweeper + _hover_comp._minesweeper = _minesweeper # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象) _palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置) for child in _world.get_children(): child.queue_free() @@ -234,7 +250,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void: _contact_unit = null _hero_sprite.last = Vector2i(-99, -99) _r0.clear() - _hover = Vector2i(-9, -9) + _hover_comp._hover = Vector2i(-9, -9) for c in _board.compute_r0(): _r0[c] = true # 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark; @@ -283,7 +299,7 @@ func refresh() -> void: for y in _board.height: for x in _board.width: _paint_cell(Vector2i(x, y), hero) - _refresh_hover_after_paint() + _hover_comp._refresh_hover_after_paint() _apply_contact_bands() _update_hero(hero) # 英雄精灵 diff 驱动 @@ -433,33 +449,18 @@ func _update_world_size() -> void: # 真实输入路径(Node2D 迁移批 Decision 3):壳层 _gui_input → 整图公式换算 → 复用既有 hover/点选/chord 出口 +#(悬停族已拆 board_hover 件——本壳留三入站口委托) func _gui_input(ev: InputEvent) -> void: if _minesweeper == null: return if ev is InputEventMouseMotion: - _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: _on_cell_input(c, ev) -func _hover_at(pos: Vector2) -> void: - var c := _cell_at(pos) - if c.x < 0: - if _hover.x >= 0: - _on_cell_unhover(_hover) - elif c != _hover: - if _hover.x >= 0: - _on_cell_unhover(_hover) - _on_cell_hover(c) - - -func _on_shell_mouse_exited() -> void: - if _hover.x >= 0: - _on_cell_unhover(_hover) - - func _on_cell_input(c: Vector2i, ev: InputEvent) -> void: if _minesweeper == null or not (ev is InputEventMouseButton and ev.pressed): return @@ -468,7 +469,7 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void: action_requested.emit(c, "move") else: action_requested.emit(c, "dig") - _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") @@ -476,89 +477,6 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void: # 左键相邻封印格直接挖(规则引擎放行)。 -# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重 -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: - chord_attempted.emit(c) - _last_click.ms = -100000 - return - _last_click = {"cell": c, "ms": now} - - -# ---- 悬停预承诺(pattern 4):三语义光标 + 目标格边框 + BFS 路径预览 ---- - -func _on_cell_hover(c: Vector2i) -> void: - _hover = c - _apply_hover_visuals(c) - - -func _on_cell_unhover(c: Vector2i) -> void: - if _hover == c: - _hover = Vector2i(-9, -9) - _clear_hover_visuals(c) - - -func _refresh_hover_after_paint() -> void: - # 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变) - if _hover.x >= 0 and _cells.has(_hover): - _apply_hover_visuals(_hover) - - -func _apply_hover_visuals(c: Vector2i) -> void: - if _minesweeper == null or not _cells.has(c): - return - _clear_hover_visuals(c) - var u: Node2D = _cells[c] - var revealed: bool = _minesweeper.is_revealed(c) - var seal: bool = _minesweeper.has_seal(c) - var adjacent: bool = _diggable.has(c) - if revealed: - u.face.hl_visible = true - u.face.hl_color = COL_HL_TARGET # 移动目标格边框(金) - _set_cursor_shape(c, CURSOR_MOVE_SEM) - _show_path_preview(c) - elif adjacent: - u.face.hl_visible = true - u.face.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖) - _set_cursor_shape(c, CURSOR_DIG) - elif seal: - u.face.hl_visible = true - u.face.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供) - _set_cursor_shape(c, Input.CURSOR_FORBIDDEN) - else: - u.face.hl_visible = true - u.face.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道) - _set_cursor_shape(c, Input.CURSOR_FORBIDDEN) - hover_forbidden.emit(c) - - -func _clear_hover_visuals(c: Vector2i) -> void: - if not _cells.has(c): - return - _cells[c].face.hl_visible = false - _hide_path_preview() - - -func _set_cursor_shape(c: Vector2i, shape: int) -> void: - mouse_default_cursor_shape = shape # 壳级光标(悬停格语义决定——单格悬停恒一) - - -func _show_path_preview(c: Vector2i) -> void: - # 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面) - if _minesweeper == null: - return - var path: Array[Variant] = _minesweeper.compute_move_path(c) - for pc in path: - if _cells.has(pc): - _cells[pc].face.dot_visible = true - - -func _hide_path_preview() -> void: - for c in _cells: - _cells[c].face.dot_visible = false - - # ---- 奖励徽章(已开格身份 icon——封印之下、纹带之上、不进数字区) ---- func _update_badge(c: Vector2i) -> String: @@ -898,9 +816,9 @@ func diggable_probe() -> Dictionary: return _diggable.duplicate() -# 悬停格只读出口(case① 悬停到达断言消费面) +# 悬停格只读出口(case① 悬停到达断言消费面——读件投影,返回语义零变化) func hover_probe() -> Vector2i: - return _hover + return _hover_comp._hover # 迷雾参数只读快照(case⑮ 参数漂移断言消费面)