860 lines
47 KiB
GDScript
860 lines
47 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 呈现层共享小部件工厂+全站取色/描边唯一出口——各视图禁内联 Color8/StyleBoxFlat 字面量,换皮只改本文件。
|
||
class_name UiKit
|
||
extends RefCounted
|
||
|
||
# ── 色板(语义族) ──
|
||
const INK := Color8(51, 38, 27) # 墨族——正文/数字(对格底 ≥4.5:1)
|
||
const INK_COLD := Color8(19, 46, 54) # 青墨 1-3 #132E36(数字墨三档·冷=稀疏;亮瓦自适应深墨族档兼色牌后备路径)
|
||
const INK_OCHRE := Color8(60, 37, 12) # 赭墨 4-5 #3C250C(数字墨三档·中;亮瓦自适应深墨族档兼色牌后备路径)
|
||
const INK_RUST := Color8(74, 30, 20) # 锈墨 6-8 #4A1E14(数字墨三档·暖=密集;亮瓦自适应深墨族档兼色牌后备路径)
|
||
# ── 亮墨三档(圣经 v3.5 冻结④——数字直画已开格底:色温承袭密度语义,冷亮/暖白/金亮;
|
||
# 实测对比 6.0–6.3:1(A3a);金亮低饱和避奖励金语义;色牌后备组用深墨族上牌〔12:1〕。
|
||
# 亮墨族限暗纹理板(dungeon),亮筋纹板按 TilePalette.digit_ink_mode 自适应切深墨三档)──
|
||
const INK_L_COLD := Color8(216, 236, 240) # 冷亮 1-3 #D8ECF0
|
||
const INK_L_WARM := Color8(240, 226, 200) # 暖白 4-5 #F0E2C8(关卡屏 WARM 同族)
|
||
const INK_L_GOLD := Color8(248, 227, 168) # 金亮 6-8 #F8E3A8
|
||
const INK_LABEL := Color8(46, 34, 24) # 标签深墨
|
||
const INK_TITLE := Color8(30, 26, 22) # 亮底标题墨
|
||
const INK_ROUTE := Color8(24, 20, 16) # 节点图最深墨
|
||
const INK_ON_GOLD := Color8(60, 44, 20) # 金底上的深墨
|
||
|
||
const PANEL_DATA := Color8(233, 217, 184) # 面板底·数据框(AP 条/状态槽)#E9D9B8(demo 唯一来源对齐,Δ≤6/255)
|
||
const PANEL_BORDER := Color8(120, 110, 88) # 面板细线框;兼熄灭点阵(DOT_OFF 别名)#786E58(demo 唯一来源对齐,Δ≤6/255)
|
||
|
||
const GOLD := Color8(240, 168, 40) # 奖励金
|
||
const GOLD_DEEP := Color8(176, 122, 20) # 深金(金框描边/达意金)
|
||
const GOLD_INK := Color8(116, 80, 11) # 深金墨(意图蓄力徽标墨——PANEL_DATA 垫牌上 4.9:1)
|
||
const GOLD_BRIGHT := Color8(224, 184, 96) # 金族亮档(敌顺位徽标①②③/重甲徽标对暗舞台底 6.5:1;值=收益金 e0b860 同门)
|
||
const GOLD_LIT := Color8(230, 190, 90) # 亮金(候选高亮)
|
||
const GOLD_DIM := Color8(140, 116, 60) # 暗金(已过节点)
|
||
const GOLD_DONE := Color8(104, 86, 44) # 已过节点暗金·关卡画面呈现态 #68562C(GOLD_DIM #8C743C 对双色墨均 <4.5:1,对比硬指标逼守此值;GOLD_DIM 他用不动)
|
||
|
||
const SEAL_BLACK := Color8(0, 0, 0) # 封印黑 #000000——封印宣告色(紫色与微光蓝族色相冲突故弃;
|
||
# 黑色无色相,天然避开禁入带——与妨害紫 281° 各留 12-14° 间距)
|
||
const RED_DEEP := Color8(150, 60, 48) # 深朱(敌卡框/敌像)
|
||
const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开危险格"雷"字)
|
||
const RED_FORBID := Color8(120, 60, 50) # 禁止框朱
|
||
const RED_ALERT := Color8(200, 60, 48) # 报错朱
|
||
|
||
const HERO_BLUE := Color8(46, 134, 222) # 小人蓝 #2E86DE(圣经 §4.1 冻结值)
|
||
const HERO_BLUE_DEEP := Color8(30, 90, 150) # 小人蓝同族深档(意图增益徽标墨——PANEL_DATA 垫牌上 4.8:1;命名同 CONTACT_GLOW_DEEP 规则)
|
||
const HERO_BLUE_BRIGHT := Color8(126, 186, 240) # 小人蓝同族亮档(魔法护甲徽标对暗舞台底 5.9:1;HERO_BLUE 语义岗〔board_view:35 小人边框/level_map:237〕在役不动)
|
||
const BOSS_PURPLE := Color8(143, 47, 106) # 紫赤(Boss 专属)
|
||
const DEBUFF_PURPLE := Color8(122, 66, 148) # 妨害紫(意图 ☠)
|
||
|
||
# ── 类型色五族(值 = demo --tg-* 冻结抄录〔需求卡片 §2.2 抄录表〕;消费面 = tg_band() 同源判定)──
|
||
const TG_BUFF := Color8(194, 178, 100) # 通用增益/默认带
|
||
const TG_HEAL := Color8(102, 164, 87) # 回复
|
||
const TG_MAGIC := Color8(138, 114, 196) # 魔法
|
||
const TG_PIERCE := Color8(87, 160, 160) # 穿甲
|
||
const TG_AOE := Color8(226, 86, 118) # 群伤/AOE #E25676(demo --tg-aoe)
|
||
|
||
# 类型色带分族判定(三选一与商店货架同源同判:id/kind 小写子串匹配,默认=通用增益带。
|
||
# demo「工兵爆破」卡=紫带(magic 族)——blast 归 TG_MAGIC,
|
||
# TG_AOE 只接 aoe 键(spark_aoe 消费面保留);max_hp 含 hp 归回复族)
|
||
static func tg_band(id_lower: String) -> Color:
|
||
if id_lower.contains("heal") or id_lower.contains("hp"):
|
||
return TG_HEAL
|
||
if id_lower.contains("aoe"):
|
||
return TG_AOE
|
||
if id_lower.contains("blast") or id_lower.contains("magic"):
|
||
return TG_MAGIC
|
||
if id_lower.contains("pierce"):
|
||
return TG_PIERCE
|
||
return TG_BUFF
|
||
|
||
# ── 未开格类型微光五组(HUD框架「未开格类型微光」节实装面;「不并入」卡顶带
|
||
# TG_* 两套色带两套归组并存,禁互为引用)──
|
||
# 色相 = art-bible §4.2 冻结五组(色相既裁不可重裁,只允许按明度档做亮度缩放)。
|
||
# 🔴 冻结面稽核:逐组 HSV 对照冻结值——BUFF/HEAL/PIERCE/
|
||
# MAGIC 四组 ΔH ≤0.13°(8bit 量化噪声)、ΔS ≤0.002,属纯 V 缩放;群伤组 #A61D37(冻结
|
||
# #FF2D55 的 V=0.65 缩放,H=348.57°/S=0.824 逐位不动,保持绯色相避开危险红)。
|
||
# 五组均满足「只动明度、不动色相」。
|
||
# 明度档 = provisional 值——补齐 §4.2 从未定义的「各组一档明度互辨」数值
|
||
# (色弱灰度互辨通道)。
|
||
# 🔴 生成口径:阶梯必须在**渲染合成空间**(sRGB/伽马空间)里排——
|
||
# 引擎 canvas 混合按帧缓冲编码(伽马)做线性插值,色弱玩家屏幕上看到的是合成值而非色值本身。
|
||
# 按 CIE L*(线性)排档会把群伤/魔法排反(绯红线性亮于紫罗兰,但伽马空间绯红 G+B 通道低,
|
||
# 合成后反而更暗——实测最弱对 CR 仅 1.05)。五档按合成空间单调阶梯排列:以「带内均值 ≈
|
||
# 0.49×色值 + 0.51×瓦片」合成模型反解,相邻组模型 CR = 1.245/1.234/1.239/1.270(判据线
|
||
# 1.20;模型域与实测的差 = shader 距离场权重非扁平混合,故互辨判据的终审在渲染取证探针实测,
|
||
# L1 棋盘探针逐对读数见 reports/iter30-实现报告读数表)。
|
||
# 明度档只许在 provisional 域内自调(L3 预授权③一轮),翻动色相 = 触 art-bible §4.2 冻结面
|
||
# = L3 停机呈报。
|
||
const TG_GLOW_BUFF := Color8(247, 223, 67) # 通用增益·芥末黄(attack/defense/weaken/action/gp)
|
||
const TG_GLOW_HEAL := Color8(104, 208, 66) # 治疗·黄绿(heal)
|
||
const TG_GLOW_PIERCE := Color8(48, 166, 166) # 穿刺·青(pierce)
|
||
const TG_GLOW_MAGIC := Color8(123, 91, 214) # 魔法·紫罗兰(magic)
|
||
const TG_GLOW_AOE := Color8(166, 29, 55) # 群伤·绯红(blast)——冻结 #FF2D55 的 V=0.65 档(色相零移动)
|
||
const TYPE_GLOW_ALPHA := 170.0 / 255.0 # 泛光强度(art-bible §4.2 v2 呈现参数 α170)
|
||
const TYPE_GLOW_BAND_FRAC := 0.125 # 带宽/格边(cell÷8——同上 v2 冻结参数)
|
||
const TYPE_GLOW_SOFT := 0.55 # 帯内软边起点(占带宽比;0=边缘突变,越大衰减越长)
|
||
const TYPE_GLOW_CORNER := 2.0 # 圆角掩罩半径(px——微光不溢出格面圆角)
|
||
const GLOW_NONE := 0
|
||
const GLOW_BUFF := 1
|
||
const GLOW_HEAL := 2
|
||
const GLOW_PIERCE := 3
|
||
const GLOW_AOE := 4
|
||
const GLOW_MAGIC := 5
|
||
|
||
# 九 cell_type → 五组封闭判定(成员集合逐字照抄 art-bible §4.2;清单外值 = 数据错,返回 GLOW_NONE
|
||
# 由调用方显式报错登记,禁静默空光——图标键错误处理同款纪律。
|
||
# 禁子串匹配、禁复用 tg_band():那是卡顶色带的 demo 冻结值且 blast 归 TG_MAGIC,与本案五组归属相反)
|
||
static func glow_group_for_cell_type(cell_type: String) -> int:
|
||
match cell_type:
|
||
"attack", "defense", "weaken", "action", "gp":
|
||
return GLOW_BUFF
|
||
"heal":
|
||
return GLOW_HEAL
|
||
"pierce":
|
||
return GLOW_PIERCE
|
||
"blast":
|
||
return GLOW_AOE
|
||
"magic":
|
||
return GLOW_MAGIC
|
||
_:
|
||
return GLOW_NONE
|
||
|
||
|
||
static func glow_color_for_group(g: int) -> Color:
|
||
match g:
|
||
GLOW_BUFF:
|
||
return TG_GLOW_BUFF
|
||
GLOW_HEAL:
|
||
return TG_GLOW_HEAL
|
||
GLOW_PIERCE:
|
||
return TG_GLOW_PIERCE
|
||
GLOW_AOE:
|
||
return TG_GLOW_AOE
|
||
GLOW_MAGIC:
|
||
return TG_GLOW_MAGIC
|
||
_:
|
||
return Color(0, 0, 0, 0)
|
||
|
||
|
||
# ── 呈现散件裸色搬运(本节立后 ui_kit 之外
|
||
# 呈现层裸 Color8 = 0,守卫 = ui_contrast_probe 静态段)──
|
||
const INK_HINT := Color8(110, 69, 52) # 提示/警示暖棕墨(设置警示文/地图底栏组标)
|
||
const INK_ON_DEEP := Color8(246, 238, 218) # 深底标题亮墨(主菜单标题/营地柜台等级章字)
|
||
const TITLE_PEDESTAL := Color8(90, 74, 56) # 标题屏底座石台面(椭圆 17 底座;描边=BG_ROOT)
|
||
const MAP_BAR_PLATE := Color8(240, 228, 200) # 关卡地图底栏亮板
|
||
const MAP_VDIV := Color8(201, 184, 142) # 关卡地图底栏竖分隔线(1px)
|
||
const HINT_STRIP_EDGE := Color8(74, 62, 48) # 棋盘底提示条描边(半透明罩层板边)
|
||
const RAP_DOT_CORE_WARM := Color8(248, 226, 168) # AP 点阵亮心(烛光黄族)
|
||
const RAP_DOT_LIT_BLUE := Color8(105, 161, 232) # RAP 点阵亮面(小人蓝族)
|
||
const RAP_DOT_CORE_BLUE := Color8(156, 194, 240) # RAP 点阵亮心(小人蓝族)
|
||
const E_FACADE_FOOT_COL := Color8(52, 42, 32) # 墙脚阴线(方向稿同色)
|
||
const FOG_MEMORY_PLATE := Color8(58, 58, 58) # 记忆板 #3A3A3A(§6.5)
|
||
const FOG_MEMORY_INK := Color8(196, 196, 196) # 记忆墨 #C4C4C4(对记忆板 ≈6.3:1)
|
||
const FOG_OUTLINE := Color8(30, 24, 18) # 雾面轮廓墨
|
||
const ICON_TINT_ON_GOLD := Color(24.0 / 240.0, 20.0 / 226.0, 16.0 / 200.0) # 当前金底图标墨 = WARM 分量乘法调制(双色墨制)
|
||
|
||
const DUSK := Color8(90, 108, 140) # 暮蓝(全作唯一暮蓝档——失败终局)
|
||
const DIM_NIGHT := Color8(24, 18, 12) # 终局压暗底 #18120C(demo 结算仪式暗纱——胜利侧 dim 基色)
|
||
|
||
const WARM := Color8(240, 226, 200) # 暖白(暗底正文)
|
||
const WARM_SOFT := Color8(232, 213, 168) # 暖白次级(说明文)#E8D5A8(配色总纲「米色去灰」)
|
||
const WARM_FAINT := Color8(205, 184, 140) # 暖白弱化(帮助文)#CDB88C(配色总纲「米色去灰」)
|
||
|
||
const STONE := Color8(120, 110, 98) # 灰石(通道/箭头/失效)
|
||
const STONE_FAINT := Color8(140, 124, 100) # 灰石弱化(占位提示文)
|
||
const STONE_DEEP := Color8(85, 77, 65) # 灰石深化(「已用」槽位墨——PANEL_DATA 槽牌 5.6:1 / 石皮 5.0:1;STONE_FAINT 对亮底双向 <4.5:1,故另立此档)
|
||
const DARK_UPCOMING := Color8(64, 58, 52) # 未达暗字(底/文同值)
|
||
|
||
const BRICK := Color8(201, 175, 131) # 棋盘·未开掩尘地砖 #C9AF83
|
||
const FLOOR := Color8(179, 154, 120) # 棋盘·已开裸土 #B39A78
|
||
const CONTACT_GLOW := Color8(214, 236, 255) # 可挖域接触带微光(蓝族浅一档·近白蓝——art-bible §3.2 v2.8)
|
||
const CONTACT_GLOW_DEEP := Color8(150, 190, 230) # 接触带呼吸暗端(lerp 目标)
|
||
const DIGIT_PLATE := Color8(245, 234, 211) # 数字色牌(素色暖纸白直角牌——art-bible §4.1;数字一律垫牌,
|
||
# 可读不依赖格底——原则二)
|
||
const DIGIT_PLATE_EDGE := Color8(210, 198, 172) # 色牌牌缘线
|
||
const SLOT_BACKDROP := Color8(62, 54, 44) # B 区状态图标槽实底(深墨棕——白图标对比承载)#3E362C
|
||
const SLOT_BACKDROP_SOFT := Color8(128, 106, 78) # 图标槽实底·柔和档(BCard 图标槽明度差降档:对米色卡面 3.7:1〔深档 8.7:1 过硬〕,白图标 5.1:1 ≥3:1 图形对比硬约束;B 区状态槽仍用深档 SLOT_BACKDROP)
|
||
|
||
const BG_ROOT := Color8(58, 46, 34) # 屏幕根底 #3A2E22(配色总纲「提暖提亮禁近黑」)
|
||
const BG_OVERLAY := Color8(54, 43, 32) # 模态遮罩底 #362B20
|
||
const BG_BRIGHT := Color8(69, 55, 35) # 暖亮档底 #453723(标题/营地/商店等暖亮屏根底)
|
||
const CANDLE := Color8(246, 198, 103) # 烛光黄 #F6C667(主操作色/候选节点/钥石徽章底;
|
||
# 与 §4.1 奖励金 #F0A828 分轨——圣经 §7.2 琥珀双岗注记)
|
||
const DIM := Color(0.08, 0.06, 0.05) # 遮罩基色(用时乘 alpha)
|
||
const TOAST_BG := Color(0.94, 0.91, 0.84, 0.95) # toast 面底
|
||
const TIP_BG := Color(0.16, 0.12, 0.09, 0.96) # tooltip 深底
|
||
|
||
# 呈现杂色档(各视图裸 Color8 字面量单点化)
|
||
const STAGE_BG := Color8(62, 52, 42) # B 区舞台暗底(方向稿 render_battle_screen——battle_stage_view 面板底)
|
||
const BEAM_LIGHT := Color8(255, 236, 200) # B 区顶光柱暖光(用时乘 alpha 渐隐——battle_stage_view 光柱层)
|
||
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
|
||
const STAND_STONE_EDGE := Color8(90, 76, 58) # 站桩石板地台·描边
|
||
const STAND_STONE_RIM := Color8(196, 172, 132) # 站桩石板地台·顶缘受光线
|
||
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep)
|
||
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
|
||
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
|
||
const HP_BAR_BACK := Color8(40, 34, 32) # 迷你血条深底 #282220(兼血数垫牌;站桩名字行素底同源——§6.1/§7.2 文字禁压地台)
|
||
const HP_BAR_FILL := Color8(168, 50, 31) # 血条填充·危险红 #A8321F(§4.2 语义物件显式落法——不因敌我分色、不因关卡换族;蓝族 #689CE8 已登记违例退役)
|
||
const HP_BAR_TRACK := Color(0.10, 0.10, 0.11) # 血条条底(视图原裸值收口单点——非色板新增,现渲染值原样入册)
|
||
|
||
# BBCode 十六进制(RichTextLabel 用;与上同值)
|
||
const HEX_GOLD := "f0a828"
|
||
const HEX_INK := "33261b"
|
||
const HEX_ON_GOLD := "3a2a08"
|
||
# 结算行/叙事行语义档(battle_log_view 行格式化与 narrator 横幅行的裸 hex 单点化;
|
||
# 名随呈现语义)
|
||
const HEX_LOG_ROUND := "9a8a72" # 轮次前缀/零值行暖灰石
|
||
const HEX_LOG_GAIN := "e0b860" # 收益金(GP 袋/商店行/败判定/节点横幅)
|
||
const HEX_LOG_LOSS := "c47c7c" # 损失红(GP 流失/负效果)
|
||
const HEX_LOG_HEAL := "7cc47c" # 治疗绿(效果正值/治疗行/恢复点行)
|
||
const HEX_LOG_DMG_NUM := "d9402a" # 伤害数字朱(放大加色——数字即信息主角)
|
||
const HEX_LOG_DMG_KILL := "e07050" # 击杀行正文橙红
|
||
const HEX_LOG_DMG_BODY := "e0a050" # 伤害行正文橙
|
||
const HEX_LOG_VOIDED := "b08060" # 未生效行暗橙(⚠ 划线)
|
||
const HEX_LOG_CAST := "b09a72" # 施放行暖石(✦ 具名技能)
|
||
const HEX_LOG_DECAY := "8a9ab0" # 状态衰减蓝灰
|
||
const HEX_LOG_WIN := "80d080" # 胜利判定绿(模拟报告关卡完成率同档)
|
||
const HEX_ROUTE_NOTE := "d0c090" # 奖励点预告暗金(narrator 行)
|
||
const HEX_SEED_NOTE := "c8a860" # 种子/开场行暗金(narrator 行)
|
||
|
||
# 语义别名(同值异名——语义站点用别名,值仍单点)
|
||
const DOT_OFF := PANEL_BORDER
|
||
|
||
|
||
# ── 减动效(battle.md §Transitions 内建承诺:开启时时长减半;结算逐行兑现为信息载体降速不跳过)──
|
||
# 唯一入口 = 设置面开关(调整板本体在调试浮窗)
|
||
static var motion_scale := 1.0 # 1.0 常态 / 0.5 减动效
|
||
static var victory_fx_style := "crest" # 胜利演出风格(P-C5 两套并存——"crest"碎砖拼纹章[默认冻结件]/"dust"尘金粒子;切换=调试浮窗,会话级)
|
||
|
||
# 全部呈现层 tween 时长经本函数包装——新增动效一律 UiKit.motion(秒),禁再写裸时长字面量
|
||
static func motion(dur: float) -> float:
|
||
return dur * motion_scale
|
||
|
||
|
||
# ── 单位/特效纹理路径单点 ──
|
||
# 素材 PNG 不入 git——装载必须经 load_tex(exists 守卫),缺位返回 null 走各消费点
|
||
# 的字形/无演出回退路径;禁 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 区站桩立绘·normal 态(48px 档)
|
||
const TEX_HERO_ATTACK := "res://assets/art/character/character_hero_attack.png" # B 区站桩立绘·attack 态(三态切换)
|
||
const TEX_HERO_HIT := "res://assets/art/character/character_hero_hit.png" # B 区站桩立绘·hit 态(三态切换)
|
||
# 敌种立绘(B 区站桩)——文件键 = 数据表主键 enemy_id(圣经 §8.3 命名规范),
|
||
# 键集 = art-bible §4.6.4 八敌种;素材批入库前全部缺位(load_tex→null→字形回退),入库即显示零代码换装
|
||
const TEX_ENEMY_GOBLIN := "res://assets/art/character/character_enemy_goblin.png" # 地精
|
||
const TEX_ENEMY_BUGBEAR := "res://assets/art/character/character_enemy_bugbear.png" # 熊地精
|
||
const TEX_ENEMY_OGRE_CHIEF := "res://assets/art/character/character_enemy_ogre_chief.png" # 食人魔酋长
|
||
const TEX_ENEMY_GIANT_SPIDER := "res://assets/art/character/character_enemy_giant_spider.png" # 巨蜘蛛
|
||
const TEX_ENEMY_DRIDER := "res://assets/art/character/character_enemy_drider.png" # 蛛化卓尔精灵
|
||
const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.png" # 灰矮人
|
||
const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_horror.png" # 钩爪恐魔
|
||
const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王
|
||
# 近景地台独立件(§6.1——源 48×12 九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定不吃 wall_ambient tint)
|
||
const TEX_STAGE_PLAZA := "res://assets/art/scene/scene_stage_plaza.png"
|
||
# 护甲类型图标三键(art-bible §5.3 工单:对象键 = 甲档变体;源 24×24 显示 24px = 1× 甲类整数倍;
|
||
# 缺位 load_tex→null→字形回退,冒烟安全)
|
||
const TEX_ARMOR_LIGHT := "res://assets/ui/icons/ui_armor_light.png"
|
||
const TEX_ARMOR_WARD := "res://assets/ui/icons/ui_armor_ward.png"
|
||
const TEX_ARMOR_HEAVY := "res://assets/ui/icons/ui_armor_heavy.png"
|
||
|
||
# 框材组纹理槽(♻ 复用件量化改色入包,产物 = tools/quantize_ui_skin.py,
|
||
# 全 CC0;缺位 = load_tex null → 程序化回退原路径,冒烟安全)
|
||
const TEX_PANEL_STONE := "res://assets/art/ui/panel_stone.png" # 石板亮面板九宫格 48×48
|
||
const TEX_BUTTON_STONE := "res://assets/art/ui/button_stone.png" # 按钮九宫格·常态(可按的砖)
|
||
const TEX_BUTTON_STONE_PRESSED := "res://assets/art/ui/button_stone_pressed.png" # 按钮九宫格·按压(下沉一档)
|
||
const TEX_BACKDROP_STONE := "res://assets/art/ui/backdrop_stone.png" # 石纹底噪 256×256 无缝
|
||
const TEX_BORDER_STONE := "res://assets/art/ui/border_stone.png" # 仪式石框(空心角括框)
|
||
const TEX_ICON_KEYSTONE := "res://assets/art/ui/icon_keystone.png" # 钥石图标(奖励金)
|
||
const TEX_ICON_FORGE := "res://assets/art/ui/icon_forge.png" # 柜台一·属性锻造
|
||
const TEX_ICON_WORKSHOP := "res://assets/art/ui/icon_workshop.png" # 柜台二·道具工坊
|
||
const TEX_ICON_ATLAS := "res://assets/art/ui/icon_atlas.png" # 柜台三·奖励格图鉴
|
||
# 节点图标五键(20px 烘焙 1:1 消费,WARM 单色;
|
||
# 对象键 = node_type 文件侧延伸 §8.3;缺位 = load_tex null → 字形回退)
|
||
const TEX_NODE_BATTLE := "res://assets/art/ui/node_battle.png" # 节点图标·战斗房
|
||
const TEX_NODE_REWARD := "res://assets/art/ui/node_reward.png" # 节点图标·奖励房
|
||
const TEX_NODE_REST := "res://assets/art/ui/node_rest.png" # 节点图标·恢复房
|
||
const TEX_NODE_BOSS := "res://assets/art/ui/node_boss.png" # 节点图标·Boss 房
|
||
const TEX_NODE_SHOP := "res://assets/art/ui/node_shop.png" # 节点图标·商店房
|
||
# 手绘功能图标十键(20×20 像素 1:1/整数倍消费〔§8.2 甲类纪律〕;
|
||
# 源=demo 原型 src 盘上件搬运;缺位 = load_tex null → 字形回退不炸)
|
||
const TEX_ICON_SWORD := "res://assets/art/ui/icon_sword.png" # 图标·攻击系(锻造行/攻击强化卡)
|
||
const TEX_ICON_SHIELD := "res://assets/art/ui/icon_shield.png" # 图标·护甲系
|
||
const TEX_ICON_HEART := "res://assets/art/ui/icon_heart.png" # 图标·生命系
|
||
const TEX_ICON_SHOVEL := "res://assets/art/ui/icon_shovel.png" # 图标·铲(挖掘系商品/锻造)
|
||
const TEX_ICON_MEDKIT := "res://assets/art/ui/icon_medkit.png" # 图标·医疗包
|
||
const TEX_ICON_POUCH := "res://assets/art/ui/icon_pouch.png" # 图标·弹药袋/行动点
|
||
const TEX_ICON_BLAST := "res://assets/art/ui/icon_blast.png" # 图标·工兵爆破(秘宝)
|
||
const TEX_ICON_COINBAG := "res://assets/art/ui/icon_coinbag.png" # 图标·GP 袋
|
||
const TEX_ICON_KEYSHARD := "res://assets/art/ui/icon_keyshard.png" # 图标·钥石残片
|
||
const TEX_ICON_REFRESH := "res://assets/art/ui/icon_refresh.png" # 图标·刷新(货架/候选)
|
||
|
||
|
||
# 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退)
|
||
static func load_tex(path: String) -> Texture2D:
|
||
if ResourceLoader.exists(path):
|
||
return load(path)
|
||
return null
|
||
|
||
|
||
# ── 屏底氛围层(demo .tilebg opacity .14/.vignette/.beams 对齐;
|
||
# demo 石纹只作弱质感,聚焦靠暗角)──
|
||
const BACKDROP_TILE_ALPHA := 0.14 # demo .tilebg{opacity:.14}
|
||
const BG_MAP_TOP := Color8(74, 59, 42) # 地图屏渐变顶 #4A3B2A(demo #v-map 180deg 0%;低点 40% = BG_ROOT #3A2E22)
|
||
|
||
|
||
# 石纹弱质感全屏片(0.14;缺素材返回 null = 调用侧回退平色)
|
||
static func backdrop_tile() -> TextureRect:
|
||
var bd_tex := load_tex(TEX_BACKDROP_STONE)
|
||
if bd_tex == null:
|
||
return null
|
||
var bd := TextureRect.new()
|
||
bd.texture = bd_tex
|
||
bd.stretch_mode = TextureRect.STRETCH_TILE
|
||
bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||
bd.modulate = Color(1, 1, 1, BACKDROP_TILE_ALPHA)
|
||
bd.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
bd.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
return bd
|
||
|
||
|
||
# 径向暗角(demo .vignette——ellipse 90%×80% at 50% 42%,透明 58% → 黑 0.30 满角)
|
||
static func vignette_rect() -> TextureRect:
|
||
var g := Gradient.new()
|
||
g.offsets = PackedFloat32Array([0.0, 0.58, 1.0])
|
||
g.colors = PackedColorArray([Color(0, 0, 0, 0), Color(0, 0, 0, 0), Color(0, 0, 0, 0.30)])
|
||
var t := GradientTexture2D.new()
|
||
t.gradient = g
|
||
t.fill = GradientTexture2D.FILL_RADIAL
|
||
t.fill_from = Vector2(0.5, 0.42)
|
||
t.fill_to = Vector2(1.4, 0.42)
|
||
t.width = 256
|
||
t.height = 256
|
||
var tr := TextureRect.new()
|
||
tr.texture = t
|
||
tr.stretch_mode = TextureRect.STRETCH_SCALE
|
||
tr.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR # 平滑渐变件白名单(全局 Nearest 下显式豁免)
|
||
tr.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
return tr
|
||
|
||
|
||
# 斜光带(demo #v-start .beams——repeating-linear-gradient 102deg,暖白 5% 窄带 240-340/560px;
|
||
# 程序纹理循环带,确定性生成零随机。光柱采用连续 alpha:
|
||
# 带内 alpha 0→0.05→0 横向过渡+纵向衰减上亮下暗;
|
||
# 纹理 filter=显式 LINEAR 白名单(与暗角/地图渐变同血统)
|
||
static func _beams_rect() -> TextureRect:
|
||
var img := Image.create(560, 560, false, Image.FORMAT_RGBA8)
|
||
_paint_beams(img)
|
||
var tr := TextureRect.new()
|
||
tr.texture = ImageTexture.create_from_image(img)
|
||
tr.stretch_mode = TextureRect.STRETCH_TILE
|
||
tr.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR # 平滑渐变件白名单(连续 alpha 与暗角/地图渐变同血统)
|
||
tr.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
return tr
|
||
|
||
|
||
# 光带几何常量(12° 斜向光带——带轴倾角/带域/峰值强度;_paint_beams 单消费)
|
||
const BEAMS_TILT_CA := 0.9135 # cos(0.2094)——12°(102deg 带轴的垂直倾角)
|
||
const BEAMS_TILT_SA := 0.4067 # sin(0.2094)
|
||
const BEAMS_BAND_LO := 240.0
|
||
const BEAMS_BAND_HI := 340.0
|
||
const BEAMS_PEAK := 0.05
|
||
|
||
|
||
## 光带像素域(循环体提函数——12° 斜向光带双三角衰减:带内峰值×纵向衰减逐像素着色)
|
||
static func _paint_beams(img: Image) -> void:
|
||
var ca := BEAMS_TILT_CA
|
||
var sa := BEAMS_TILT_SA
|
||
var beam_col := Color(1.0, 236.0 / 255.0, 200.0 / 255.0)
|
||
var band_mid := (BEAMS_BAND_LO + BEAMS_BAND_HI) * 0.5
|
||
var band_half := (BEAMS_BAND_HI - BEAMS_BAND_LO) * 0.5
|
||
for y in 560:
|
||
var v_fall := 1.0 - float(y) / 560.0 # 纵向衰减:上亮下暗(连续 alpha)
|
||
for x in 560:
|
||
var d := fposmod(x * ca + y * sa, 560.0)
|
||
if d >= BEAMS_BAND_LO and d < BEAMS_BAND_HI:
|
||
var a: float = BEAMS_PEAK * (1.0 - absf(d - band_mid) / band_half) * v_fall
|
||
img.set_pixel(x, y, Color(beam_col.r, beam_col.g, beam_col.b, a))
|
||
|
||
|
||
# 竖向渐变底(demo #v-map linear-gradient(180deg, top 0%, low 40%)——stop 后恒 low)
|
||
static func _gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect:
|
||
var g := Gradient.new()
|
||
g.offsets = PackedFloat32Array([0.0, stop])
|
||
g.colors = PackedColorArray([top, low])
|
||
var t := GradientTexture2D.new()
|
||
t.gradient = g
|
||
t.fill_from = Vector2(0.5, 0.0)
|
||
t.fill_to = Vector2(0.5, 1.0)
|
||
t.width = 64
|
||
t.height = 256
|
||
var tr := TextureRect.new()
|
||
tr.texture = t
|
||
tr.stretch_mode = TextureRect.STRETCH_SCALE
|
||
tr.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR # 平滑渐变件白名单(全局 Nearest 下显式豁免)
|
||
tr.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
return tr
|
||
|
||
|
||
# CJK 随包字体(Fusion Pixel OFL——CJK 覆盖+授权干净;命名遵 art-bible §8.3
|
||
# 全小写 snake_case 域前缀)。守卫装载同 load_tex 纪律:缺位返回 null(引擎回退字体接手,不炸)。
|
||
const FONT_UI := "res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf"
|
||
|
||
static func load_font(path: String) -> Font:
|
||
if ResourceLoader.exists(path):
|
||
var res: Resource = load(path)
|
||
return res as Font
|
||
return null
|
||
|
||
|
||
# ── 字号档位表(六档;全站 UiKit.label/字号 override 只许取
|
||
# 本表。归档映射:10/11/12→CAPTION、13/14→HINT、15/16→BODY、17/18/20→SUB、
|
||
# 21/22/24/26→TITLE、32/40→H1(就近归档);棋盘数字公式 maxi(18, ceil(格边×0.5)) 与站桩血数
|
||
# 11px 条带内读数豁免不入表(art-bible §7.4)──
|
||
const FS_CAPTION := 12 # 装饰 caption/分区小字/微计数(§7.4 豁免下限)
|
||
const FS_HINT := 14 # 提示/tooltip 下限档
|
||
const FS_BODY := 16 # 正文/明细下限档
|
||
const FS_SUB := 18 # 副标题/小组标题(=棋盘数字下限同值)
|
||
const FS_TITLE := 24 # 屏标题/横幅/价格
|
||
const FS_H1 := 34 # 主标题/演算暴击
|
||
|
||
# ── 秘宝解锁条件计数键 → 呈现标签(秘宝匣柜台条件进度文案单点;键域=
|
||
# TreasureItemTable.UNLOCK_COUNTER_KEYS 封闭枚举,键出域走「条件」兜底不炸)──
|
||
const TREASURE_REQ_LABELS := {"adventure_cleared": "冒险成功", "boss_kill_count": "Boss击杀", "vision_used": "真视使用"}
|
||
# 秘宝档位 → 呈现名(契约 2 三档;legendary 传说空集合法不在柜)
|
||
const TREASURE_TIER_LABELS := {"common": "普通", "excellent": "优秀", "rare": "稀有", "legendary": "传说"}
|
||
|
||
# ── StyleBox 工厂 ──
|
||
# margin = (左, 上, 右, 下);border_w = 0 即无边框(根底/遮罩用)
|
||
# 圆角率单点(容器/按钮全站唯一圆角值;数据框/数字牌/AP 点阵/准线/血条
|
||
# =0 直角不变;底座椭圆 17/结算金印 200/状态脉冲环 100 圆形 = §7.1 明示例外保留)
|
||
const RADIUS_OBJECT := 5
|
||
# 九宫格切片(panel 石皮 16 与 demo .plate9 slice16 对齐;button 维持 8、仪式框维持 12)
|
||
const PANEL_SLICE := 16.0
|
||
static func panel_flat(bg: Color, border: Color, corner: int = 0, border_w: int = 1, margin: Vector4 = Vector4.ZERO) -> StyleBoxFlat:
|
||
var sb := StyleBoxFlat.new()
|
||
sb.bg_color = bg
|
||
sb.set_corner_radius_all(corner)
|
||
sb.set_border_width_all(border_w)
|
||
sb.border_color = border
|
||
sb.content_margin_left = margin.x
|
||
sb.content_margin_top = margin.y
|
||
sb.content_margin_right = margin.z
|
||
sb.content_margin_bottom = margin.w
|
||
return sb
|
||
|
||
|
||
# color 缺省哨兵 = alpha<0(真实用色不取负 alpha)——传色即一次到位;省略时缺省
|
||
# bold=INK_LABEL / 非 bold=主题缺省
|
||
static func label(text: String, size: int, bold: bool, color: Color = Color(-1, -1, -1, -1)) -> Label:
|
||
var l := Label.new()
|
||
l.text = text
|
||
l.add_theme_font_size_override("font_size", size)
|
||
if color.a >= 0.0:
|
||
l.add_theme_color_override("font_color", color)
|
||
elif bold:
|
||
l.add_theme_color_override("font_color", INK_LABEL)
|
||
return l
|
||
|
||
|
||
# ── 模态壳工厂(各 overlay 手搓 dim+Center+VBox/Panel 骨架单点化)──
|
||
# dim = 全屏半透明遮罩(全屏锚定纪律同既有各 overlay);center 全屏锚定居中;with_panel = 内容包
|
||
# PanelContainer(panel_sb 缺省 = 主题默认板——settlement_review 同款);box = 内容列
|
||
#(min_size/separation 参数化,min_size ZERO = 不设)。返回 Dictionary{dim, center, panel, box}
|
||
#——探针直读的私有件由调用方按名摘引,结构同名同层级不变。
|
||
static func _modal_dim(parent: Control, dim_color: Color) -> ColorRect:
|
||
var dim := ColorRect.new()
|
||
dim.color = dim_color
|
||
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
parent.add_child(dim)
|
||
return dim
|
||
|
||
|
||
const PANEL_DEFAULT := true # modal_shell 面板哨兵(非 StyleBox 实例——null=无面板/本哨兵=默认样式盒面板/StyleBox=自定义)
|
||
|
||
|
||
# 面板哨兵解析(六参收拢五参——三态:null=无面板〔原 with_panel=false〕/PANEL_DEFAULT=默认样式盒面板
|
||
# 〔原 with_panel=true+panel_sb=null〕/StyleBox 实例=自定义样式盒面板〔原双参形态〕;
|
||
# 判别用 is StyleBox——StyleBox 与布尔哨兵直接 != 属异类操作数违例)
|
||
static func _resolve_shell_panel(panel_sb) -> PanelContainer:
|
||
if panel_sb == null:
|
||
return null
|
||
var panel := PanelContainer.new()
|
||
if panel_sb is StyleBox:
|
||
panel.add_theme_stylebox_override("panel", panel_sb)
|
||
return panel
|
||
|
||
|
||
static func modal_shell(parent: Control, dim_color: Color, min_size := Vector2.ZERO, separation := 12,
|
||
panel_sb = null) -> Dictionary:
|
||
var dim := _modal_dim(parent, dim_color)
|
||
var center := CenterContainer.new()
|
||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
dim.add_child(center)
|
||
var panel := _resolve_shell_panel(panel_sb)
|
||
var host: Control = center
|
||
if panel != null:
|
||
center.add_child(panel)
|
||
host = panel
|
||
var box := VBoxContainer.new()
|
||
if min_size != Vector2.ZERO:
|
||
box.custom_minimum_size = min_size
|
||
box.add_theme_constant_override("separation", separation)
|
||
host.add_child(box)
|
||
return {"dim": dim, "center": center, "panel": panel, "box": box}
|
||
|
||
|
||
# ── 全屏页壳(demo #v-shop/#v-bonus 全屏页形态落地:
|
||
# 商店全屏化 + 竖卡 232×352。暖底+石纹弱质感+暗角+56px 通栏顶条+内容列;
|
||
# 商店=暖亮底、三选一=根底,两屏同构差异仅底色与顶条工具件)──
|
||
const PAGE_TOP_H := 56.0
|
||
|
||
static func page_shell(host: Control, bright := true) -> Dictionary:
|
||
var page := Control.new()
|
||
page.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
host.add_child(page)
|
||
var base := ColorRect.new()
|
||
base.color = BG_BRIGHT if bright else BG_ROOT
|
||
base.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
base.mouse_filter = Control.MOUSE_FILTER_STOP # 页底挡点击——模态语义由组合根输入锁兜底
|
||
page.add_child(base)
|
||
var tile := backdrop_tile()
|
||
if tile != null:
|
||
page.add_child(tile)
|
||
page.add_child(vignette_rect())
|
||
var top := PanelContainer.new()
|
||
top.set_anchors_preset(Control.PRESET_TOP_WIDE)
|
||
top.custom_minimum_size = Vector2(0, PAGE_TOP_H)
|
||
top.add_theme_stylebox_override("panel",
|
||
panel_flat(Color8(38, 29, 20), Color8(85, 72, 58), 0, 1, Vector4(26, 6, 26, 6)))
|
||
page.add_child(top)
|
||
var top_row := HBoxContainer.new()
|
||
top_row.add_theme_constant_override("separation", 12)
|
||
top.add_child(top_row)
|
||
var box := VBoxContainer.new()
|
||
box.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
box.offset_top = PAGE_TOP_H + 20
|
||
box.offset_left = 26
|
||
box.offset_right = -26
|
||
box.offset_bottom = -22
|
||
box.add_theme_constant_override("separation", 10)
|
||
page.add_child(box)
|
||
return {"page": page, "base": base, "top_row": top_row, "box": box}
|
||
|
||
|
||
# 属性百分比合成(board_resolvers.debug_hero_override 与调试浮窗生效值预览共用单点——两处同式实现收口)
|
||
static func scaled(base: int, pct: int) -> int:
|
||
return maxi(1, int(round(float(base) * (100.0 + float(pct)) / 100.0)))
|
||
|
||
|
||
# 宽度纪律(全站按钮最小宽度基准 216+SIZE_FILL:VBox 列内拉满列宽等宽,
|
||
# HBox 行内不抢余宽(EXPAND_FILL 会把行撑爆——营地底条 1616px 实证);角落小件/紧凑位传 0 豁免)
|
||
# 钮高纪律(统一全站钮高,消除标题屏主钮与常规钮不齐):
|
||
# 全站常规钮统一最小高 40(主操作钮/危险钮经 button() 同高对齐,压扁形态从此不存在);
|
||
# 豁免域与宽度纪律同款——min_w=0 紧凑位(HUD 顶条小钮/角标)不抬行高
|
||
const BUTTON_MIN_H := 40.0
|
||
|
||
static func button(text: String, min_w := 216.0) -> Button:
|
||
var b := Button.new()
|
||
b.text = text
|
||
if min_w > 0.0:
|
||
b.custom_minimum_size = Vector2(min_w, BUTTON_MIN_H)
|
||
b.size_flags_horizontal = Control.SIZE_FILL
|
||
_apply_button_skin(b)
|
||
return b
|
||
|
||
|
||
# ── 框材纹理工厂(§7.1 容器穿戏服、数据素面;缺位回退 flat/主题默认) ──
|
||
# 九宫格 StyleBoxTexture(texture_margin 8px——quantize 脚本同值);tint 供消费点微调色调
|
||
static func _tex_sb(tex: Texture2D, margin: float, tint := Color.WHITE) -> StyleBoxTexture:
|
||
var sb := StyleBoxTexture.new()
|
||
sb.texture = tex
|
||
sb.texture_margin_left = margin
|
||
sb.texture_margin_top = margin
|
||
sb.texture_margin_right = margin
|
||
sb.texture_margin_bottom = margin
|
||
sb.modulate_color = tint
|
||
return sb
|
||
|
||
|
||
# 石板亮面板(B 区舞台/C 区主面板/图例——容器穿戏服;数据框仍 panel_flat 素面 §7.1)
|
||
# content margin 低于切片的分量自动抬到切片——切片 16 后石框纹理不吃内容
|
||
# (9 消费面 7 处 margin<16;工厂单点 clamp,调用点零改)
|
||
static func panel_skinned(margin: Vector4 = Vector4.ZERO, tint := Color.WHITE) -> StyleBox:
|
||
var m := Vector4(maxf(margin.x, PANEL_SLICE), maxf(margin.y, PANEL_SLICE),
|
||
maxf(margin.z, PANEL_SLICE), maxf(margin.w, PANEL_SLICE))
|
||
var tex := load_tex(TEX_PANEL_STONE)
|
||
if tex == null:
|
||
return panel_flat(PANEL_DATA, PANEL_BORDER, RADIUS_OBJECT, 1, m)
|
||
var sb := _tex_sb(tex, PANEL_SLICE, tint)
|
||
sb.content_margin_left = m.x
|
||
sb.content_margin_top = m.y
|
||
sb.content_margin_right = m.z
|
||
sb.content_margin_bottom = m.w
|
||
return sb
|
||
|
||
|
||
# 仪式石框(空心角括框——营地容器框;缺位回退暗面板)
|
||
static func icon_rect(tex_path: String, px: float) -> TextureRect:
|
||
var t := load_tex(tex_path)
|
||
if t == null:
|
||
return null
|
||
var tr := TextureRect.new()
|
||
tr.texture = t
|
||
tr.custom_minimum_size = Vector2(px, px)
|
||
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||
tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||
return tr
|
||
|
||
|
||
# 钥石徽(badge 工厂件——消费方 = 营地覆盖层 + 状态面板钥石余额行。形制 = 烛光黄底+木色描边
|
||
# +钥石图标(icon_rect 守卫装载,缺位=纯文本回退)+深墨字;零新素材零新色值——值全取现行 token)
|
||
class KeystoneBadge:
|
||
extends PanelContainer
|
||
var value_label: Label
|
||
|
||
func _init(text: String, icon_path: String, icon_px: float) -> void:
|
||
add_theme_stylebox_override("panel",
|
||
UiKit.panel_flat(UiKit.CANDLE, UiKit.CAMP_HEADER_BAND, UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8)))
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 6)
|
||
add_child(row)
|
||
if icon_path != "":
|
||
var ic := UiKit.icon_rect(icon_path, icon_px)
|
||
if ic != null:
|
||
row.add_child(ic)
|
||
value_label = UiKit.label(text, UiKit.FS_SUB, true, UiKit.INK_ON_GOLD)
|
||
row.add_child(value_label)
|
||
|
||
func set_text(t: String) -> void:
|
||
value_label.text = t
|
||
|
||
|
||
static func keystone_badge(text: String, icon_path := TEX_ICON_KEYSTONE, icon_px := 32.0) -> KeystoneBadge:
|
||
return KeystoneBadge.new(text, icon_path, icon_px)
|
||
|
||
|
||
# 按钮皮·五态矩阵(逐实例 override 不动主题层)
|
||
# 悬停:常态图+modulate 提亮(0.06 档与 primary_button 现行 lightened(0.06) 同纪律,零新素材);
|
||
# 禁用:本族底+α0.45 幽灵化(与 font_disabled_color 同纪律);
|
||
# 焦点:INK 2px 内缩 1px 环(与 font_focus_color=INK 同源;引擎 focus 样式盒叠加绘制,不遮钮底)
|
||
const BUTTON_HOVER_LIFT := Color(1.06, 1.06, 1.06, 1.0) # 悬停提亮档(modulate 乘数>1=中间调提亮,高光像素封顶不漂)
|
||
const BUTTON_GHOST_ALPHA := 0.45 # 禁用幽灵化 α(与文字禁用同档)
|
||
const BUTTON_FOCUS_RING_W := 2.0 # 焦点环宽 px
|
||
const BUTTON_FOCUS_RING_INSET := 1.0 # 焦点环内缩 px(负 expand margin 实现)
|
||
|
||
static func _focus_ring_sb() -> StyleBoxFlat:
|
||
var sb := StyleBoxFlat.new()
|
||
sb.bg_color = Color(0, 0, 0, 0)
|
||
sb.draw_center = false
|
||
sb.set_border_width_all(BUTTON_FOCUS_RING_W)
|
||
sb.border_color = INK
|
||
sb.set_expand_margin_all(-BUTTON_FOCUS_RING_INSET)
|
||
return sb
|
||
|
||
|
||
# 按钮皮(五态统一管线——常态/悬停/按压/禁用/焦点+墨字;缺位素材 = 引擎默认主题)
|
||
static func _apply_button_skin(b: Button) -> void:
|
||
var tex_n := load_tex(TEX_BUTTON_STONE)
|
||
if tex_n == null:
|
||
return
|
||
var tex_p := load_tex(TEX_BUTTON_STONE_PRESSED)
|
||
b.add_theme_stylebox_override("normal", _tex_sb(tex_n, 8.0))
|
||
b.add_theme_stylebox_override("hover", _tex_sb(tex_n, 8.0, BUTTON_HOVER_LIFT))
|
||
if tex_p != null:
|
||
b.add_theme_stylebox_override("pressed", _tex_sb(tex_p, 8.0))
|
||
b.add_theme_stylebox_override("disabled",
|
||
_tex_sb(tex_n, 8.0, Color(1, 1, 1, BUTTON_GHOST_ALPHA)))
|
||
b.add_theme_stylebox_override("focus", _focus_ring_sb())
|
||
b.add_theme_color_override("font_color", INK)
|
||
b.add_theme_color_override("font_hover_color", INK)
|
||
b.add_theme_color_override("font_pressed_color", INK)
|
||
b.add_theme_color_override("font_focus_color", INK)
|
||
b.add_theme_color_override("font_disabled_color", Color(INK, BUTTON_GHOST_ALPHA))
|
||
|
||
|
||
# ── 公共件(bcard 竖卡工厂/拨杆开关/样式滑杆/动词按钮/tooltip 主题;
|
||
# 消费方 = 标题/营地/设置/商店/三选一屏。单点纪律同 §7.1:新件只加不改旧件)──
|
||
|
||
# 动词按钮(图标+动词+悬停说明三件套——标题屏按钮组/通用;icon 缺位=纯文字不炸)
|
||
static func primary_button(text: String, tip := "") -> Button:
|
||
var b := button(text)
|
||
var sb := panel_flat(CANDLE, Color8(110, 69, 52),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2))
|
||
b.add_theme_stylebox_override("normal", sb)
|
||
b.add_theme_stylebox_override("hover", panel_flat(CANDLE.lightened(0.06), Color8(110, 69, 52),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
|
||
b.add_theme_stylebox_override("pressed", panel_flat(Color8(214, 172, 88), Color8(110, 69, 52),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
|
||
b.add_theme_stylebox_override("disabled",
|
||
panel_flat(Color(CANDLE, BUTTON_GHOST_ALPHA), Color(Color8(110, 69, 52), BUTTON_GHOST_ALPHA), RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
|
||
b.add_theme_stylebox_override("focus", _focus_ring_sb())
|
||
b.add_theme_color_override("font_color", Color8(58, 42, 8))
|
||
b.add_theme_color_override("font_hover_color", Color8(58, 42, 8))
|
||
b.add_theme_color_override("font_pressed_color", Color8(58, 42, 8))
|
||
b.add_theme_color_override("font_focus_color", Color8(58, 42, 8))
|
||
b.add_theme_color_override("font_disabled_color", Color(Color8(58, 42, 8), BUTTON_GHOST_ALPHA))
|
||
if tip != "":
|
||
b.tooltip_text = tip
|
||
return b
|
||
|
||
|
||
# 危险钮(深红平涂——放弃/删除类;破坏性确认位置纪律=不得与常规确认同位)
|
||
static func danger_button(text: String, tip := "", min_w := 216.0) -> Button:
|
||
var b := button(text, min_w)
|
||
var sb := panel_flat(Color8(180, 78, 54), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2))
|
||
b.add_theme_stylebox_override("normal", sb)
|
||
b.add_theme_stylebox_override("hover", panel_flat(Color8(196, 90, 62), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
|
||
b.add_theme_stylebox_override("pressed",
|
||
panel_flat(Color8(180, 78, 54).darkened(0.18), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
|
||
b.add_theme_stylebox_override("disabled",
|
||
panel_flat(Color(Color8(180, 78, 54), BUTTON_GHOST_ALPHA), Color(Color8(110, 42, 28), BUTTON_GHOST_ALPHA), RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)))
|
||
b.add_theme_stylebox_override("focus", _focus_ring_sb())
|
||
b.add_theme_color_override("font_color", Color8(255, 244, 224))
|
||
b.add_theme_color_override("font_hover_color", Color8(255, 244, 224))
|
||
b.add_theme_color_override("font_pressed_color", Color8(255, 244, 224))
|
||
b.add_theme_color_override("font_focus_color", Color8(255, 244, 224))
|
||
b.add_theme_color_override("font_disabled_color", Color(Color8(255, 244, 224), BUTTON_GHOST_ALPHA))
|
||
if tip != "":
|
||
b.tooltip_text = tip
|
||
return b
|
||
|
||
|
||
# 卡顶类型色带·Bayer 有序抖动(
|
||
# demo CSS 平滑渐变/blur 违反像素纪律,改 4×4 Bayer 有序抖动台阶:α 由深至 0,2px 元胞逐行降覆盖;
|
||
# 纯静态绘制零动效(减动效无涉);Bayer 阶数=实现细节白名单项,取 4×4)
|
||
class DitherBand:
|
||
extends Control
|
||
var band: Color
|
||
|
||
func _init(c: Color) -> void:
|
||
band = c
|
||
custom_minimum_size = Vector2(0, 8) # 8px = 4×4 Bayer 全周期
|
||
|
||
func _draw() -> void:
|
||
var bayer := [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]]
|
||
var cell := 2.0
|
||
var rows := int(ceil(custom_minimum_size.y / cell))
|
||
var cols := int(ceil(size.x / cell))
|
||
for ry in rows:
|
||
var cover := 1.0 - float(ry) / float(rows) # 顶行全覆盖 → 底行趋零
|
||
for rx in cols:
|
||
if float(bayer[ry % 4][rx % 4]) / 16.0 < cover:
|
||
draw_rect(Rect2(float(rx) * cell, float(ry) * cell, cell, cell), band, true)
|
||
|
||
|
||
# bcard 竖卡工厂(商店/三选一同构卡——两屏共用;差异仅=价签 vs ✓选择章)
|
||
# set_selected 经 mark/样式换装
|
||
class BCard:
|
||
extends PanelContainer
|
||
var mark: Label
|
||
var icon_tr: TextureRect
|
||
var name_l: Label
|
||
var desc_l: Label
|
||
var price_l: Label
|
||
var foot_l: Label
|
||
var body: VBoxContainer # 内容列(消费方经 add_footer 插件到底注前——购买钮挂位)
|
||
var _sb_normal: StyleBox
|
||
var _sb_sel: StyleBox
|
||
|
||
func _init(title: String, desc: String, band: Color, icon_path: String, opts := {}) -> void:
|
||
var price: String = opts.get("price", "")
|
||
var footnote: String = opts.get("footnote", "")
|
||
var tex := UiKit.load_tex(UiKit.TEX_PANEL_STONE)
|
||
_sb_normal = UiKit.panel_skinned(Vector4(12, 10, 12, 10)) if tex != null else UiKit.panel_flat(PANEL_DATA, PANEL_BORDER,RADIUS_OBJECT, 1, Vector4(12, 10, 12, 10)) # 回退=数据板单点
|
||
_sb_sel = UiKit.panel_flat(PANEL_DATA, Color8(138, 90, 52), RADIUS_OBJECT, 2, Vector4(12, 10, 12, 10)) # 选中底=数据板单点
|
||
add_theme_stylebox_override("panel", _sb_normal)
|
||
custom_minimum_size = Vector2(232, 352) # demo .bcard 232×352
|
||
size_flags_vertical = Control.SIZE_SHRINK_CENTER # 行容器不纵向拉伸(卡恒 352 高居中——HBox fill 会拉到 ~536)
|
||
var box := VBoxContainer.new()
|
||
box.add_theme_constant_override("separation", 8)
|
||
add_child(box)
|
||
body = box
|
||
var band_r := DitherBand.new(band) # 卡顶类型色带=Bayer 有序抖动带(随各 TG_* 逐卡着色)
|
||
box.add_child(band_r)
|
||
mark = UiKit.label("✓",FS_BODY, true, Color8(58, 42, 8))
|
||
mark.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||
mark.visible = false
|
||
box.add_child(mark)
|
||
box.add_child(_build_icon_slot(icon_path))
|
||
name_l = UiKit.label(title,FS_SUB, true, INK_TITLE)
|
||
name_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
box.add_child(name_l)
|
||
desc_l = UiKit.label(desc,FS_HINT, false, Color8(74, 58, 40))
|
||
desc_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
desc_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
box.add_child(desc_l)
|
||
var spacer := Control.new()
|
||
spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
box.add_child(spacer)
|
||
price_l = UiKit.label(price,FS_TITLE, true, GOLD_INK) # GOLD 对货架板 1.46:1 越带,换深金墨 5.21:1(三选一价签恒空串隐藏零波及)
|
||
price_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
price_l.visible = price != ""
|
||
box.add_child(price_l)
|
||
foot_l = UiKit.label(footnote,FS_CAPTION, false, Color8(110, 69, 52))
|
||
foot_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
foot_l.visible = footnote != ""
|
||
box.add_child(foot_l)
|
||
|
||
# 图标槽(demo .ic 76×76 槽底+图标 60=20px 源 3 整数倍;明度差降档——柔和档;白图标 ≥3:1 硬约束在役)
|
||
func _build_icon_slot(icon_path: String) -> PanelContainer:
|
||
var slot := PanelContainer.new()
|
||
slot.add_theme_stylebox_override("panel", UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1))
|
||
slot.custom_minimum_size = Vector2(76, 76)
|
||
slot.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||
icon_tr = TextureRect.new()
|
||
var itex := UiKit.load_tex(icon_path)
|
||
if itex != null:
|
||
icon_tr.texture = itex
|
||
icon_tr.custom_minimum_size = Vector2(60, 60)
|
||
icon_tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
icon_tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||
icon_tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||
slot.add_child(icon_tr)
|
||
return slot
|
||
|
||
func set_selected(v: bool) -> void:
|
||
add_theme_stylebox_override("panel", _sb_sel if v else _sb_normal)
|
||
mark.visible = v
|
||
|
||
func add_footer(c: Control) -> void:
|
||
body.add_child(c)
|
||
body.move_child(c, foot_l.get_index())
|
||
|
||
|
||
## 竖卡工厂(BCard._init 同形收拢 6→5 参——opts 键 {price, footnote} 缺省逐字同值)
|
||
static func bcard(title: String, desc: String, band: Color, icon_path: String, opts := {}) -> BCard:
|
||
return BCard.new(title, desc, band, icon_path, opts)
|
||
|
||
|
||
# 拨杆开关(设置屏——46×22 滑柄形;状态字随行由消费方摆;会话级/持久由消费方接 SettingsStore)
|
||
class SwitchToggle:
|
||
extends Button
|
||
var _knob: ColorRect
|
||
|
||
func _init(initial: bool) -> void:
|
||
toggle_mode = true
|
||
button_pressed = initial
|
||
custom_minimum_size = Vector2(46, 22)
|
||
var track := UiKit.panel_flat(Color8(184, 165, 130), Color8(120, 110, 88),RADIUS_OBJECT, 2)
|
||
add_theme_stylebox_override("normal", track)
|
||
add_theme_stylebox_override("hover", track)
|
||
add_theme_stylebox_override("pressed", track)
|
||
_knob = ColorRect.new()
|
||
_knob.color = Color8(58, 42, 8)
|
||
_knob.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
add_child(_knob)
|
||
toggled.connect(func(_p: bool) -> void: _place())
|
||
_place()
|
||
|
||
func _place() -> void:
|
||
_knob.position = Vector2(25, 3) if button_pressed else Vector2(3, 3)
|
||
_knob.size = Vector2(18, 16)
|
||
|
||
|
||
static func _switch_toggle(initial: bool) -> SwitchToggle:
|
||
return SwitchToggle.new(initial)
|
||
|
||
|
||
# 样式滑杆/tooltip 主题词条单点迁
|
||
# game/assets/theme/main_theme.tres 全局主题:滑轨 slider/grabber_area/grabber、
|
||
# TooltipPanel/TooltipLabel;禁新旧并存。消费方几何内联:
|
||
# settings_overlay 音量滑轨 / debug_overlay _slider_row
|