diff --git a/game/presentation/main.gd b/game/presentation/main.gd index c4d7d42c..59920f0e 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -686,10 +686,11 @@ func _on_board_action(cell: Vector2i, kind: String) -> void: if accepted and kind == "dig" and settlement != null and settlement.minesweeper.current_board() != null and settlement.minesweeper.clear_map_cleared(): board_zone.fx.play_clear_burst() if accepted and kind == "dig" and settlement != null and settlement.minesweeper != null: - # 挖掘演出(棋盘小人贴图批 2026-09-25):挥铲三帧 + 角色弹跳——受理后一拍触发(挖掘不移动英雄, - # 出手方向 = 目标格−英雄格;refresh 同拍重绘零冲突) + # 挖掘演出(棋盘小人贴图批 2026-09-25):挥铲三帧 + 角色弹跳——触发=格子点开那一刻 + #(2026-09-25 用户裁决;settle_batch 同步受理即状态已变,动画纯表现与 refresh 同拍零竞态; + # 挖掘不移动英雄,出手方向 = 目标格−英雄格,圆心=英雄持握手位) var hero_cell: Vector2i = settlement.minesweeper.hero_pos() - board_zone.fx.play_shovel_swing(board_view.cell_origin(hero_cell), cell - hero_cell, board_view.cell_size) + board_zone.fx.play_shovel_swing(board_view.hero_hand_point(), cell - hero_cell, board_view.hero_sprite_scale()) board_view.play_hero_dig_bounce() elif accepted and kind == "move": board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知 diff --git a/game/presentation/views/board_fx_view.gd b/game/presentation/views/board_fx_view.gd index 12fdfa6f..bcd3edbd 100644 --- a/game/presentation/views/board_fx_view.gd +++ b/game/presentation/views/board_fx_view.gd @@ -125,15 +125,16 @@ func play_flag_hit_projectile(heavy: bool) -> void: const SHOVEL_FRAME_PX := 32.0 # 铲帧画布边长(缩放比与英雄帧同源) const SHOVEL_FRAME_S := 0.08 # ~80ms/帧(f0→f1→f2 共 ~240ms 即收) -const SHOVEL_GRIP := Vector2(18, 12) # 握把锚点(素材侧锁定的画布坐标——出手位对齐点) +const SHOVEL_GRIP := Vector2(18, 12) # 握把锚点(素材侧锁定的画布坐标——英雄持握手位对齐点) var _shovel_texs: Array = [] # 惰性装载缓存(素材缺位 = 空表静默回退——素材不入 git) -# 挖掘挥铲:origin = 英雄格左上(board_area 坐标系——BoardView.cell_origin 出口,fx 与棋盘同层同系); -# dir = 挖掘方向单位格向量(素材铲头朝左——dir.x>0 翻转);edge = 格边长(缩放 = edge/32 与角色一致)。 -# 握把锚对齐英雄格与目标格共享边中点(出手位),铲头弧线自然落进目标格。 -func play_shovel_swing(origin: Vector2, dir: Vector2i, edge: float) -> void: +# 挖掘挥铲:hand = 英雄持握手位(BoardView.hero_hand_point 出口,fx 与棋盘同层同系)—— +# 以手为圆心、铲长为半径旋转(2026-09-25 用户裁决;三帧系素材侧预烤的旋转相位,握把锚 (18,12) +# 三帧同位,锚点钉死手位即成弧);dir = 挖掘方向单位格向量(素材铲头朝左——dir.x>0 翻转); +# sprite_scale 与英雄精灵同档(整数 1×——原「共享边中点+12% 偏置」定位与 edge/32 缩放作废)。 +func play_shovel_swing(hand: Vector2, dir: Vector2i, sprite_scale: float) -> void: if _shovel_texs.is_empty(): for p in ["res://assets/art/fx/shovel_swing_f0.png", "res://assets/art/fx/shovel_swing_f1.png", @@ -142,20 +143,17 @@ func play_shovel_swing(origin: Vector2, dir: Vector2i, edge: float) -> void: if t == null: return # 素材缺位——静默回退(fx 层占位演出不设断言面) _shovel_texs.append(t) - var s := edge / SHOVEL_FRAME_PX var flip := dir.x > 0 var grip := SHOVEL_GRIP if flip: grip.x = SHOVEL_FRAME_PX - SHOVEL_GRIP.x # flip_h 围绕纹理中心镜像——锚点 x 随镜 - var deliver := origin + Vector2(edge, edge) * 0.5 \ - + Vector2(dir) * (edge * 0.5 + edge * 0.12) # 共享边中点再向目标格偏 12% 格宽 var sp := Sprite2D.new() sp.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST sp.centered = true sp.texture = _shovel_texs[0] sp.flip_h = flip - sp.scale = Vector2.ONE * s - sp.position = deliver + (Vector2(SHOVEL_FRAME_PX, SHOVEL_FRAME_PX) * 0.5 - grip) * s + sp.scale = Vector2.ONE * sprite_scale + sp.position = hand + (Vector2(SHOVEL_FRAME_PX, SHOVEL_FRAME_PX) * 0.5 - grip) * sprite_scale add_child(sp) var tw := sp.create_tween() for i in [1, 2]: diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index d10ba047..3f34c0c7 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -57,6 +57,10 @@ const DEPTH_BEVEL := 0.6 # 顶面倒角强度 # 英雄单位演出参数(棋盘小人贴图批 2026-09-25——计划书 §3.B/C 时长档) const HERO_FRAME_PX := 32.0 # 英雄帧画布边长(素材 32×32 锁定——缩放比分母) +const HERO_SPRITE_SCALE := 1.0 # 整数 1× 档、32×32 盒格内居中(2026-09-25 用户裁决:不要求覆盖 + # 格子——圣经 §8.2「禁非整数缩放」硬规则,v1.9 注记 45px 格 1× 占 + # ≈71% 合规;格边 <32 板溢出 ≤(32−cell)px 接受;计划书原 cell_edge/32 + # 填充公式作废) const HERO_MOVE_S := 0.14 # 位移时长(~140ms) const HERO_STEP_S := 0.18 # 走路帧周期 = 原地撞墙帧闪时长(~180ms) const HERO_DIG_S := 0.15 # 挖掘弹跳总时长(~150ms,半程压半程回) @@ -251,8 +255,8 @@ func _update_hero(hero: Vector2i) -> void: return if _world.hero == null: var sp: Sprite2D = _world.ensure_hero(_hero_tex_idle) - sp.scale = Vector2.ONE * (_cell / HERO_FRAME_PX) - sp.position = Vector2(hero) * (_cell + float(SEP)) + Vector2(_cell * 0.5, float(_cell)) + sp.scale = Vector2.ONE * HERO_SPRITE_SCALE + sp.position = _hero_feet(hero) _hero_last = hero # 首建/随 rebuild 重建——即时落位无演出 return if hero == _hero_last and not _hero_bump_pending: @@ -273,8 +277,8 @@ func _place_hero(from: Vector2i, to: Vector2i, instant: bool) -> void: var sp: Sprite2D = _world.hero if sp == null: return - var s := _cell / HERO_FRAME_PX - var feet := Vector2(to) * (_cell + float(SEP)) + Vector2(_cell * 0.5, float(_cell)) + var s := HERO_SPRITE_SCALE + var feet := _hero_feet(to) if _hero_move_tw != null and _hero_move_tw.is_valid(): _hero_move_tw.kill() if instant: @@ -322,7 +326,7 @@ func play_hero_dig_bounce() -> void: return if _hero_dig_tw != null and _hero_dig_tw.is_valid(): _hero_dig_tw.kill() - var s := _cell / HERO_FRAME_PX + var s := HERO_SPRITE_SCALE sp.scale = Vector2(s, s) _hero_dig_tw = create_tween() _hero_dig_tw.tween_property(sp, "scale:y", s * HERO_BOUNCE, UiKit.motion(HERO_DIG_S * 0.5)).set_trans(Tween.TRANS_SINE) @@ -335,6 +339,23 @@ func cell_origin(c: Vector2i) -> Vector2: return _svp_container.position + Vector2(c) * (_cell + float(SEP)) +# 英雄 32×32 盒格内居中的脚底锚点(盒底中点——弹跳 scale.y 向此收敛) +func _hero_feet(c: Vector2i) -> Vector2: + var m := (_cell - HERO_FRAME_PX) * 0.5 + return cell_origin(c) + Vector2(m + HERO_FRAME_PX * 0.5, m + HERO_FRAME_PX) + + +# 英雄持握手位(fx 同层坐标系)——挥铲特效圆心(2026-09-25 用户裁决:以手为圆心、铲长为半径 +# 旋转;手 = 盒底往上 1/4 盒高、水平居中) +func hero_hand_point() -> Vector2: + return _hero_feet(_hero_last) + Vector2(0.0, -HERO_FRAME_PX * 0.25) + + +# 英雄精灵缩放档(fx 挥铲同档出口——两演出比例恒一致) +func hero_sprite_scale() -> float: + return HERO_SPRITE_SCALE + + # 真实输入路径(行为探针复用):合成点击 → 与真实 gui_input 同一翻译出口 func click_cell(cell: Vector2i, button: int) -> void: var ev := InputEventMouseButton.new()