diff --git a/game/presentation/views/battle_stage_view.gd b/game/presentation/views/battle_stage_view.gd index b1c5a1f8..b31ce8f6 100644 --- a/game/presentation/views/battle_stage_view.gd +++ b/game/presentation/views/battle_stage_view.gd @@ -35,7 +35,7 @@ func _init(db_ref) -> void: vb.add_child(row) hero_standee = StandeeView.new("hero") - hero_standee.set_portrait_texture(UiKit.load_tex(UiKit.TEX_HERO_IDLE)) # 棋盘小人贴图批 2026-09-25(挂点现成——null 回退字形「人」;NEAREST 归 set_portrait_texture 单点) + hero_standee.set_portrait_texture(UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT), true) # B 区立绘 48×48 朝右(2026-09-26 用户点名 hero_idle_01 换装+水平向右;素材缺位 null 回退字形「人」) row.add_child(hero_standee) var center := CenterContainer.new() diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 3f34c0c7..1b4dda3b 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -339,16 +339,18 @@ func cell_origin(c: Vector2i) -> Vector2: return _svp_container.position + Vector2(c) * (_cell + float(SEP)) -# 英雄 32×32 盒格内居中的脚底锚点(盒底中点——弹跳 scale.y 向此收敛) +# 英雄 32×32 盒格内居中的脚底锚点——**世界坐标**(精灵在 SubViewport 世界内:格原点 = 格×步距, +# 禁带壳层居中偏移。2026-09-26 修正:上批误用壳层 cell_origin(含 _svp_container.position 居中 +# 偏移)定位世界内精灵,实测整体右偏 ~7 格+偏离格心——实机截图用户报缺陷) 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) + return Vector2(c) * (_cell + float(SEP)) + Vector2(m + HERO_FRAME_PX * 0.5, m + HERO_FRAME_PX) -# 英雄持握手位(fx 同层坐标系)——挥铲特效圆心(2026-09-25 用户裁决:以手为圆心、铲长为半径 -# 旋转;手 = 盒底往上 1/4 盒高、水平居中) +# 英雄持握手位(fx 同层坐标系 = 壳层坐标——fx 在 SubViewport 之外,须含 _svp_container.position +# 居中偏移:世界脚底锚 + 壳层偏移即得)。手 = 盒底往上 1/4 盒高、水平居中(2026-09-25 用户裁决) func hero_hand_point() -> Vector2: - return _hero_feet(_hero_last) + Vector2(0.0, -HERO_FRAME_PX * 0.25) + return _svp_container.position + _hero_feet(_hero_last) + Vector2(0.0, -HERO_FRAME_PX * 0.25) # 英雄精灵缩放档(fx 挥铲同档出口——两演出比例恒一致) diff --git a/game/presentation/views/standee_view.gd b/game/presentation/views/standee_view.gd index 3c37528c..c0115984 100644 --- a/game/presentation/views/standee_view.gd +++ b/game/presentation/views/standee_view.gd @@ -224,19 +224,23 @@ func _play_intent_motion() -> void: tw.tween_property(_portrait, "rotation_degrees", 0.0, UiKit.motion(0.14)) -# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形 -func set_portrait_texture(tex: Texture2D) -> void: +# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。 +# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右——2026-09-26 用户点名英雄立绘水平向右)。 +# 盒高 96px = 48px 立绘源整数 2×(圣经 §8.2 禁非整数缩放;offset_top 44 = 血条+状态行之下净空)—— +# 源换代时随源重算盒高。 +func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void: if _portrait_tex == null: _portrait_tex = TextureRect.new() _portrait_tex.expand_mode = TextureRect.EXPAND_IGNORE_SIZE _portrait_tex.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED - _portrait_tex.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 32px 像素帧放大防糊(棋盘小人贴图批 2026-09-25) + _portrait_tex.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊(棋盘小人贴图批 2026-09-25) _portrait_tex.set_anchors_preset(Control.PRESET_FULL_RECT) _portrait_tex.offset_top = 44.0 - _portrait_tex.offset_bottom = -26.0 + _portrait_tex.offset_bottom = -36.0 # 盒高 96 = 48 源 ×2 整数档(原 −26 盒高 106 系非整数缩放) _portrait_tex.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(_portrait_tex) _portrait_tex.texture = tex + _portrait_tex.flip_h = flip _portrait_tex.visible = tex != null _portrait.visible = tex == null diff --git a/game/presentation/views/ui_kit.gd b/game/presentation/views/ui_kit.gd index 075ede83..b732d8f0 100644 --- a/game/presentation/views/ui_kit.gd +++ b/game/presentation/views/ui_kit.gd @@ -79,6 +79,7 @@ static func motion(dur: float) -> float: # 的字形/无演出回退路径;禁 preload(素材缺位树上直接断言炸脚本,冒烟安全红线) const TEX_HERO_IDLE := "res://assets/art/character/character_hero_idle.png" const TEX_HERO_WALK := "res://assets/art/character/character_hero_walk.png" +const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘 48×48(源档 candidates/hero-idle/hero_idle_01.png,2026-09-26 用户点名换装) # 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退) diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index 7f4817a3..790a74aa 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -4,7 +4,8 @@ # 本探针是真实输入路径的唯一自动化覆盖:鼠标事件 → 壳层 _gui_input → 板级公式换算(_cell_at)→ # 悬停/点选/chord 既有出口 → 组合根信号。四 case:①悬停可挖域(金框+十字光标)②悬停禁入 # (hover_forbidden 信号+灰禁红框)③悬停已开格(路径预览点迹+移动光标)④右键插旗(flag_place -# 到达组合根+核心旗态)⑤双击 chord(禁入格双左击,核心拒挖零副作用)⑥左键挖(dig 到达组合根+核心揭格)。 +# 到达组合根+核心旗态)⑤双击 chord(禁入格双左击,核心拒挖零副作用)⑥左键挖(dig 到达组合根+核心揭格) +# ⑦英雄精灵世界落位(棋盘小人贴图批回归闸——SubViewport 世界坐标判据,素材缺位树跳过)。 # 事件路径:Viewport.push_input(ev, in_local_coords=true)——落点用 BoardView.cell_center_local # (与 _cell_at 同一公式族)换算画布坐标,SubViewportContainer mouse_filter=IGNORE 不拦截。 # 运行:Godot_console.exe --headless --path . res://smoke/ui_input_probe.tscn @@ -120,8 +121,20 @@ func _ready() -> void: await get_tree().process_frame if not ms.is_revealed(dig_t): _problems.append("case⑥ 核心揭格未落位(目标=%s)" % dig_t) + # ---- case⑦ 英雄精灵世界落位(棋盘小人贴图批回归闸 2026-09-26):精灵在 SubViewport 世界内, + # 落位 = 格×步距 + 居中脚底锚(禁带壳层 _svp_container.position 居中偏移——上批误用壳层 + # cell_origin 实测右偏 ~7 格+偏离格心,实机截图用户报缺陷)。素材缺位树(hero==null, + # 字形「人」回退在役)跳过;断言前等 0.3s 墙钟——headless 帧率无上限,帧数等待 < 位移 + # Tween 140ms 时会采到半途位置(首跑实证),必须按墙钟等到位。 ---- + if bv._world.hero != null: + await get_tree().create_timer(0.3).timeout + var hm: float = (bv.cell_size - bv.HERO_FRAME_PX) * 0.5 + var expect: Vector2 = Vector2(ms.hero_pos()) * (bv.cell_size + float(bv.SEP)) \ + + Vector2(hm + bv.HERO_FRAME_PX * 0.5, hm + bv.HERO_FRAME_PX) + if bv._world.hero.position.distance_to(expect) > 0.5: + _problems.append("case⑦ 英雄精灵世界落位偏移 pos=%s expect=%s" % [str(bv._world.hero.position), str(expect)]) if _problems.is_empty(): - print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig 六断言全绿(真实 push_input 路径)") + print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位 七断言全绿(真实 push_input 路径)") get_tree().quit(0) else: for p in _problems: