## 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