iter45-卡①T2: 站桩两状态件建件与宿主改造——_StandeeState 真实25成员沿身份命值/资产演出缝拆 standee_vitals(12成员,apply_hp=_set_hp整体搬移,hp_value setter逐字)/standee_motion(11成员,六迁入函数:set_portrait_texture(tex,flip,box)/register_hero_state/apply_hero_state(is_hero参判,三连早退序逐字)/reset_state(RESET绕同态早退两拍封装)/play_intent_motion(charge参判)/state_probe),HeroState枚举与_HS_NAME/_HS_VALUE随三态机归motion(全仓零外部消费实测);宿主10成员(五冻结var+st/mo+三演出探针计数器上收),转调壳三入口外形零变,fx_probe改读宿主计数器,tween路径串st.hp_value/mo._intent_scale/mo._glyph_rot,源码锚三函数(_draw_hp_bar/_draw_armor_name_line/set_pending)留宿主且锚行逐字(st=vitals名沿用故血条/名字行零改写)〔状态件 V3〕;battle_stage_view一行未改实测;孤立验证run_all两套件全绿+run_smoke

This commit is contained in:
DKChan committed 2026-10-31 00:15:39 +08:00
1 parent ea03765adc
commit 75780e31fa
3 files changed
+210 -161

No files matched your search

+46 -161
View File
@@ -2,9 +2,9 @@
## 站桩本体:B 区主角/敌人无框立绘+底座+血条+顺位/意图徽标的世界精灵(Node2D)。
## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。
## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。
## 内部状态件(u4 子域拆件——契约 9 职责语句「站桩演出状态」):单一变化原因=站桩呈现与演出,
## 成员共同表达站桩的行进态/演出计数/资产挂点。外部直访冻结成员(box/font/slot_row/has_world_hp_bar)
## 与源码锚承载成员(_intent_charge——theme 源锚句所在行逐字保留)住宿主。
## 内部状态两件(u4 拆件——st=身份命值态 standee_vitals、mo=资产演出态 standee_motion,_init 自建);
## 外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员(_intent_charge——theme
## 源锚句所在行逐字保留)与三个演出探针计数器住宿主。
extends Node2D
const CHARGE_SKILL_IDS := ["heavy"]
@@ -16,65 +16,28 @@ const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"}
const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值)
const HIT_SPARK_N := 6 # 命中火花数(打击瞬间可读性一拍)
const _VITALS := preload("res://presentation/views/standee_vitals.gd")
const _MOTION := preload("res://presentation/views/standee_motion.gd")
## 站桩演出状态(全部内部态单点——含 setter 触发宿主重绘语义,_owner 回引保持逐字)
class _StandeeState extends RefCounted:
var _owner: Node2D = null
var _kind := "enemy"
var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位)
var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据)
# 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理
# (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。
var _hero_tex := {}
var _hero_state: int = 0
var _state_revert_tw: Tween
var _order := 1
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()
var _display_name := ""
var _enemy_id := ""
var _armor := "light"
var _dead := false
var _hp_max := 1.0
var _hp_cur := 0 # 血量数字显示面(HUD框架 验收「血量数字可读」)
var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积)
set(v):
hp_value = clampf(v, 0.0, 1.0)
_owner.queue_redraw()
var _hp_tween: Tween
var _hp_flash_tw: Tween
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
var _hit_sparks := 0 # 探针出口计数(命中火花累计)
var _flash_count := 0 # 探针出口计数(受击闪白触发次数——主角路由断言面)
var st := _StandeeState.new()
var st: _VITALS # 身份与命值态件实例(名字沿用——外部路径串 "st.hp_value" 与约 40 处引用零改)
var mo: _MOTION # 资产与演出态件实例(_init 自建)
var box := Vector2(150, 176) # 站桩盒尺寸(实际高随壳层 row 布局注入;外部直访冻结)
var font: Font = null # 世界字体槽位(null = ThemeDB.fallback_font——world.world_font 注入;外部直访冻结)
var slot_row: StatusSlotRow # 状态效果唯一展示面 = 壳层伴随件(挂 BattleStageView 壳层,本体进世界;外部直访冻结)
var _intent_charge := false # 蓄力档(pid ∈ CHARGE_SKILL_IDS——意图三档 ③ 分支判据;源码锚承载成员住宿主)
# 血条存在性探针出口(世界直角血条常在;外部直访冻结)
var has_world_hp_bar := true
# 演出探针计数器(写入方=宿主演出函数、读取方=宿主 fx_probe 出口——仪器跟着驱动它的函数走)
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
var _hit_sparks := 0 # 探针出口计数(命中火花累计)
var _flash_count := 0 # 探针出口计数(受击闪白触发次数——主角路由断言面)
func _init(kind: String = "enemy") -> void:
st._owner = self
st._hero_state = HeroState.IDLE
st = _VITALS.new(self)
mo = _MOTION.new(self)
st._kind = kind
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}
mo._hero_state = _MOTION.HeroState.IDLE
func _efont() -> Font:
@@ -92,7 +55,7 @@ func enemy_id() -> String:
# 意图徽标探针出口
func intent_probe() -> Dictionary:
return {"visible": st._intent_visible, "text": st._intent_glyph}
return {"visible": mo._intent_visible, "text": mo._intent_glyph}
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
@@ -118,7 +81,7 @@ func set_data(d: Dictionary) -> void:
st._enemy_id = String(d.get("enemy_id", ""))
st._armor = String(d.get("armor_type", "light"))
st._hp_max = float(maxi(1, int(d.get("max_hp", 1))))
_set_hp(int(d.get("hp", 0)))
st.apply_hp(int(d.get("hp", 0)))
_set_order(int(d.get("order", 1)))
set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", "")))
set_effects(d.get("effects", []))
@@ -129,21 +92,11 @@ func set_data(d: Dictionary) -> void:
func set_hero(hp: int, max_hp: int) -> void:
st._hp_max = float(maxi(1, max_hp))
_set_hp(hp)
st.apply_hp(hp)
if st._dead and hp > 0:
_revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义)
# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积
func _set_hp(hp: int) -> void:
st._hp_cur = clampi(hp, 0, int(st._hp_max))
var target := float(st._hp_cur) / st._hp_max
if st._hp_tween != null and st._hp_tween.is_valid():
st._hp_tween.kill()
st._hp_tween = create_tween()
st._hp_tween.tween_property(st, "hp_value", target, UiKit.motion(0.15))
# 主角/敌方共用——状态效果唯一展示面(壳层伴随件转发;空态 = 图标行隐藏不占位)
func set_effects(effects: Array) -> void:
if slot_row == null:
@@ -182,12 +135,12 @@ func _set_order(i: int) -> void:
func set_pending(ptype: String, pid: String) -> void:
if st._dead:
_intent_charge = false
st._intent_visible = false
mo._intent_visible = false
queue_redraw()
return
if ptype == "" or ptype == "basic":
_intent_charge = false
st._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
queue_redraw()
return
var glyph := "⚔" # 攻击系(damage)
@@ -201,60 +154,15 @@ func set_pending(ptype: String, pid: String) -> void:
_intent_charge = pid in CHARGE_SKILL_IDS
if _intent_charge:
col = UiKit.GOLD_INK # 重击类蓄力档(深金墨——浅垫牌上 ≥4.5:1)
st._intent_glyph = glyph
st._intent_col = col
st._intent_visible = true
_play_intent_motion()
mo._intent_glyph = glyph
mo._intent_col = col
mo._intent_visible = true
mo.play_intent_motion(_intent_charge)
# 意图三档动作动画(§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕
# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落;
# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。
func _play_intent_motion() -> void:
var tw := create_tween()
tw.tween_property(st, "_intent_scale", 1.6, UiKit.motion(0.10))
tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.16))
tw.parallel().tween_property(st, "_glyph_rot", 4.0, UiKit.motion(0.10))
tw.tween_property(st, "_glyph_rot", 0.0, UiKit.motion(0.14))
if _intent_charge:
tw.tween_property(st, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推
tw.tween_interval(UiKit.motion(0.40)) # 定格微顿
tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.18))
else:
tw.tween_property(st, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举)
tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.13))
# 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2×
# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。
# 资产挂点转调壳(签名外形零变——box 由宿主传入件内)
func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
if tex == null:
if st._portrait_sprite != null:
st._portrait_sprite.visible = false
queue_redraw()
return
st._portrait_tex = tex
if st._portrait_sprite == null:
st._portrait_sprite = Sprite2D.new()
st._portrait_sprite.centered = false
st._portrait_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊
add_child(st._portrait_sprite)
st._portrait_sprite.texture = tex
st._portrait_sprite.flip_h = flip
st._portrait_sprite.visible = true
_place_portrait()
func _place_portrait() -> void:
# 立绘盒 = (0,44)-(box.x, box.y-36);48 源 ×2 整数档居中
if st._portrait_sprite == null or st._portrait_tex == null:
return
var box_h := box.y - 44.0 - 36.0
var w := st._portrait_tex.get_width() * 2.0
var h := st._portrait_tex.get_height() * 2.0
st._portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5)
st._portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - h) * 0.5)
mo.set_portrait_texture(tex, flip, box)
func mark_dead() -> void:
@@ -268,8 +176,8 @@ func mark_dead() -> void:
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
_spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s)
_intent_charge = false
st._intent_visible = false # 死亡残位意图退位
_set_hp(0)
mo._intent_visible = false # 死亡残位意图退位
st.apply_hp(0)
if slot_row != null:
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏
@@ -279,7 +187,7 @@ func mark_dead() -> void:
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
func _spawn_death_shards() -> void:
st._death_shards = DEATH_SHARD_N
_death_shards = DEATH_SHARD_N
var center := _portrait_rect().get_center()
for i in DEATH_SHARD_N:
_spawn_death_shard(i, center)
@@ -305,7 +213,7 @@ func _spawn_death_shard(i: int, center: Vector2) -> void:
# 演出探针出口(fx_probe——死亡碎裂/命中火花/闪白计数断言面)
func fx_probe() -> Dictionary:
return {"death_shards": st._death_shards, "hit_sparks": st._hit_sparks, "flash_count": st._flash_count}
return {"death_shards": _death_shards, "hit_sparks": _hit_sparks, "flash_count": _flash_count}
# 命中瞬间打击特效:立绘盒中心六枚短火花放射(程序化色块,确定性方向
@@ -319,7 +227,7 @@ func play_hit_impact() -> void:
## 单火花域(循环体提函数——放射弹道+渐隐+回收)
func _spawn_hit_spark(i: int, center: Vector2) -> void:
st._hit_sparks += 1
_hit_sparks += 1
var spark := ColorRect.new()
spark.color = UiKit.GOLD_LIT if i % 2 == 0 else UiKit.WARM
spark.size = Vector2(4, 3)
@@ -340,7 +248,7 @@ func is_dead() -> bool:
# 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行)
func flash_hit() -> void:
st._flash_count += 1
_flash_count += 1
if st._hp_flash_tw != null and st._hp_flash_tw.is_valid():
st._hp_flash_tw.kill()
st._hp_flash_tw = create_tween()
@@ -353,8 +261,7 @@ func _revive() -> void:
modulate = Color(1, 1, 1)
rotation_degrees = 0.0
if st._kind == "hero":
st._hero_state = HeroState.RESET # 强制回 idle 态(绕过同态早退)
set_hero_state("idle")
mo.reset_state()
if slot_row != null:
slot_row.visible = true
queue_redraw()
@@ -372,45 +279,24 @@ func _portrait_glyph() -> String:
# 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面)
func portrait_probe() -> Dictionary:
return {"has_tex": st._portrait_sprite != null and st._portrait_sprite.texture != null}
return {"has_tex": mo._portrait_sprite != null and mo._portrait_sprite.texture != null}
# ---- 主角三态切换(攻击演出→attack/受击→hit/结算回 idle;§8.4 帧数档挂点) ----
# 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理)
# 组合点注册转调壳(签名外形零变)
func register_hero_state(state: String, tex: Texture2D) -> void:
if tex != null:
st._hero_tex[state] = tex
mo.register_hero_state(state, tex)
# 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。
# 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。
# 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内)
func set_hero_state(state: String) -> void:
var target: int = _HS_VALUE.get(state, -1)
if st._kind != "hero" or target == st._hero_state:
return
if not st._hero_tex.has(state) and state != "idle":
return
st._hero_state = target
if st._portrait_sprite != null and st._hero_tex.has(state):
st._portrait_sprite.texture = st._hero_tex[state]
if st._state_revert_tw != null and st._state_revert_tw.is_valid():
st._state_revert_tw.kill()
if state != "idle":
st._state_revert_tw = create_tween()
st._state_revert_tw.tween_interval(UiKit.motion(0.35))
st._state_revert_tw.tween_callback(_settle_hero_idle)
mo.apply_hero_state(state, st._kind == "hero")
func _settle_hero_idle() -> void:
st._hero_state = HeroState.IDLE
if st._portrait_sprite != null and st._hero_tex.has("idle"):
st._portrait_sprite.texture = st._hero_tex["idle"]
# 三态探针出口(fx_probe——当前态名+注册态集合)
# 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变)
func hero_state_probe() -> Dictionary:
return {"state": _HS_NAME.get(st._hero_state, "idle"), "registered": st._hero_tex.keys()}
return mo.state_probe()
func _portrait_color() -> Color:
@@ -535,7 +421,7 @@ func _draw_hp_bar(f: Font) -> void:
## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退
func _draw_portrait(f: Font) -> void:
if st._portrait_sprite != null and st._portrait_sprite.texture != null and st._portrait_sprite.visible:
if mo._portrait_sprite != null and mo._portrait_sprite.texture != null and mo._portrait_sprite.visible:
return
var prect := _portrait_rect()
var glyph := _portrait_glyph()
@@ -543,7 +429,7 @@ func _draw_portrait(f: Font) -> void:
# 坐标系注记:draw_set_transform 原点已置于 center——draw_string 的
# pos 是**变换后的局部坐标**,须传相对 center 的偏移(-w/2 水平居中、+0.35h 基线下移);
# 传绝对 center 会成双重偏移、把字形画到盒外。
draw_set_transform(center, deg_to_rad(st._glyph_rot), Vector2.ONE)
draw_set_transform(center, deg_to_rad(mo._glyph_rot), Vector2.ONE)
var ts := f.get_string_size(glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42)
f.draw_string(get_canvas_item(), Vector2(-ts.x * 0.5, ts.y * 0.35), glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 42, _portrait_color())
@@ -552,18 +438,17 @@ func _draw_portrait(f: Font) -> void:
## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画
func _draw_intent_badge(f: Font) -> void:
if not st._intent_visible:
if not mo._intent_visible:
return
var icenter := Vector2(box.x * 0.5, 86.0)
draw_set_transform(icenter, 0.0, Vector2(st._intent_scale, st._intent_scale))
var its := f.get_string_size(st._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
draw_set_transform(icenter, 0.0, Vector2(mo._intent_scale, mo._intent_scale))
var its := f.get_string_size(mo._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
# 对比度垫牌(四色墨直压立绘纹理/暗舞台底 1.5-3.3:1 违 §7.2
# 「文字禁直压纹理底」;垫 PANEL_DATA 素牌后 atk 5.3/buf 4.8/debuff 4.7/charge 4.9 全 ≥4.5:1;
# atk/debuff 零换墨、buf 换 HERO_BLUE_DEEP、charge 换 GOLD_INK;随弹入 transform 同步缩放)
draw_rect(Rect2(-its.x * 0.5 - 4.0, -its.y * 0.75, its.x + 8.0, its.y * 1.1), UiKit.PANEL_DATA, true)
f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), st._intent_glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, st._intent_col) # 局部坐标(同上注记——双重偏移修复)
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo._intent_glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复)
## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2
+128
View File
@@ -0,0 +1,128 @@
## 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 装载;字形回退判据)
# 主角三态切换: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 呈现朝右)。盒高 96px = 48px 立绘源整数 2×
# (圣经 §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);48 源 ×2 整数档居中
if _portrait_sprite == null or _portrait_tex == null:
return
var box_h := box.y - 44.0 - 36.0
var w := _portrait_tex.get_width() * 2.0
var h := _portrait_tex.get_height() * 2.0
_portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5)
_portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - 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()}
+36
View File
@@ -0,0 +1,36 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 站桩身份与命值态件(StandeeBody 拆件——单一变化原因=战斗结算快照投影的字段形态):
## 站桩是谁、活没活、血量几何的唯一账面——身份五元组(种类/名/id/护甲/顺位)+存活位+血量账与两条血量系 tween 槽。
## 无 class_name(类名缓存面纪律)——经宿主 standee_body preload 引用(st 实例名沿用);
## hp_value 属性 setter 经 _owner 回引触发宿主重绘语义逐字。
extends RefCounted
var _owner: Node2D = null
var _kind := "enemy"
var _display_name := ""
var _enemy_id := ""
var _armor := "light"
var _order := 1
var _dead := false
var _hp_max := 1.0
var _hp_cur := 0 # 血量数字显示面(HUD框架 验收「血量数字可读」)
var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积)
set(v):
hp_value = clampf(v, 0.0, 1.0)
_owner.queue_redraw()
var _hp_tween: Tween
var _hp_flash_tw: Tween
func _init(owner: Node2D) -> void:
_owner = owner
# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积
func apply_hp(hp: int) -> void:
_hp_cur = clampi(hp, 0, int(_hp_max))
var target := float(_hp_cur) / _hp_max
if _hp_tween != null and _hp_tween.is_valid():
_hp_tween.kill()
_hp_tween = _owner.create_tween()
_hp_tween.tween_property(self, "hp_value", target, UiKit.motion(0.15))