矿彩国风锚 r1 落地:P1 80 镜头(图 1 tileset+三常规兽各 6 帧+词缀精英/光环/双 Boss 22 帧含立绘+UI 13+底图)
+P2 44 镜头(主角 13 帧定妆帧参考图法跨帧身份一致+法器图标 12+弹道命中特效 15+拾取 4);
5 图集 xingji-{map1,beasts,boss,ui,actors}+tiles+bg 共 2,911,369B≈2.72MB(首屏切面≈2.03MB,boss 页 1.03MB=
懒加载分包面,分包决策归开发席 M4);机检 C1–C5 全绿。累计 127 呼 ¥4.19、首过 92%,远离升档 B 判据;
clean-room 五条全程(兽名不入 prompt/伪字物件化改词)。
主会话修机检门两态口径:C2/C3 keyed 中间件只活在批产工作区(ASSET_WORK 不入仓),整目录缺席=仓库态
复跑降级警示跳过、部分缺席仍判错(消除席位环境绿/CI 必红假门,仓库态复跑 rc=0);
顺手修 asset-atlas 局部重打包整写 manifest 丢条目缺陷。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
89 lines
4.4 KiB
Python
89 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
# 《山海行纪》资产批产 · 拼板图(迁移自批次一 contact-sheet.py;眼验入口)
|
|
# 用法: python3 scripts/asset-contact-sheet.py <输出.png> [--prio P1] [--group beasts] [--cols 8] [--title 标题] [--raw]
|
|
# python3 scripts/asset-contact-sheet.py --files <输出.png> <标题> <标签=路径>...
|
|
# 默认取 keyed 后精灵(所见即入图集帧);--raw 取生图原片;bleed 镜头(tiles/bg)始终取原片。
|
|
# 中文标题字体:环境变量 FONT_TTF 或批次一同款霞鹜文楷(OFL)。
|
|
import sys, os, json, tempfile
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work')
|
|
BATCH1 = os.path.normpath(os.path.join(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一'))
|
|
FONT_TTF = os.environ.get('FONT_TTF') or '/private/tmp/claude-501/-Users-lili-Project-games-development-ai/69248bd4-16ac-4906-aea1-4157edf7b10f/scratchpad/deps/LXGWWenKai-Regular.ttf'
|
|
|
|
def load_font(size):
|
|
try: return ImageFont.truetype(FONT_TTF, size)
|
|
except Exception: return ImageFont.load_default()
|
|
|
|
def make_sheet(items, out_path, cols, title, tile_w=300, tile_h=330):
|
|
label_h, title_h, gap = 26, 52, 8
|
|
rows = (len(items) + cols - 1) // cols
|
|
W = cols * (tile_w + gap) + gap
|
|
H = title_h + rows * (tile_h + label_h + gap) + gap
|
|
sheet = Image.new('RGB', (W, H), (30, 34, 32))
|
|
d = ImageDraw.Draw(sheet)
|
|
d.text((gap + 4, 10), title, fill=(232, 197, 106), font=load_font(28))
|
|
f_label = load_font(17)
|
|
for i, (label, path) in enumerate(items):
|
|
r, c = divmod(i, cols)
|
|
x = gap + c * (tile_w + gap)
|
|
y = title_h + r * (tile_h + label_h + gap)
|
|
try:
|
|
im = Image.open(path)
|
|
if im.mode == 'RGBA': # 透明底铺浅格,眼验抠图质量
|
|
bgc = Image.new('RGB', im.size, (58, 62, 58))
|
|
bgc.paste(im, (0, 0), im)
|
|
im = bgc
|
|
else:
|
|
im = im.convert('RGB')
|
|
except Exception as e:
|
|
d.text((x, y), f'{label}: 打开失败 {e}', fill=(200, 80, 80), font=f_label); continue
|
|
im.thumbnail((tile_w, tile_h))
|
|
sheet.paste(im, (x + (tile_w - im.width) // 2, y + (tile_h - im.height) // 2))
|
|
d.rectangle([x, y + tile_h, x + tile_w, y + tile_h + label_h], fill=(20, 23, 21))
|
|
d.text((x + 5, y + tile_h + 3), label, fill=(220, 220, 210), font=f_label)
|
|
sheet.save(out_path)
|
|
print(f'拼板完成:{out_path} ({W}x{H}, {len(items)} 格, {os.path.getsize(out_path)//1024}KB)')
|
|
|
|
def locate(shot, prefer_raw):
|
|
"""镜头 → 现存图:keyed 优先(sprite),bleed/raw 回原片,adopt 回批次一源。"""
|
|
cands = []
|
|
if shot['type'] == 'sprite' and not prefer_raw:
|
|
cands.append(os.path.join(WORK, 'keyed', shot['group'], shot['id'] + '.png'))
|
|
for ext in ('.jpg', '.jpeg', '.png', '.webp'):
|
|
cands.append(os.path.join(WORK, 'raw', shot['id'] + ext))
|
|
if shot.get('adopt'):
|
|
cands.append(os.path.join(BATCH1, shot['adopt']))
|
|
for p in cands:
|
|
if os.path.exists(p): return p
|
|
return None
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) > 1 and sys.argv[1] == '--files':
|
|
out, title = sys.argv[2], sys.argv[3]
|
|
items = [(kv.split('=', 1)[0], kv.split('=', 1)[1]) for kv in sys.argv[4:]]
|
|
make_sheet(items, out, cols=min(6, max(1, len(items))), title=title)
|
|
sys.exit(0)
|
|
out = sys.argv[1]
|
|
argv = sys.argv[2:]
|
|
prio = group = None; cols = 8; title = '山海行纪 · 资产一期'; raw = False
|
|
i = 0
|
|
while i < len(argv):
|
|
if argv[i] == '--prio': prio = argv[i+1]; i += 2
|
|
elif argv[i] == '--group': group = argv[i+1]; i += 2
|
|
elif argv[i] == '--cols': cols = int(argv[i+1]); i += 2
|
|
elif argv[i] == '--title': title = argv[i+1]; i += 2
|
|
elif argv[i] == '--raw': raw = True; i += 1
|
|
else: sys.exit(f'未知参数 {argv[i]}')
|
|
shots = json.load(open(os.path.join(GAME, 'scripts', 'asset-shots.json'), encoding='utf-8'))['shots']
|
|
items, missing = [], []
|
|
for s in shots:
|
|
if prio and s['prio'] != prio: continue
|
|
if group and s['group'] != group: continue
|
|
p = locate(s, raw)
|
|
if p: items.append((s['id'], p))
|
|
else: missing.append(s['id'])
|
|
if missing: print(f"缺图 {len(missing)}:{','.join(missing)}")
|
|
make_sheet(items, out, cols=cols, title=title)
|