diff --git a/game/presentation/main.gd b/game/presentation/main.gd index b8bc0204..9ba12238 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -549,6 +549,15 @@ func _on_chord_attempted(_cell: Vector2i) -> void: _show_hint("本作无双击快开——插旗后轮末自动判定标中;点数字格可移动小人", "chord") +# 立体原型档切换(F9 循环:平面→薄→中→厚——2026-09-24 用户拍板三档实机对比;定稿后此入口退役归表现批) +func _unhandled_key_input(event: InputEvent) -> void: + if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_F9: + if board_view == null or board_view.board() == null: + return + var tier: String = board_view.set_depth_mode((board_view.depth_mode + 1) % board_view.DEPTH_NAMES.size()) + board_zone.toast.show_toast("棋盘立体档:%s(F9 继续切换)" % tier, "info") + + func _show_hint(text: String, hint_id: String) -> void: _hints_shown[hint_id] = true logger.log_ux("ux_hint_shown", {"hint_id": hint_id}) diff --git a/game/presentation/views/board_cell_unit.gd b/game/presentation/views/board_cell_unit.gd index cf71c47d..f81e3740 100644 --- a/game/presentation/views/board_cell_unit.gd +++ b/game/presentation/views/board_cell_unit.gd @@ -40,6 +40,10 @@ var hl_color := UiKit.GOLD: # 高亮色(金目标/旗朱/禁入灰禁 hl_color = v queue_redraw() var font: Font = null # 字体槽位(null = ThemeDB.fallback_font——BoardView.set_world_font 注入) +var bevel := 0.0: # 顶面倒角强度(0=关——立体原型批 2026-09-24;档位 0/0.35/0.6/1.0) + set(v): + bevel = v + queue_redraw() # ── 素材槽位(本批全 null = 程序化占位服役;art-bible §8.2 格面 32×32 暂定档)── var tex_base: Texture2D = null # ① 砖面底 var tex_mutation: Texture2D = null # ② 变异纹带 @@ -56,6 +60,17 @@ func _draw() -> void: draw_texture_rect(tex_base, rect, false) elif bg_sb != null: draw_style_box(bg_sb, rect) + # 顶面倒角(立体原型批:光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让 + # 呼吸微光/主角蓝边框保持最外沿可见;强度随档位) + if bevel > 0.0: + var base: Color = bg_sb.bg_color + var lit: Color = base.lightened(0.22 * bevel) + var dark: Color = base.darkened(0.22 * bevel) + var t := 2.0 + draw_rect(Rect2(t, t, edge - t * 2.0, t), lit, true) + draw_rect(Rect2(t, t, t, edge - t * 2.0), lit, true) + draw_rect(Rect2(t, edge - t * 2.0, edge - t * 2.0, t), dark, true) + draw_rect(Rect2(edge - t * 2.0, t, t, edge - t * 2.0), dark, true) # ② 变异纹带(占位批次无程序化纹带——槽位先行,终稿换入即用) if tex_mutation != null: draw_texture_rect(tex_mutation, rect, false) diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 20399c61..ec20121f 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -47,6 +47,13 @@ const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名 const CHORD_WINDOW_MS := 350 const SEP := 2 # 格间距(旧 GridContainer v/h_separation 同值——格尺寸公式与命中换算共用) +# 立体原型档位(2026-09-24 用户拍板三档实机对比——F9 循环切换;定稿参数归格子样张批 §1.4 冻结) +const DEPTH_NAMES := ["平面(现行)", "薄(缩小帽档)", "中", "厚(纸雕织梦岛档)"] +const DEPTH_PX := [0.0, 4.0, 8.0, 14.0] # 悬崖厚度(像素) +const DEPTH_BEVEL := [0.0, 0.35, 0.6, 1.0] # 顶面倒角强度 + +var depth_mode := 0 # 当前立体档(0..3——平面/薄/中/厚) + var _wrap: CenterContainer var _svp_container: SubViewportContainer var _viewport: SubViewport @@ -141,17 +148,40 @@ func set_cell_area(avail: Vector2) -> void: return var n: int = maxi(_board.width, _board.height) var sep := SEP # 格间距(_init 同值) - var reserve := 24.0 # 垂直总预留(上 12 + 下 12:棋盘底部不贴 D 区边框) - var cell := int(floor((minf(avail.x - 12.0, avail.y - reserve) - float((n - 1) * sep)) / float(n))) + var d := float(DEPTH_PX[depth_mode]) + var reserve := 24.0 + d # 垂直总预留(上 12 + 下 12 + 悬崖外延——立体原型批:悬崖占预算防溢出) + var cell := int(floor((minf(avail.x - 12.0 - d, avail.y - reserve) - float((n - 1) * sep)) / float(n))) _cell = float(clampi(cell, 26, 46)) var stride := _cell + float(SEP) for c in _cells: var u: Node2D = _cells[c] u.edge = _cell u.position = Vector2(c) * stride + _refresh_slab() _update_world_size() +# 立体档切换(F9 组合根入口;返回档名供 toast) +func set_depth_mode(m: int) -> String: + depth_mode = clampi(m, 0, DEPTH_NAMES.size() - 1) + for c in _cells: + _cells[c].bevel = float(DEPTH_BEVEL[depth_mode]) + _refresh_slab() + set_cell_area(size) + return DEPTH_NAMES[depth_mode] + + +# 悬崖参数下发(网格像素尺寸/步距/列数/材质锚色——BoardWorld._draw 消费) +func _refresh_slab() -> void: + if _board == null or _board.width == 0: + return + _world.depth_px = float(DEPTH_PX[depth_mode]) + _world.slab = Vector2(_board.width, _board.height) * (_cell + float(SEP)) - Vector2(SEP, SEP) + _world.stride_px = _cell + float(SEP) + _world.cols = _board.width + _world.mat = COL_BRICK + + # 战斗启动/换板:按板尺寸重建格单元(minesweeper 引用随之更新) func rebuild(board: BoardData, minesweeper) -> void: _board = board @@ -173,6 +203,7 @@ func rebuild(board: BoardData, minesweeper) -> void: u.cell = c u.edge = _cell u.position = Vector2(c) * stride + u.bevel = float(DEPTH_BEVEL[depth_mode]) # 立体原型批——新建格单元继承当前档倒角 u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化 u.font = _world.board_font _world.add_child(u) @@ -225,11 +256,12 @@ func cell_center_local(c: Vector2i) -> Vector2: return _svp_container.position + Vector2(c) * (_cell + float(SEP)) + Vector2.ONE * (_cell * 0.5) -# 视口逻辑分辨率 = 板像素尺寸(stretch=true 且容器 min 尺寸同步——缩放比恒 1:1) +# 视口逻辑分辨率 = 板像素尺寸 + 悬崖外延(stretch=true 且容器 min 尺寸同步——缩放比恒 1:1) func _update_world_size() -> void: if _board == null or _board.width == 0: return - var px := Vector2(_board.width, _board.height) * (_cell + float(SEP)) - Vector2(SEP, SEP) + var d := float(DEPTH_PX[depth_mode]) + var px := Vector2(_board.width, _board.height) * (_cell + float(SEP)) - Vector2(SEP, SEP) + Vector2(d, d) _svp_container.custom_minimum_size = px # 树内 stretch=true 时 SubViewport 尺寸由容器接管(手设被引擎拒绝并刷警告);树外(冒烟直建)才需手设保 1:1 if not _svp_container.is_inside_tree(): diff --git a/game/presentation/views/board_world.gd b/game/presentation/views/board_world.gd index ad12ed2c..abce7e19 100644 --- a/game/presentation/views/board_world.gd +++ b/game/presentation/views/board_world.gd @@ -4,6 +4,63 @@ # use_parent_material 链下随世界灰化)+ 主题/字体显式注入槽位(SubViewport 不自动继承主场景 # Control 主题——ADR-0006 Negative 项;Phase B 两舞台共用骨架,ADR-0006 Decision 5)。 # 无 class_name(类名缓存面纪律——BoardView 经 preload 使用)。 +# 立体原型批 2026-09-24(用户拍板「三档厚度实机对比」):板块伪立体绘制——正俯视纸雕风, +# 顶面方格不变(数字可读性硬指标不受伤),厚度全部画在网格矩形外的正面/右侧悬崖带: +# 正面(背光暗)+右侧(微受光)+列分隔缝(ITB 悬崖列感)+岩层横纹+底部悬浮影(厚档)+ +# 顶面接触 AO。零布局/命中改动(悬崖向网格外延展,世界尺寸由 BoardView._update_world_size 加 D)。 extends Node2D var board_font: Font = null # 字体注入槽位(null = ThemeDB.fallback_font——与旧 Button 缺省主题字体同源) + +# ── 立体原型(BoardView.set_depth_mode / set_cell_area 单点驱动)── +var depth_px := 0.0: # 悬崖厚度(0 = 平面现行;档位 4/8/14) + set(v): + depth_px = v + queue_redraw() +var slab := Vector2.ZERO: # 网格矩形像素尺寸(不含悬崖外延) + set(v): + slab = v + queue_redraw() +var stride_px := 48.0: # 格步距(cell + SEP——列分隔缝对齐格边界) + set(v): + stride_px = v + queue_redraw() +var cols := 0: # 列数(正面悬崖竖缝数 = cols−1) + set(v): + cols = v + queue_redraw() +var mat := Color8(201, 175, 131) # 材质锚色(掩尘 #C9AF83——侧面由此派生明暗;BoardView 传入) + + +func _draw() -> void: + if depth_px <= 0.0 or slab == Vector2.ZERO: + return + var w := slab.x + var h := slab.y + var d := depth_px + var front := mat.darkened(0.55) # 正面悬崖(背光) + var right := mat.darkened(0.38) # 右侧悬崖(微受光) + # 正面悬崖带 + 右侧悬崖带 + draw_rect(Rect2(0, h, w, d), front, true) + draw_rect(Rect2(w, 0, d, h + d), right, true) + # 正面列分隔竖缝(对齐格边界——ITB 悬崖列感) + for i in range(1, cols): + var x := float(i) * stride_px - 1.0 + draw_rect(Rect2(x, h, 2.0, d), front.darkened(0.25), true) + # 岩层横纹(两条微暗横带——碎石层理) + draw_rect(Rect2(0, h + d * 0.38, w, 1.5), front.darkened(0.18), true) + draw_rect(Rect2(0, h + d * 0.72, w, 1.5), front.darkened(0.12), true) + # 悬崖口沿高光(顶面与悬崖交界 1px 亮线——倒角受光) + draw_rect(Rect2(0, h, w, 1.0), mat.lightened(0.18), true) + draw_rect(Rect2(w, 0, 1.0, h), mat.lightened(0.12), true) + # 顶面接触 AO(网格内侧贴边 2px 暗带——顶面陷进阴影的衔接感) + draw_rect(Rect2(0, h - 2.0, w, 2.0), Color(0, 0, 0, 0.18), true) + draw_rect(Rect2(w - 2.0, 0, 2.0, h), Color(0, 0, 0, 0.14), true) + # 底线(悬崖最底 2px 最暗——收边) + draw_rect(Rect2(0, h + d - 2.0, w + d, 2.0), front.darkened(0.35), true) + # 底部悬浮影(中厚档起——悬浮岛离地感;三层渐隐) + if d >= 8.0: + var layers := [Color(0, 0, 0, 0.16), Color(0, 0, 0, 0.10), Color(0, 0, 0, 0.05)] + for i in layers.size(): + var sh := 3.0 + float(i) * 3.0 + draw_rect(Rect2(sh, h + d + sh * 0.5, w + d - sh * 1.5, float(2 + i)), layers[i], true)