## VERTICAL SLICE - NOT FOR PRODUCTION ## 站桩资产与演出态件(StandeeBody 拆件——单一变化原因=美术素材落位规则与演出编排设计): ## 站桩的资产挂点与一次性演出——立绘精灵落位与换装、主角三态注册与 0.35s 短驻回归、意图徽标弹入动画。 ## 无 class_name(类名缓存面纪律)——经宿主 standee_body preload 引用(mo 实例); ## 两个属性 setter 经 _owner 回引触发宿主重绘语义逐字;tween 经 _owner 创建(同一 Node 生命绑定)。 extends RefCounted enum HeroState { IDLE, RESET, ATTACK, HIT } # 主角三态+复位哨兵(外部 String 契约=register/set 形参零变化) const _HS_NAME := {HeroState.IDLE: "idle", HeroState.RESET: "reset", HeroState.ATTACK: "attack", HeroState.HIT: "hit"} const _HS_VALUE := {"idle": HeroState.IDLE, "reset": HeroState.RESET, "attack": HeroState.ATTACK, "hit": HeroState.HIT} var _owner: Node2D = null var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位) var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据) # 站桩立绘显示档(三档绝对高——art-bible §8.2 显示档定值单点;九键逐键钉值,禁兜底字面量): # 小型 96 = 地精/巨蜘蛛/灰矮人;中型 144 = 主角/钩爪恐魔/熊地精;大型 188 = 酋长/卓尔/石君王。 # 显示倍率 = 档高 / 源高(宽随源比例);源高 = 原稿表值(Python 侧 STANDEE_SRC_H—— # 跨语言逐键对账由 standee_present_values_test V17 承担)。 const PORTRAIT_DISPLAY_H := { "goblin": 96.0, "giant_spider": 96.0, "duergar": 96.0, "hook_horror": 144.0, "bugbear": 144.0, "hero": 144.0, "ogre_chief": 188.0, "drider": 188.0, "duergar_king": 188.0, } # 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理 # (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。 var _hero_tex := {} var _hero_state: int = 0 var _state_revert_tw: Tween var _intent_glyph := "" var _intent_col := Color.WHITE var _intent_visible := false var _intent_scale := 1.0: # 意图弹入动画参数 set(v): _intent_scale = v _owner.queue_redraw() var _glyph_rot := 0.0: # 立绘前倾角(贴图态无此动画) set(v): _glyph_rot = v _owner.queue_redraw() func _init(owner: Node2D) -> void: _owner = owner # 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。 # flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。显示高 = PORTRAIT_DISPLAY_H # 逐键绝对高(art-bible §8.2 三档),宽随源比例(高定宽随)。 # (box = 宿主冻结 var,由宿主传入) func set_portrait_texture(tex: Texture2D, flip: bool, box: Vector2) -> void: if tex == null: if _portrait_sprite != null: _portrait_sprite.visible = false _owner.queue_redraw() return _portrait_tex = tex if _portrait_sprite == null: _portrait_sprite = Sprite2D.new() _portrait_sprite.centered = false _portrait_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊 _owner.add_child(_portrait_sprite) _portrait_sprite.texture = tex _portrait_sprite.flip_h = flip _portrait_sprite.visible = true _place_portrait(box) func _place_portrait(box: Vector2) -> void: # 立绘盒 = (0,44)-(box.x, box.y-36);显示档逐键取 PORTRAIT_DISPLAY_H 绝对高, # 宽随源比例(高定宽随);高于盒者沿既有居中溢出机制((box_h-h)*0.5 为负即上溢) if _portrait_sprite == null or _portrait_tex == null: return var eid: String = _owner.enemy_id() var key := "hero" if eid.is_empty() else eid var box_h := box.y - 44.0 - 36.0 var disp_h: float = PORTRAIT_DISPLAY_H.get(key, -1.0) if disp_h < 0.0: # 缺键不崩、不静默兜档——push_error 报警后按 1× 落位(V18 键集自检在测试期报警) push_error("[站桩] 显示档表缺键: %s(逐键登记纪律)" % key) _portrait_sprite.scale = Vector2.ONE _portrait_sprite.position = Vector2((box.x - _portrait_tex.get_width()) * 0.5, 44.0 + (box_h - _portrait_tex.get_height()) * 0.5) return var s := disp_h / float(_portrait_tex.get_height()) _portrait_sprite.scale = Vector2(s, s) _portrait_sprite.position = Vector2((box.x - _portrait_tex.get_width() * s) * 0.5, 44.0 + (box_h - disp_h) * 0.5) # 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理) func register_hero_state(state: String, tex: Texture2D) -> void: if tex != null: _hero_tex[state] = tex # 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。 # 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。 #(is_hero = 宿主 vitals._kind == "hero" 参判传入——三连早退条件序逐字) func apply_hero_state(state: String, is_hero: bool) -> void: var target: int = _HS_VALUE.get(state, -1) if not is_hero or target == _hero_state: return if not _hero_tex.has(state) and state != "idle": return _hero_state = target if _portrait_sprite != null and _hero_tex.has(state): _portrait_sprite.texture = _hero_tex[state] if _state_revert_tw != null and _state_revert_tw.is_valid(): _state_revert_tw.kill() if state != "idle": _state_revert_tw = _owner.create_tween() _state_revert_tw.tween_interval(UiKit.motion(0.35)) _state_revert_tw.tween_callback(_settle_hero_idle) func _settle_hero_idle() -> void: _hero_state = HeroState.IDLE if _portrait_sprite != null and _hero_tex.has("idle"): _portrait_sprite.texture = _hero_tex["idle"] # 复位强制绕同态早退两拍封装(原 _revive 内 hero 分支——先置 RESET 哨兵再回 idle) func reset_state() -> void: _hero_state = HeroState.RESET # 强制回 idle 态(绕过同态早退) apply_hero_state("idle", true) # 意图三档动作动画(§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕 # ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落; # 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。 #(charge = 宿主 _intent_charge 分档布尔参判传入) func play_intent_motion(charge: bool) -> void: var tw := _owner.create_tween() tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10)) tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16)) tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10)) tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14)) if charge: tw.tween_property(self, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推 tw.tween_interval(UiKit.motion(0.40)) # 定格微顿 tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.18)) else: tw.tween_property(self, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举) tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.13)) # 三态探针出口(fx_probe——当前态名+注册态集合;宿主 hero_state_probe 读件投影,外形零变) func state_probe() -> Dictionary: return {"state": _HS_NAME.get(_hero_state, "idle"), "registered": _hero_tex.keys()} # ---- 意图徽标行为与绘制读口(pres封装 A2——宿主经此读写,字段保持私有)---- # 意图徽标显隐/设定(宿主 set_pending/mark_dead 三处同型归位——显示后由宿主拍 play_intent_motion) func show_intent(glyph: String, col: Color) -> void: _intent_glyph = glyph _intent_col = col _intent_visible = true func hide_intent() -> void: _intent_visible = false # 绘制路径读投影(宿主 _draw 每帧调用——逐 getter 免逐帧字典分配) func intent_visible() -> bool: return _intent_visible func intent_glyph() -> String: return _intent_glyph func intent_col() -> Color: return _intent_col func intent_scale() -> float: return _intent_scale func intent_rot() -> float: return _glyph_rot func portrait_sprite() -> Sprite2D: return _portrait_sprite