feat(ui): T2 框材组 8 件换皮接线批(样张 v2 冻结色板量化)——UiKit 单点工厂+五视图接线

♻ 复用件审阅+量化改色(tools/quantize_ui_skin.py,全 CC0,闸①色板 token 渐变带重映射/闸②token 天然合规/闸③图标四形互异/闸④授权干净):panel_stone/button_stone 两态/backdrop_stone/border_stone/营地图标四件(钥石金+柜台暖白)九件入 game/assets/art/ui/(PNG 不入 git,缺位 load_tex null 回退)。
接线=UiKit 单点:panel_skinned(B 区舞台 tint PANEL_STAGE/C 区主面板/图例面板+墨字随行改色 §7.2)+_apply_button_skin(全站按钮两态皮+墨字,UiKit.button 消费点零改)+frame_skinned(营地仪式石框容器)+icon_rect(营地钥石行/三柜台头图标,缺位纯文本回退)+开始界面石纹底噪底层(ui_assembler,z 序 dim 之上内容之下)。数据框/暗卡/结算回看保持素面或暗底(§7.1 数据素面纪律)。
✦ B 区背景横幅不生图列缺料清单(归 T5 交付)。8 件=面板九宫格/按钮常态/按钮按压/石纹底噪/仪式石框/钥石图标/柜台图标组/B 区横幅(缺料)。轻闸=冒烟 557/0+四探针 PASS(带素材模式)+编辑器导入 9 件零报错。
This commit is contained in:
DKChan committed 2026-09-28 04:10:49 +08:00
1 parent f9b0765412
commit 0ad16747fc
7 files changed
+244 -12

No files matched your search

+10
View File
@@ -95,6 +95,16 @@ static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Di
# 单场遭遇用户入口已删(2026-09-13 用户裁决——单场原语仅探针经 debug_session.start_single 使用))
static func build_start_overlay(host: Control, db = null) -> Dictionary:
var overlay := overlay_bg(host, UiKit.BG_OVERLAY)
# 石纹底噪底层(样张 v2 冻结批 T2——z 序=dim 之上/内容之下;缺位不挂即回退平色)
var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE)
if bd_tex != null:
var bd := TextureRect.new()
bd.texture = bd_tex
bd.stretch_mode = TextureRect.STRETCH_TILE
bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
bd.set_anchors_preset(Control.PRESET_FULL_RECT)
bd.mouse_filter = Control.MOUSE_FILTER_IGNORE
overlay.add_child(bd)
var box := overlay_box(overlay)
var title := UiKit.label("Dungeon Sweeper", 34, true)
title.add_theme_color_override("font_color", UiKit.WARM)
+3 -2
View File
@@ -41,9 +41,10 @@ var world: Node2D # StageWorld(preload 脚本实例——Node2D 世
func _init(db_ref) -> void:
custom_minimum_size = Vector2(0, 252)
# 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载)
# 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载;
# 石板皮 = 样张 v2 冻结批 T2——缺位回退 PANEL_DATA flat,渲染观感不变)
var bg := Panel.new()
var sb := UiKit.panel_flat(UiKit.PANEL_STAGE, UiKit.PANEL_BORDER, 0, 1, Vector4(6, 4, 6, 4))
var sb := UiKit.panel_skinned(Vector4(6, 4, 6, 4), Color(0.956, 0.952, 0.945)) # tint=PANEL_STAGE 对 PANEL_DATA 比值
bg.add_theme_stylebox_override("panel", sb)
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
+24 -8
View File
@@ -34,30 +34,39 @@ func _init() -> void:
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center)
var frame := PanelContainer.new()
frame.add_theme_stylebox_override("panel", UiKit.frame_skinned(Vector4(26, 22, 26, 22))) # 仪式石框(T2;缺位回退暗面板)
center.add_child(frame)
var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 6)
center.add_child(box)
frame.add_child(box)
var title := UiKit.label("营地 · 永久强化", 24, true)
title.add_theme_color_override("font_color", UiKit.WARM)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(title)
var bal_row := HBoxContainer.new()
bal_row.add_theme_constant_override("separation", 6)
bal_row.alignment = BoxContainer.ALIGNMENT_CENTER
var keystone_icon := UiKit.icon_rect(UiKit.TEX_ICON_KEYSTONE, 18) # 缺位 null → 纯文本回退
if keystone_icon != null:
bal_row.add_child(keystone_icon)
_balance_label = UiKit.label("钥石 0", 18, true)
_balance_label.add_theme_color_override("font_color", UiKit.GOLD)
_balance_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(_balance_label)
bal_row.add_child(_balance_label)
box.add_child(bal_row)
box.add_child(HSeparator.new())
box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)"))
box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)", UiKit.TEX_ICON_FORGE))
_forge_holder = VBoxContainer.new()
_forge_holder.add_theme_constant_override("separation", 4)
box.add_child(_forge_holder)
box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)"))
box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)", UiKit.TEX_ICON_WORKSHOP))
_workshop_holder = VBoxContainer.new()
_workshop_holder.add_theme_constant_override("separation", 4)
box.add_child(_workshop_holder)
box.add_child(_counter_header("奖励格图鉴"))
box.add_child(_counter_header("奖励格图鉴", UiKit.TEX_ICON_ATLAS))
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false)
lock_l.add_theme_color_override("font_color", UiKit.WARM_FAINT)
box.add_child(lock_l)
@@ -80,10 +89,17 @@ func _init() -> void:
visible = false
func _counter_header(text: String) -> Label:
func _counter_header(text: String, icon_path := "") -> Control:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 6)
if icon_path != "":
var ic := UiKit.icon_rect(icon_path, 16) # 缺位 null → 纯文本回退
if ic != null:
row.add_child(ic)
var l := UiKit.label(text, 15, true)
l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
return l
row.add_child(l)
return row
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡)----
@@ -22,7 +22,8 @@ var _bonus_list: VBoxContainer
func _init() -> void:
custom_minimum_size = Vector2(240, 0)
clip_contents = true
var sb := UiKit.panel_flat(UiKit.PANEL_HERO, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 6, 8, 6))
# 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色≈PANEL_HERO;数据框仍素面 §7.1;缺位回退 flat)
var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6))
add_theme_stylebox_override("panel", sb)
var col := VBoxContainer.new()
col.add_theme_constant_override("separation", 14) # 布局充实(2026-09-26 反馈③:区段间距放大)
+5 -1
View File
@@ -33,12 +33,15 @@ func _init() -> void:
center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center)
_panel = PanelContainer.new()
_panel.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(12, 10, 12, 10))) # 石板皮(T2;缺位回退 flat)
center.add_child(_panel)
var box := VBoxContainer.new()
box.custom_minimum_size = Vector2(520, 0)
box.add_theme_constant_override("separation", 6)
_panel.add_child(box)
box.add_child(UiKit.label("图例 · 格态", 20, true))
var title := UiKit.label("图例 · 格态", 20, true)
title.add_theme_color_override("font_color", UiKit.INK_TITLE) # 亮底墨字(§7.2 数据素面)
box.add_child(title)
box.add_child(HSeparator.new())
for e in ENTRIES:
var row := HBoxContainer.new()
@@ -48,6 +51,7 @@ func _init() -> void:
chip.color = Color(e[0])
row.add_child(chip)
var lab := UiKit.label("%s —— %s" % [e[1], e[2]], 13, false)
lab.add_theme_color_override("font_color", UiKit.INK_LABEL) # 亮底墨字(§7.2 数据素面)
lab.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lab.custom_minimum_size = Vector2(440, 0)
row.add_child(lab)
+84
View File
@@ -98,6 +98,18 @@ const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.p
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" # 灰矮人石君王
# 框材组纹理槽(样张 v2 冻结批 T2 2026-09-28——♻ 复用件量化改色入包,产物 = 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" # 柜台三·奖励格图鉴
# 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退)
static func load_tex(path: String) -> Texture2D:
@@ -133,4 +145,76 @@ static func label(text: String, size: int, bold: bool) -> Label:
static func button(text: String) -> Button:
var b := Button.new()
b.text = text
_apply_button_skin(b)
return b
# ── 框材纹理工厂(样张 v2 冻结批 T2——§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)
static func panel_skinned(margin: Vector4 = Vector4.ZERO, tint := Color.WHITE) -> StyleBox:
var tex := load_tex(TEX_PANEL_STONE)
if tex == null:
return panel_flat(PANEL_DATA, PANEL_BORDER, 4, 1, margin)
var sb := _tex_sb(tex, 8.0, tint)
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
# 仪式石框(空心角括框——营地容器框;缺位回退暗面板)
static func frame_skinned(margin: Vector4 = Vector4.ZERO) -> StyleBox:
var tex := load_tex(TEX_BORDER_STONE)
if tex == null:
return panel_flat(BG_OVERLAY, PANEL_BORDER, 6, 1, margin)
var sb := _tex_sb(tex, 12.0)
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
# 图标槽(营地钥石/柜台件——缺位返回 null,消费点不挂 TextureRect 即回退纯文本)
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
# 按钮皮(常态/按压两态+墨字——缺位 = 引擎默认主题,素材批前逐位一致)
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))
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_p, 8.0))
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, 0.45))
+116
View File
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""UI 框材组量化改色脚本(M-1 迭代 2 T2,2026-09-28)。
♻ 复用件 → 复用闸①色板量化:全部产物按明度重映射到 art-bible §4.1/§7.2 token 渐变带,
不保留原包杂色;闸②禁入带=token 色天然合规;闸③灰度可辨=图标四形互异;闸④授权=全 CC0。
源包 = artwork/ui-reuse-2026-09-23/(素材不入 git——先查本仓库,缺位回退主检出 D:/code/g-plan2):
oga-kenney-pixel-ui-750(CC0,OGA) 9-Slice/Ancient 面板/按钮九宫格 48×48
kenney-fantasy-ui-borders(CC0) panel-border 石框 48×48
5-石纹底噪/stone_6.png(CC0,OGA Handpainted stone) 无缝底噪 1200×1200
kenney-board-game-icons(CC0) 64px 图标四件
产物 → game/assets/art/ui/(终稿素材目录,不入 git;消费 = UiKit.load_tex 守卫,缺位程序化回退)。
用法:python tools/quantize_ui_skin.py
"""
from PIL import Image
import os
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MAIN_ROOT = "D:/code/g-plan2"
SRC = ("artwork", "ui-reuse-2026-09-23")
OUT = ("game", "assets", "art", "ui")
def src_path(*parts):
for base in (ROOT, MAIN_ROOT):
p = os.path.join(base, *SRC, *parts)
if os.path.exists(p):
return p
raise FileNotFoundError("/".join(SRC + parts))
def out_path(name):
p = os.path.join(ROOT, *OUT, name)
os.makedirs(os.path.dirname(p), exist_ok=True)
return p
# §4.1/§7.2 token 渐变带(量化目标值——样张 v2 冻结色板)
RAMP_PANEL = [(120, 106, 88), (226, 210, 182), (243, 234, 214)] # PANEL_BORDER→PANEL_DATA→PLATE 微亮
RAMP_BUTTON = [(179, 154, 120), (201, 175, 131), (245, 234, 211)] # FLOOR→BRICK(掩尘"可按的砖")→PLATE
RAMP_PRESSED = [(120, 106, 88), (179, 154, 120)] # PANEL_BORDER→FLOOR
RAMP_BACKDROP = [(30, 25, 20), (120, 106, 88)] # BG_ROOT→PANEL_BORDER(低对比自然后退 §6.1)
RAMP_BORDER = [(120, 106, 88), (201, 175, 131)] # PANEL_BORDER→BRICK
ICON_GOLD = (240, 168, 40) # 奖励金(钥石)
ICON_WARM = (240, 226, 200) # 暖白(柜台件,暗卡上可读)
def ramp_color(ramp, t):
if t <= 0:
return ramp[0]
if t >= 1:
return ramp[-1]
seg = t * (len(ramp) - 1)
i = min(int(seg), len(ramp) - 2)
k = seg - i
a, b = ramp[i], ramp[i + 1]
return tuple(int(a[j] + (b[j] - a[j]) * k) for j in range(3))
def remap_luma(img, ramp, gamma=1.0):
"""明度→token 渐变带重映射(保留 alpha 与纹理结构——闸①量化)。"""
im = img.convert("RGBA")
px = im.load()
vals = [sum(px[x, y][:3]) / 3.0 for y in range(im.size[1]) for x in range(im.size[0])
if px[x, y][3] > 8]
lo, hi = min(vals), max(vals)
span = max(1.0, hi - lo)
for y in range(im.size[1]):
for x in range(im.size[0]):
r, g, b, a = px[x, y]
if a <= 8:
continue
t = ((sum((r, g, b)) / 3.0) - lo) / span
px[x, y] = ramp_color(ramp, t ** gamma) + (a,)
return im
def recolor_flat(img, rgb):
"""剪影图标单色重着色(保留 alpha 抗锯齿——形状承载识别,闸③)。"""
im = img.convert("RGBA")
px = im.load()
for y in range(im.size[1]):
for x in range(im.size[0]):
r, g, b, a = px[x, y]
if a > 0:
px[x, y] = rgb + (a,)
return im
def main():
jobs = []
# 面板/按钮九宫格(pixel-ui-750 Ancient;texture_margin 取 8px——消费侧同值)
jobs.append(("panel_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "brown.png")), RAMP_PANEL)))
jobs.append(("button_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan.png")), RAMP_BUTTON)))
jobs.append(("button_stone_pressed.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan_pressed.png")), RAMP_PRESSED)))
# 石框(fantasy-ui-borders)
jobs.append(("border_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "kenney-fantasy-ui-borders(CC0)", "PNG", "Default", "Border", "panel-border-000.png")), RAMP_BORDER)))
# 石纹底噪(seamless 1200→256,暗带低对比——B 区/开始层背景)
stone = Image.open(src_path("5-石纹底噪", "stone_6.png")).convert("RGB").resize((256, 256), Image.LANCZOS)
jobs.append(("backdrop_stone.png", remap_luma(stone, RAMP_BACKDROP, gamma=1.6)))
# 营地图标四件(board-game-icons 64px;钥石=奖励金,柜台件=暖白)
icon_dir = ("3-节点图与营地图标", "kenney-board-game-icons(CC0)", "PNG", "Default (64px)")
jobs.append(("icon_keystone.png", recolor_flat(Image.open(src_path(*(icon_dir + ("suit_diamonds.png",)))), ICON_GOLD)))
jobs.append(("icon_forge.png", recolor_flat(Image.open(src_path(*(icon_dir + ("sword.png",)))), ICON_WARM)))
jobs.append(("icon_workshop.png", recolor_flat(Image.open(src_path(*(icon_dir + ("flask_full.png",)))), ICON_WARM)))
jobs.append(("icon_atlas.png", recolor_flat(Image.open(src_path(*(icon_dir + ("book_open.png",)))), ICON_WARM)))
for name, im in jobs:
im.save(out_path(name))
print("saved:", "/".join(OUT + (name,)))
print("=== 量化改色完成(%d 件,全 CC0;闸①②③④ 见脚本头注) ===" % len(jobs))
return 0
if __name__ == "__main__":
import sys
sys.exit(main())