## 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: # type-hint: ignore(三态哨兵 bool/null/StyleBox 异构形参) 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