188 lines
8.2 KiB
GDScript
188 lines
8.2 KiB
GDScript
## 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
|