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。
153 lines
7.1 KiB
GDScript
153 lines
7.1 KiB
GDScript
## 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
|