484 lines
22 KiB
GDScript
484 lines
22 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 站桩本体:B 区主角/敌人无框立绘+底座+血条+顺位/意图徽标的世界精灵(Node2D)。
|
||
## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。
|
||
## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。
|
||
## 内部状态两件(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"]
|
||
# 护甲类型图标三键(light/ward/heavy)——终稿 = 三枚 24×24 PNG(UiKit.TEX_ARMOR_*,art-bible §5.3
|
||
# 工单:形状承载身份、色相不承载身份;挂载底色 = 名字行素底 HP_BAR_BACK)。字形占位仅作素材缺位
|
||
# 回退(与立绘缺位回退链同构——素材不入 git,缺位不炸)。
|
||
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")
|
||
|
||
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 = _VITALS.new(self)
|
||
mo = _MOTION.new(self)
|
||
st._kind = kind
|
||
mo._hero_state = _MOTION.HeroState.IDLE
|
||
|
||
|
||
func _efont() -> Font:
|
||
return font if font != null else ThemeDB.fallback_font
|
||
|
||
|
||
func display_name() -> String:
|
||
return st._display_name
|
||
|
||
|
||
# 敌单位身份出口(d12 站桩解析主键——同名敌按 id 精确匹配;主角站桩恒空串)
|
||
func enemy_id() -> String:
|
||
return st._enemy_id
|
||
|
||
|
||
# 意图徽标探针出口
|
||
func intent_probe() -> Dictionary:
|
||
return {"visible": mo._intent_visible, "text": mo._intent_glyph}
|
||
|
||
|
||
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
|
||
# 几何 = 素底牌矩形与文字基线、素底色——V6 判据 = baseline_y < plinth_top_y;
|
||
# name_font_size = 名字行字号档实读(迭代 46 卡B——提档 FS_HINT 14 的几何单点出口))
|
||
func name_line_probe() -> Dictionary:
|
||
var f := _efont()
|
||
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
|
||
var plate := _name_plate_rect(f, tex)
|
||
return {"name": st._display_name,
|
||
"armor_icon": _armor_tex_path(st._armor) if tex != null else String(ARMOR_ICONS.get(st._armor, "◇")),
|
||
"armor_tex_bound": tex != null,
|
||
"plate": plate, "baseline_y": _name_baseline(plate).y,
|
||
"plinth_top_y": _pedestal_rect().position.y,
|
||
"plate_color": UiKit.HP_BAR_BACK,
|
||
"name_font_size": UiKit.FS_HINT}
|
||
|
||
|
||
# ---- setter 面(presenter/组合根零改排) ----
|
||
|
||
# data = 战斗结算 presentation_snapshot enemies[] 投影:{order, display_name, hp, max_hp, enemy_id,
|
||
# pending_skill/pending_type/pending_name, effects[](敌方 side 增益——各立绘同值呈现)}
|
||
func set_data(d: Dictionary) -> void:
|
||
st._display_name = String(d.get("display_name", "敌人"))
|
||
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))))
|
||
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", []))
|
||
queue_redraw()
|
||
if st._dead and int(d.get("hp", 0)) > 0:
|
||
_revive()
|
||
|
||
|
||
func set_hero(hp: int, max_hp: int) -> void:
|
||
st._hp_max = float(maxi(1, max_hp))
|
||
st.apply_hp(hp)
|
||
if st._dead and hp > 0:
|
||
_revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义)
|
||
|
||
|
||
# 主角/敌方共用——状态效果唯一展示面(壳层伴随件转发;空态 = 图标行隐藏不占位)
|
||
func set_effects(effects: Array) -> void:
|
||
if slot_row == null:
|
||
return
|
||
slot_row.set_effects(effects)
|
||
slot_row.visible = not effects.is_empty()
|
||
|
||
|
||
# battle_over:状态图标行全清可见反馈(微文案挂伴随件父层 = 壳层)
|
||
func set_effects_cleared_feedback() -> void:
|
||
if slot_row == null:
|
||
return
|
||
slot_row.visible = true
|
||
slot_row.set_cleared_feedback()
|
||
var note := UiKit.label("状态随战斗结束消散",UiKit.FS_CAPTION, false, UiKit.STONE_FAINT)
|
||
note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
note.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
var parent := slot_row.get_parent()
|
||
if parent != null:
|
||
parent.add_child(note)
|
||
note.position = slot_row.position + Vector2((slot_row.size.x - 134.0) * 0.5, slot_row.size.y)
|
||
var tw := create_tween()
|
||
tw.tween_interval(1.2)
|
||
tw.tween_property(note, "modulate:a", 0.0, UiKit.motion(0.8))
|
||
tw.tween_callback(note.queue_free)
|
||
|
||
|
||
func _set_order(i: int) -> void:
|
||
# 顺位徽标 = 行主序生成序(固定顺位——敌人系统 顺位标记义务)
|
||
st._order = i
|
||
queue_redraw()
|
||
|
||
|
||
# 摇定动作渲染(三档可见性):pending_type = 战斗结算 快照投影(basic/damage/buff/debuff;
|
||
# "" = 未摇定/死亡残位)。不显示技能名——类型级图标 + 一次性动作动画。
|
||
func set_pending(ptype: String, pid: String) -> void:
|
||
if st._dead:
|
||
_intent_charge = false
|
||
mo._intent_visible = false
|
||
queue_redraw()
|
||
return
|
||
if ptype == "" or ptype == "basic":
|
||
_intent_charge = false
|
||
mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
|
||
queue_redraw()
|
||
return
|
||
var glyph := "⚔" # 攻击系(damage)
|
||
var col: Color = UiKit.RED_MINE # 攻击预告红(危险红族——预告语义归封印色独占〔现行封印黑〕)
|
||
if ptype == "buff":
|
||
glyph = "✦" # 增益系
|
||
col = UiKit.HERO_BLUE_DEEP # 深档墨(浅垫牌上 ≥4.5:1)
|
||
elif ptype == "debuff":
|
||
glyph = "☠" # 妨害系
|
||
col = UiKit.DEBUFF_PURPLE
|
||
_intent_charge = pid in CHARGE_SKILL_IDS
|
||
if _intent_charge:
|
||
col = UiKit.GOLD_INK # 重击类蓄力档(深金墨——浅垫牌上 ≥4.5:1)
|
||
mo._intent_glyph = glyph
|
||
mo._intent_col = col
|
||
mo._intent_visible = true
|
||
mo.play_intent_motion(_intent_charge)
|
||
|
||
|
||
# 资产挂点转调壳(签名外形零变——box 由宿主传入件内)
|
||
func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
|
||
mo.set_portrait_texture(tex, flip, box)
|
||
|
||
|
||
func mark_dead() -> void:
|
||
if st._dead:
|
||
return
|
||
st._dead = true
|
||
modulate = Color(0.62, 0.62, 0.62, 0.9)
|
||
var tw := create_tween()
|
||
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
|
||
tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1))
|
||
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
|
||
mo._intent_visible = false # 死亡残位意图退位
|
||
st.apply_hp(0)
|
||
if slot_row != null:
|
||
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏
|
||
|
||
|
||
# ---- 演出密度(程序化 tween,零新帧资产) ----
|
||
|
||
|
||
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
|
||
func _spawn_death_shards() -> void:
|
||
_death_shards = DEATH_SHARD_N
|
||
var center := _portrait_rect().get_center()
|
||
for i in DEATH_SHARD_N:
|
||
_spawn_death_shard(i, center)
|
||
|
||
|
||
## 单碎块域(循环体提函数——弹道两段+渐隐+回收)
|
||
func _spawn_death_shard(i: int, center: Vector2) -> void:
|
||
var shard := ColorRect.new()
|
||
shard.color = UiKit.STONE if i % 2 == 0 else UiKit.STONE.darkened(0.2)
|
||
shard.size = Vector2(7, 5) + Vector2(2, 0) * float(i % 2)
|
||
shard.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
shard.position = center + Vector2(float(i % 3) - 1.0, float(i / 3) - 0.5) * 6.0
|
||
shard.rotation = float(i) * 0.7
|
||
add_child(shard)
|
||
var dir := Vector2((float(i % 3) - 1.0) * 30.0, -36.0 - 6.0 * float(i / 3))
|
||
var peak := shard.position + dir
|
||
var stw := shard.create_tween()
|
||
stw.tween_property(shard, "position", peak, UiKit.motion(0.18)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
|
||
stw.tween_property(shard, "position", peak + Vector2(dir.x * 0.4, 52.0), UiKit.motion(0.27)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||
stw.parallel().tween_property(shard, "modulate:a", 0.0, UiKit.motion(0.18)).set_delay(UiKit.motion(0.27))
|
||
stw.tween_callback(shard.queue_free)
|
||
|
||
|
||
# 演出探针出口(fx_probe——死亡碎裂/命中火花/闪白计数断言面)
|
||
func fx_probe() -> Dictionary:
|
||
return {"death_shards": _death_shards, "hit_sparks": _hit_sparks, "flash_count": _flash_count}
|
||
|
||
|
||
# 命中瞬间打击特效:立绘盒中心六枚短火花放射(程序化色块,确定性方向
|
||
# i*60°——禁全局随机;亮金/暖白交替=物理打击语义,不占 危险红=雷 与 奖励金=收益 的语义位)。
|
||
# 弹道到达拍由 battle_stage_view 调度(on_step_row_fx 延迟 0.33s≈弹道飞行)。
|
||
func play_hit_impact() -> void:
|
||
var center := _portrait_rect().get_center()
|
||
for i in HIT_SPARK_N:
|
||
_spawn_hit_spark(i, center)
|
||
|
||
|
||
## 单火花域(循环体提函数——放射弹道+渐隐+回收)
|
||
func _spawn_hit_spark(i: int, center: Vector2) -> void:
|
||
_hit_sparks += 1
|
||
var spark := ColorRect.new()
|
||
spark.color = UiKit.GOLD_LIT if i % 2 == 0 else UiKit.WARM
|
||
spark.size = Vector2(4, 3)
|
||
spark.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
spark.position = center - spark.size * 0.5
|
||
add_child(spark)
|
||
var ang := TAU * float(i) / float(HIT_SPARK_N)
|
||
var dir := Vector2(cos(ang), sin(ang)) * 16.0
|
||
var stw := spark.create_tween()
|
||
stw.tween_property(spark, "position", spark.position + dir, UiKit.motion(0.12)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||
stw.parallel().tween_property(spark, "modulate:a", 0.0, UiKit.motion(0.12))
|
||
stw.tween_callback(spark.queue_free)
|
||
|
||
|
||
func is_dead() -> bool:
|
||
return st._dead
|
||
|
||
|
||
# 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行)
|
||
func flash_hit() -> void:
|
||
_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()
|
||
st._hp_flash_tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
|
||
st._hp_flash_tw.tween_property(self, "modulate", Color(1, 1, 1) if not st._dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
|
||
|
||
|
||
func _revive() -> void:
|
||
st._dead = false
|
||
modulate = Color(1, 1, 1)
|
||
rotation_degrees = 0.0
|
||
if st._kind == "hero":
|
||
mo.reset_state()
|
||
if slot_row != null:
|
||
slot_row.visible = true
|
||
queue_redraw()
|
||
|
||
|
||
func _portrait_glyph() -> String:
|
||
# 字形回退仅作缺位兜底(八敌真图落位;相对体量语义由 48px 档统一比例承载)。
|
||
# 缺位态:主角「人」/死亡「✕」/其余「敌」。
|
||
if st._kind == "hero":
|
||
return "人"
|
||
if st._dead:
|
||
return "✕"
|
||
return "敌"
|
||
|
||
|
||
# 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面)
|
||
func portrait_probe() -> Dictionary:
|
||
var sp: Sprite2D = mo._portrait_sprite
|
||
if sp == null or sp.texture == null:
|
||
return {"has_tex": false, "display_size": Vector2.ZERO, "display_scale": 0.0}
|
||
var src := Vector2(sp.texture.get_width(), sp.texture.get_height())
|
||
return {"has_tex": true, "display_size": src * sp.scale, "display_scale": sp.scale.x}
|
||
|
||
|
||
# ---- 主角三态切换(攻击演出→attack/受击→hit/结算回 idle;§8.4 帧数档挂点) ----
|
||
|
||
# 组合点注册转调壳(签名外形零变)
|
||
func register_hero_state(state: String, tex: Texture2D) -> void:
|
||
mo.register_hero_state(state, tex)
|
||
|
||
|
||
# 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内)
|
||
func set_hero_state(state: String) -> void:
|
||
mo.apply_hero_state(state, st._kind == "hero")
|
||
|
||
|
||
# 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变)
|
||
func hero_state_probe() -> Dictionary:
|
||
return mo.state_probe()
|
||
|
||
|
||
func _portrait_color() -> Color:
|
||
return UiKit.HERO_BLUE if st._kind == "hero" else UiKit.RED_DEEP
|
||
|
||
|
||
# 立绘盒矩形(世界局部):(0,44)-(box.x, box.y-36)——贴图与字形共用
|
||
func _portrait_rect() -> Rect2:
|
||
return Rect2(0, 44, box.x, box.y - 80.0)
|
||
|
||
|
||
# 石板地台矩形(_draw 与 content_rect 共用几何——单一来源防两处漂移)
|
||
func _pedestal_rect() -> Rect2:
|
||
return Rect2(18.0, box.y - 18.0, box.x - 36.0, 12.0)
|
||
|
||
|
||
# 护甲图标纹理路径(甲档键 → UiKit.TEX_ARMOR_*——显式 match 单点,禁动态拼键取常量;
|
||
# 未知键回退轻甲路径,由 load_tex exists 守卫接住)
|
||
func _armor_tex_path(armor: String) -> String:
|
||
match armor:
|
||
"ward":
|
||
return UiKit.TEX_ARMOR_WARD
|
||
"heavy":
|
||
return UiKit.TEX_ARMOR_HEAVY
|
||
_:
|
||
return UiKit.TEX_ARMOR_LIGHT
|
||
|
||
|
||
# 名字行素底牌矩形(_draw 与 name_line_probe 共用几何——单一来源防两处漂移):
|
||
# 牌高 26 = 护甲图标 24px(甲类 1× 禁缩放)+ 上下各 1px;牌底沿 = 地台顶(box.y-18)——
|
||
# 文字禁直接压地台(§6.1/§7.2 素底落法);牌宽 = 行宽(图标+间距+名字)+ 8,水平居中
|
||
func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2:
|
||
var icon_w := 24.0
|
||
if tex == null:
|
||
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")),
|
||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||
var total_w := icon_w + 3.0 + name_w
|
||
return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0)
|
||
|
||
|
||
# 名字行文字基线(牌内几何单点——图标 24px 垂直居中牌内〔中心=牌顶+13〕,14px 字形基线=中心+5
|
||
# (0.35h 规则按字号档推导 14×0.35≈4.9→5,T0 实测校验——迭代 46 卡B §7.4 提档))
|
||
func _name_baseline(plate: Rect2) -> Vector2:
|
||
return plate.position + Vector2(4.0, 18.0)
|
||
|
||
|
||
# 绘制内容矩形(世界局部;ui_struct_probe ⑥standee「不出框+下余量≥8px」断言读数面——
|
||
# 最低绘制元素 = 底座下缘)
|
||
func content_rect() -> Rect2:
|
||
return Rect2(0.0, 0.0, box.x, _pedestal_rect().end.y)
|
||
|
||
|
||
func _draw() -> void:
|
||
var f := _efont()
|
||
_draw_ground(f)
|
||
_draw_hp_bar(f)
|
||
_draw_portrait(f)
|
||
_draw_intent_badge(f)
|
||
if st._kind == "enemy":
|
||
_draw_armor_name_line(f)
|
||
|
||
|
||
## 地面域:椭圆落影(圣经 §6.1 地台落影压脚:方向稿椭圆 (0,0,0,70),压在地台
|
||
## 上缘周围;24 段多边形近似椭圆,中心=台面上方 6px、横半径=台宽一半)
|
||
## + 石板地台(§6.1 近景地台独立件:TEX_STAGE_PLAZA 源 48×12 九宫格横向拉伸、顶缘受光线 2px
|
||
## 不随拉伸变形、跨三关恒定;素材缺位回退程序化三段绘制〔板身 STAND_STONE/描边 PLAZA_EDGE/
|
||
## 顶缘线 BRICK——art-bible §4.1 换色合并①②改指目标色,几何同源 _pedestal_rect〕)
|
||
func _draw_ground(f: Font) -> void:
|
||
var sh_center := Vector2(box.x * 0.5, box.y - 24.0)
|
||
var sh_rx := (box.x - 36.0) * 0.5
|
||
var sh_ry := 9.0
|
||
var pts := PackedVector2Array()
|
||
for i in 24:
|
||
var ang := TAU * float(i) / 24.0
|
||
pts.append(sh_center + Vector2(cos(ang) * sh_rx, sin(ang) * sh_ry))
|
||
draw_colored_polygon(pts, Color(0, 0, 0, 0.27))
|
||
var ped := _pedestal_rect()
|
||
var plaza := UiKit.load_tex(UiKit.TEX_STAGE_PLAZA)
|
||
if plaza != null:
|
||
_draw_plinth_stretched(plaza, ped)
|
||
else:
|
||
draw_rect(ped, UiKit.STAND_STONE, true)
|
||
draw_rect(ped, UiKit.PLAZA_EDGE, false, 1.0)
|
||
draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.BRICK, true)
|
||
|
||
|
||
## 地台九宫格横向拉伸域(循环体提函数——左右 4px 端头切片 + 中段拉伸;纵向恒 12px 源档,
|
||
## 顶缘受光线 2px 落纵向不缩放区,工单 §5.2 工艺要点)
|
||
func _draw_plinth_stretched(tex: Texture2D, ped: Rect2) -> void:
|
||
draw_texture_rect_region(tex, Rect2(ped.position, Vector2(4.0, 12.0)), Rect2(0, 0, 4, 12))
|
||
draw_texture_rect_region(tex, Rect2(ped.position + Vector2(4.0, 0.0),
|
||
Vector2(ped.size.x - 8.0, 12.0)), Rect2(4, 0, 40, 12))
|
||
draw_texture_rect_region(tex, Rect2(ped.position + Vector2(ped.size.x - 4.0, 0.0),
|
||
Vector2(4.0, 12.0)), Rect2(44, 0, 4, 12))
|
||
|
||
|
||
## 血条域(直角递减):敌附顺位徽标(GOLD_BRIGHT ①②③,字号 FS_HINT——art-bible §7.4 清单外
|
||
## 11–13px 提档归档);血条几何左 8 / 右 -8;血条填充 = 危险红 HP_BAR_FILL(§4.2 语义物件——
|
||
## 不因敌我分色);条底 = HP_BAR_TRACK;血量数字「当前/最大」居中压条(白字+深描边双通道——11px 于
|
||
## 10px 条带垂直居中;数字=目标值即时刷新,条 tween 只管填充宽度;死残位 0/max 照常可读)
|
||
func _draw_hp_bar(f: Font) -> void:
|
||
var bar_x := 8.0
|
||
if st._kind == "enemy":
|
||
var order_txt := String("①②③"[clampi(st._order - 1, 0, 2)])
|
||
f.draw_string(get_canvas_item(), Vector2(bar_x, 12), order_txt,
|
||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.GOLD_BRIGHT)
|
||
bar_x += 20.0
|
||
var bar_w := box.x - 8.0 - bar_x
|
||
draw_rect(Rect2(bar_x, 2, bar_w, 10), UiKit.HP_BAR_TRACK) # 条底(值单点收 ui_kit)
|
||
var fill_w := bar_w * st.hp_value
|
||
if fill_w > 0.0:
|
||
draw_rect(Rect2(bar_x, 2, fill_w, 10), UiKit.HP_BAR_FILL) # 直角填充(危险红——§4.2 血条显式句)
|
||
var hp_txt := "%d/%d" % [st._hp_cur, int(st._hp_max)]
|
||
var hts: Vector2 = f.get_string_size(hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11)
|
||
# 对比度垫牌(白 α.95 直压蓝条 2.67:1 违 §7.2;
|
||
# 中央垫 HP_BAR_BACK 深素牌 = 14.3:1;牌宽=字宽+8〔白名单取 8〕,高=条带全高;§4.1 数字色牌原则)
|
||
draw_rect(Rect2(bar_x + (bar_w - hts.x) * 0.5 - 4.0, 2.0, hts.x + 8.0, 10.0), UiKit.HP_BAR_BACK, true)
|
||
var hbase := Vector2(bar_x + (bar_w - hts.x) * 0.5, 2.0 + 5.0 + 4.0)
|
||
f.draw_string(get_canvas_item(), hbase + Vector2(1, 1), hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, Color(0.10, 0.08, 0.06, 0.85))
|
||
f.draw_string(get_canvas_item(), hbase, hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, Color(1, 1, 1, 0.95))
|
||
|
||
|
||
## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退
|
||
func _draw_portrait(f: Font) -> void:
|
||
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()
|
||
var center := prect.get_center()
|
||
# 坐标系注记:draw_set_transform 原点已置于 center——draw_string 的
|
||
# pos 是**变换后的局部坐标**,须传相对 center 的偏移(-w/2 水平居中、+0.35h 基线下移);
|
||
# 传绝对 center 会成双重偏移、把字形画到盒外。
|
||
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())
|
||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||
|
||
|
||
## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画
|
||
func _draw_intent_badge(f: Font) -> void:
|
||
if not mo._intent_visible:
|
||
return
|
||
var icenter := Vector2(box.x * 0.5, 86.0)
|
||
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), mo._intent_glyph,
|
||
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复)
|
||
|
||
|
||
## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2
|
||
## 「文字禁直接压地台」素底落法——牌底沿 = 地台顶,几何单点 _name_plate_rect);护甲图标 =
|
||
## 24×24 PNG(甲类 1×,缺位回退字形,色随甲档亮档族);名字 = 暖白(暗底双色墨制)
|
||
func _draw_armor_name_line(f: Font) -> void:
|
||
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
|
||
var plate := _name_plate_rect(f, tex)
|
||
draw_rect(plate, UiKit.HP_BAR_BACK, true)
|
||
var baseline := _name_baseline(plate)
|
||
var icon_w := 24.0
|
||
if tex != null:
|
||
draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false)
|
||
else:
|
||
var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇"))
|
||
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||
var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制)
|
||
match st._armor:
|
||
"ward":
|
||
col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档
|
||
"heavy":
|
||
col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档
|
||
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col)
|
||
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name,
|
||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM) # v3.5 暗底双色墨制
|