feat(t3): 主角三态切换——portrait 换装 new-normal+attack/hit 入包+挂事件切换+结算回 idle

①48px 档生成器默认补 attack/hit 两档(hero-ref/edit 1x 源→character_hero_attack/hit.png;new-hit.pxo 09-27 已程序导出 hit_1x 在位,U1 手出预案不触发);机检 11/11 过
②ui_kit 增 TEX_HERO_ATTACK/TEX_HERO_HIT;TEX_HERO_PORTRAIT 注记改 normal 态(48px 档换装)
③standee_body 状态机:register_hero_state(组合点注册,缺位不注册=该态保持现纹理)/set_hero_state(短驻 0.35s 自回 idle;未注册态保持)/hero_state_probe;_revive 增态回 idle;set_hero 增死亡残位复活(hp>0——敌侧 set_data 同款)
④battle_stage_view:三态注册+_init;on_step_row_fx 主角攻击行→attack 态/受击行→hit 态(闪白弹道归 T5 拆早退);settle_hero_idle 显式收尾挂 main 两侧 battle_finished/adventure_finished
⑤fx_probe 扩第五段:三态切换+短驻自回+死亡复活断言
轻闸=冒烟 576/0+三探针 PASS(编辑器刷缓存后)
This commit is contained in:
DKChan committed 2026-09-29 04:09:13 +08:00
1 parent ac43e1333d
commit cabb6b07bf
6 files changed
+99 -6

No files matched your search

+2
View File
@@ -314,6 +314,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
_sync_settlement_visuals() # → 全关
stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体 2026-09-26 迁 B 区站桩)
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
stage.settle_hero_idle() # 主角态回 idle(迭代 6 T3 三态收尾)
# 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。
var detail: Dictionary = {}
@@ -443,6 +444,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
var result: String = String(payload.get("result", ""))
_sync_settlement_visuals() # → 全关(battle_over 反馈面)
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
stage.settle_hero_idle() # 主角态回 idle(迭代 6 T3 三态收尾)
ap_bar.set_rap(0) # RAP 清零脉冲 + 微文案(载体 2026-09-26 迁 D 区 AP 条)
stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体迁 B 区站桩)
var decision: Dictionary = _debug.on_battle_finished(payload)
+15 -2
View File
@@ -113,7 +113,12 @@ func _init(db_ref) -> void:
# 主角站桩本体(世界)+ 状态行伴随件(壳层)
hero_standee = _STANDEE_BODY.new("hero")
hero_standee.set_portrait_texture(UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT), true) # B 区立绘 48×48 朝右(2026-09-26 用户点名 hero_idle_01 换装+水平向右;素材缺位 null 回退字形「人」)
var hero_tex := UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT)
hero_standee.set_portrait_texture(hero_tex, true) # B 区立绘 48px 档·normal 态朝右(迭代 6 T3 换装=new-normal 源;素材缺位 null 回退字形「人」)
# 主角三态注册(迭代 6 T3——attack/hit 缺位不注册=该态保持现纹理,缺位回退链延伸)
hero_standee.register_hero_state("idle", hero_tex)
hero_standee.register_hero_state("attack", UiKit.load_tex(UiKit.TEX_HERO_ATTACK))
hero_standee.register_hero_state("hit", UiKit.load_tex(UiKit.TEX_HERO_HIT))
world.add_child(hero_standee)
hero_standee.slot_row = _make_slot_companion(_companion_layer)
@@ -272,14 +277,17 @@ func set_round_text(text: String) -> void:
round_label.text = text
# 结算行演出分发(占位级):伤害行 → 目标站桩闪白 / 击杀 → 灰化塌陷残位
# 结算行演出分发(占位级):伤害行 → 目标站桩闪白 / 击杀 → 灰化塌陷残位;
# 主角三态切换(迭代 6 T3)——主角攻击行→attack 态(短驻自回)/ 主角受击行→hit 态
func on_step_row_fx(row: Dictionary) -> void:
var t := String(row.get("type", ""))
if t != "damage":
return
var target := String(row.get("target", ""))
if target == "hero" or target == "":
hero_standee.set_hero_state("hit") # 受击态(短驻自回 idle;闪白/弹道归迭代 6 T5 拆早退)
return
hero_standee.set_hero_state("attack") # 我方攻击行——attack 态(短驻自回)
var card: Node2D = _card_by_name(target)
if card == null:
return
@@ -316,6 +324,11 @@ func gray_all_enemies() -> void:
c.mark_dead()
# 结算收尾:主角态显式回 idle(迭代 6 T3——「结算回 idle」事件;0.35s 短驻自回的兜底)
func settle_hero_idle() -> void:
hero_standee.set_hero_state("idle")
# 结算期 B 区第一注视点切换(能量 7/10 清算调性):结算中占位文案退位——兑现演出由
# SettlementFxView 弹道/光带本体接管(演出链批);挖掘期恢复教学占位文案
func set_settle_focus(on: bool) -> void:
+47
View File
@@ -31,6 +31,11 @@ var _kind := "enemy"
var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位)
var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据)
var _portrait_flip := false
# 主角三态切换(迭代 6 T3):state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理
# (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。
var _hero_tex := {}
var _hero_state := "idle"
var _state_revert_tw: Tween
var _order := 1
var _intent_glyph := ""
var _intent_col := Color.WHITE
@@ -105,6 +110,8 @@ func set_data(d: Dictionary) -> void:
func set_hero(hp: int, max_hp: int) -> void:
_hp_max = float(maxi(1, max_hp))
_set_hp(hp)
if _dead and hp > 0:
_revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义)
# 血条短 tween 递减(直角递减占位——密度/缓动终稿归战斗表现批);逐行刷新时重启 tween 不累积
@@ -300,6 +307,9 @@ func _revive() -> void:
_dead = false
modulate = Color(1, 1, 1)
rotation_degrees = 0.0
if _kind == "hero":
_hero_state = "reset" # 强制回 idle 态(绕过同态早退)
set_hero_state("idle")
_refresh_portrait_glyph()
if slot_row != null:
slot_row.visible = true
@@ -325,6 +335,43 @@ func portrait_probe() -> Dictionary:
return {"has_tex": _portrait_sprite != null and _portrait_sprite.texture != null}
# ---- 主角三态切换(迭代 6 T3——攻击演出→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:
_hero_tex[state] = tex
# 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。
# 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。
func set_hero_state(state: String) -> void:
if _kind != "hero" or state == _hero_state:
return
if not _hero_tex.has(state) and state != "idle":
return
_hero_state = state
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 = 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 = "idle"
if _portrait_sprite != null and _hero_tex.has("idle"):
_portrait_sprite.texture = _hero_tex["idle"]
# 三态探针出口(fx_probe——当前态名+注册态集合)
func hero_state_probe() -> Dictionary:
return {"state": _hero_state, "registered": _hero_tex.keys()}
func _portrait_color() -> Color:
return UiKit.HERO_BLUE if _kind == "hero" else UiKit.RED_DEEP
+3 -1
View File
@@ -90,7 +90,9 @@ static func motion(dur: float) -> float:
# 的字形/无演出回退路径;禁 preload(素材缺位树上直接断言炸脚本,冒烟安全红线)
const TEX_HERO_IDLE := "res://assets/art/character/character_hero_idle.png"
const TEX_HERO_WALK := "res://assets/art/character/character_hero_walk.png"
const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘 48×48(源档 candidates/hero-idle/hero_idle_01.png,2026-09-26 用户点名换装)
const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘·normal 态(48px 档,迭代 6 T3 换装=new-normal 源)
const TEX_HERO_ATTACK := "res://assets/art/character/character_hero_attack.png" # B 区站桩立绘·attack 态(迭代 6 T3 三态切换)
const TEX_HERO_HIT := "res://assets/art/character/character_hero_hit.png" # B 区站桩立绘·hit 态(迭代 6 T3 三态切换)
# 敌种立绘(B 区站桩 2026-09-26 槽接线批)——文件键 = 数据表主键 enemy_id(圣经 §8.3 命名规范),
# 键集 = art-bible §4.6.4 八敌种;素材批入库前全部缺位(load_tex→null→字形回退),入库即显示零代码换装
const TEX_ENEMY_GOBLIN := "res://assets/art/character/character_enemy_goblin.png" # 地精
+30 -1
View File
@@ -67,9 +67,38 @@ func _ready() -> void:
if n_fx < 2:
problems.append("结算光带应含带体+前导缘线 ≥2 节点,实得 %d" % n_fx)
# ---- ⑤ 主角三态切换(迭代 6 T3——attack/hit 短驻自回 + 死亡复活态回 idle) ----
var hero_sb = sb_script.new("hero")
hero_sb.box = Vector2(150, 176)
add_child(hero_sb)
var t_idle: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT)
var t_atk: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_ATTACK)
var t_hit: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_HIT)
if t_idle == null or t_atk == null or t_hit == null:
problems.append("主角三态素材缺位(portrait/attack/hit——迭代 6 T3)")
else:
hero_sb.set_portrait_texture(t_idle, true)
hero_sb.register_hero_state("idle", t_idle)
hero_sb.register_hero_state("attack", t_atk)
hero_sb.register_hero_state("hit", t_hit)
hero_sb.set_hero_state("attack")
if String(hero_sb.hero_state_probe().get("state", "")) != "attack":
problems.append("主角攻击态切换失败(set_hero_state attack)")
await get_tree().create_timer(UiKit.motion(0.5)).timeout
if String(hero_sb.hero_state_probe().get("state", "")) != "idle":
problems.append("attack 态未短驻自回 idle(0.35s)")
hero_sb.set_hero_state("hit")
if String(hero_sb.hero_state_probe().get("state", "")) != "hit":
problems.append("主角受击态切换失败(set_hero_state hit)")
hero_sb.set_hero_state("idle")
hero_sb.mark_dead()
hero_sb.set_hero(6, 10) # 新战斗开场 HP 回正——死亡残位复活(敌侧 set_data 同款语义)
if hero_sb.is_dead() or String(hero_sb.hero_state_probe().get("state", "")) != "idle":
problems.append("主角死亡后 set_hero 复活失败(is_dead 应假+态回 idle)")
# ---- 汇总 ----
if problems.is_empty():
print("[fx_probe] PASS — 死亡碎裂5块/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线 全链路通过")
print("[fx_probe] PASS — 死亡碎裂5块/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线/主角三态切换 全链路通过")
get_tree().quit(0)
else:
for p in problems:
+2 -2
View File
@@ -439,8 +439,8 @@ def dom_block_resize(im, nw, nh):
return out
def build_standee48(batch_root, out_dir, extra_hero_states=()):
"""九张 48px 站桩槽位档:源=八敌 pixel-1+主角 normal_1x(extra_hero_states 追加 attack/hit)。
def build_standee48(batch_root, out_dir, extra_hero_states=("attack", "hit")):
"""九张 48px 站桩槽位档:源=八敌 pixel-1+主角 normal_1x(extra_hero_states 追加 attack/hit,T3 起默认)。
口径(代拍板待追认): 敌八种统一比例(最大体量归 48 高,相对体量语义=字形回退 ▂▆█ 同构);
主角自适比例(48/56);NEAREST 零插值;内容 bbox 裁边后底中锚贴 (w,48) 透明画布。