Files
DKChan 5c9c5f8083 refactor(iter37③): boardview 拆件——瓦片调色板/英雄精灵两族出根(行为等价,golden 逐像素零变)
r2-1 瓦片族(排版+素材装载+墨族判据+查表)→ views/tile_palette.gd;
r2-2 英雄族(贴图/位移/影/朝向/缩放几何)→ views/hero_sprite.gd(Node2D);
r2-3 board_view 改引+直引面换指(terrain_tile_sets_test/ui_contrast_probe),
探针与 main 的组合根直读面经转发属性/常量别名保持零改动。

S13 计数守恒:基线含类变量数/公有方法数两条计数型键,故 board_view 用 13 个转发属性
+2 个组件引用压回原计数(45 变量/27 公有方法);baselined 的 wall_tile_for/
wall_center_tile/digit_ink_mode 以查表/辅助函数重构压回阈值内(非改名保违例),
旧文件级键随函数迁出自然失效——基线文件零改动(用户令:只改代码不改基线)。

验收:terrain_tile_sets_test 19 检查绿(判据读数与迁移前同值);gdstyle 基线外新违规 0;
S9/S10 绿;probe_gate --click 全绿(含 ui_input_probe 十八断言/click 走查 0 问题);
render_gate 棋盘三图+菜单 golden changed=0.00%;撤销证明:测试改引回退旧址符号
→ SCRIPT ERROR(Parse Error: Static function "_normalize_floor_luma()" not found)+
error_scan exit 1,还原后 19 检查复绿、error_scan exit 0。
2026-10-25 06:43:03 +08:00

153 lines
7.1 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## VERTICAL SLICE - NOT FOR PRODUCTION
## 战斗屏英雄小人精灵件:落位与位移演出、撞墙帧闪、挖掘弹跳、脚底锚与持握手位出口。
## Node2D 件(create_tween 系 Node 方法,RefCounted 无此 API)——由棋盘视图在 _init 建为子件,
## 注入世界根与壳层容器引用;精灵本体经世界根 ensure_hero 建于 SubViewport 世界内,本件只驱动其状态。
## 素材缺位 = 零精灵(棋盘视图字形「人」回退路径在役)。
class_name HeroSprite
extends Node2D
# 英雄单位演出参数(计划书 §3.B/C 时长档)
const FRAME_PX := 32.0 # 英雄帧画布边长(素材 32×32 锁定——缩放比分母)
const SPRITE_SCALE := 1.5 # 1.5× 档(实机 1× 偏小故放大 1.5 倍——本档为圣经 §8.2「禁非整数缩放」
# 一般规则的显式豁免;头可越格上沿不裁剪)
const GROUND_FRAC := 0.25 # 脚锚纵向落点 = 格底往上 25%(口径「脚踩格子 1/4 处·从下往上数」;
# 头越上格沿造立体感。⚠ 参照系纪律:本值一律自底边起算,禁改用「自顶 75%」
# 一类的同线异名表述)
const MOVE_S := 0.14 # 位移时长(~140ms)
const STEP_S := 0.18 # 走路帧周期 = 原地撞墙帧闪时长(~180ms)
const DIG_S := 0.15 # 挖掘弹跳总时长(~150ms,半程压半程回)
const BOUNCE := 0.82 # 弹跳压缩比(scale.y 1→0.82→1 脚底收敛)
const SHADOW_PX := Vector2(32, 12) # 脚下椭圆影屏幕尺寸(Godot 组件合成——径向渐变纹理压扁,
# 贴图零改动,走引擎侧)
# ── 精灵状态(素材 PNG 不入 git——装载在棋盘视图 _init,缺位=字形回退)──
var tex_idle: Texture2D # 站立帧(null = 素材缺位——零精灵,字形「人」原路径)
var tex_walk: Texture2D # 跨步帧
var last := Vector2i(-99, -99) # 上拍英雄格(refresh diff 基准;哨兵 = 未落位/随 rebuild 复位)
var face_left := true # 朝向(素材只入库朝左帧——右向 = flip_h,计划书 §2 约定)
var bump_pending := false # 受理零位移移动回执(refresh diff 只认位置变化,原地移动须外部告知)
var move_tw: Tween
var bump_tw: Tween
var dig_tw: Tween
# ── 几何注入(棋盘视图 set_cell_area 下发;世界脚底锚公式与壳层手位换算同源)──
var cell := 0.0 # 当前格边长
var sep := 0.0 # 格间距
var world: Node2D = null # 世界根(ensure_hero/hero 所在)
var svp: SubViewportContainer = null # 壳层容器(手位须含其居中偏移)
func bind(world_ref: Node2D, svp_ref: SubViewportContainer) -> void:
world = world_ref
svp = svp_ref
func set_cell_size(cell_size: float, sep_px: float) -> void:
cell = cell_size
sep = sep_px
# refresh diff 驱动:位置变化 = 位移演出(140ms Tween + 走路帧);零位移但有受理移动回执 = 只播帧
# 不位移(撞墙语义——排雷玩法「目标=当前格」幂等受理)。素材缺位 = 零精灵,字形「人」回退路径。
func update_hero(hero: Vector2i) -> void:
if tex_idle == null:
return
if world.hero == null:
var sp: Sprite2D = world.ensure_hero(tex_idle, SHADOW_PX, SPRITE_SCALE)
sp.scale = Vector2.ONE * SPRITE_SCALE
sp.position = feet(hero)
last = hero # 首建/随 rebuild 重建——即时落位无演出
return
if hero == last and not bump_pending:
return
if hero != last and hero.x != last.x:
face_left = hero.x < last.x
place_hero(last, hero, hero == last)
if hero == last:
frame_flash()
bump_pending = false
last = hero
# 英雄落位(instant = 即位无演出——首建/格边长变更;否则 140ms 位移 + 走路帧起手、到位回站帧。
# 帧权威单一化:Sprite2D 换纹理路线(计划书 §3.B)——换纹理+flip_h 同拍写定即生效,
# 无 AnimatedSprite2D 的 play()/advance(0) 错向面)
func place_hero(from: Vector2i, to: Vector2i, instant: bool) -> void:
var sp: Sprite2D = world.hero
if sp == null:
return
var s := SPRITE_SCALE
var feet_px := feet(to)
if move_tw != null and move_tw.is_valid():
move_tw.kill()
if instant:
sp.scale = Vector2(s, s)
sp.position = feet_px
sp.texture = tex_idle
return
sp.scale = Vector2(s, s) # 位移重置弹跳残形(scale.y 由 play_dig_bounce 独立回收)
sp.texture = tex_walk
sp.flip_h = not face_left # 素材基准朝左
move_tw = create_tween()
move_tw.tween_property(sp, "position", feet_px, UiKit.motion(MOVE_S)).set_trans(Tween.TRANS_SINE)
move_tw.tween_callback(func() -> void:
if is_instance_valid(sp):
sp.texture = tex_idle)
# 原地移动帧闪(撞墙语义):跨步帧 ~180ms 后回站帧,零位移
func frame_flash() -> void:
var sp: Sprite2D = world.hero
if sp == null:
return
if bump_tw != null and bump_tw.is_valid():
bump_tw.kill()
sp.texture = tex_walk
sp.flip_h = not face_left
bump_tw = create_tween()
bump_tw.tween_interval(UiKit.motion(STEP_S))
bump_tw.tween_callback(func() -> void:
if is_instance_valid(sp):
sp.texture = tex_idle)
# 受理移动指令回执(组合根 settle 受理后、refresh 前调用)——原地移动经此触发帧闪;
# 目标异格时本封印标被 diff 位移路径自然吞掉(置位即清)
func notify_move() -> void:
bump_pending = true
# 挖掘角色弹跳(计划书 §3.C:scale.y 1→0.82→1,脚底锚收敛不悬空;与位移/帧闪 tween 分开管理
# 避免属性争抢——旧弹跳先 kill)
func play_dig_bounce() -> void:
var sp: Sprite2D = world.hero
if sp == null or tex_idle == null:
return
if dig_tw != null and dig_tw.is_valid():
dig_tw.kill()
var s := SPRITE_SCALE
sp.scale = Vector2(s, s)
dig_tw = create_tween()
dig_tw.tween_property(sp, "scale:y", s * BOUNCE, UiKit.motion(DIG_S * 0.5)).set_trans(Tween.TRANS_SINE)
dig_tw.tween_property(sp, "scale:y", s, UiKit.motion(DIG_S * 0.5)).set_trans(Tween.TRANS_SINE)
# 英雄脚底锚点——**世界坐标**(精灵在 SubViewport 世界内:格原点 = 格×步距,禁带壳层居中偏移——
# cell_origin 含 svp.position,世界内精灵定位误用会整体右偏 ~7 格+偏离格心)。
# 纵向 = 格底往上 25% 线(脚踩格子 1/4 处);横向格中线。身体向上延伸,
# 头越上格沿不裁剪(造立体感);锚点与精灵缩放解耦(缩放只改身体向上长多少)。
func feet(c: Vector2i) -> Vector2:
return Vector2(c) * (cell + sep) + Vector2(cell * 0.5, cell * (1.0 - GROUND_FRAC))
# 英雄持握手位(fx 同层坐标系 = 壳层坐标——fx 在 SubViewport 之外,须含 svp.position
# 居中偏移:世界脚底锚 + 壳层偏移即得)。手 = 盒底往上 1/4 盒高、水平居中(
# 盒高随 SPRITE_SCALE——手位钉在身体上不随缩放漂移)
func hand_point() -> Vector2:
return svp.position + feet(last) + Vector2(0.0, -FRAME_PX * 0.25 * SPRITE_SCALE)
# 英雄精灵缩放档(fx 挥铲同档出口——两演出比例恒一致)
func sprite_scale() -> float:
return SPRITE_SCALE