pipeline.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. """可被网站/命令行复用的生成管线。
  2. 读 manifest -> 生成各类资产 -> 写 library.json(供网站可视化列出与预览)。
  3. """
  4. import json
  5. import os
  6. import providers
  7. import spine_builder
  8. import particle_builder
  9. import tween_builder
  10. import background_remover
  11. HERE = os.path.dirname(os.path.abspath(__file__))
  12. def run(manifest, out_root, creds=None, log=print):
  13. """manifest: dict; out_root: 输出根目录; creds: {provider,api_key,base_url,model,size}
  14. 返回 (library_dict, base_out)。"""
  15. creds = creds or {}
  16. game = manifest.get("game", "game")
  17. base_out = os.path.join(out_root, game)
  18. chars_out = os.path.join(base_out, "characters")
  19. vfx_out = os.path.join(base_out, "vfx")
  20. ui_out = os.path.join(base_out, "ui")
  21. style = manifest.get("style", "")
  22. library = {
  23. "game": game,
  24. "slot_config": manifest.get("slot_config", {}),
  25. "characters": [],
  26. "vfx": [],
  27. "ui": [],
  28. "ui_art": [],
  29. }
  30. remove_bg_enabled = bool(creds.get("remove_bg", False))
  31. total_steps = len(manifest.get("characters", [])) + len(manifest.get("ui_art", [])) + len(manifest.get("vfx", [])) + (1 if manifest.get("ui", []) else 0)
  32. done_steps = 0
  33. def progress(label):
  34. nonlocal done_steps
  35. done_steps += 1
  36. log(f"进度 {done_steps}/{max(1, total_steps)} · {label}")
  37. def transparent_prompt(extra):
  38. return ", ".join([
  39. extra,
  40. "no shadow",
  41. "no background",
  42. "transparent background",
  43. "isolated subject",
  44. "clean PNG asset",
  45. "输出 PNG,背景必须是真实 Alpha 透明通道,不是白底,不是棋盘格。主体居中,边缘干净,适合用于 App、网页和海报叠加",
  46. ])
  47. # ---- A. 角色(Spine)----
  48. for i, c in enumerate(manifest.get("characters", [])):
  49. cid = c.get("id", f"char_{i}")
  50. anims = c.get("animations", ["idle"])
  51. if not creds.get("api_key"):
  52. log(f"⚠️ 未填 key,跳过角色 {cid}")
  53. continue
  54. try:
  55. full_prompt = ", ".join(x for x in [
  56. c.get("prompt", ""), style,
  57. transparent_prompt("single game icon character, centered, full body in frame, no text"),
  58. ] if x)
  59. log(f"🎨 [{cid}] 生成角色图…")
  60. img = providers.generate(creds["provider"], full_prompt, creds["api_key"],
  61. creds.get("base_url", "https://api.openai.com/v1"),
  62. creds.get("model", "gpt-image-2"),
  63. c.get("size", creds.get("size", "1024x1024")))
  64. img = background_remover.remove_background(img, log=log, label=cid, enabled=remove_bg_enabled,
  65. image_url=img.info.get("source_url"))
  66. spine_builder.build_character(cid, img, chars_out, anims)
  67. w, h = spine_builder.trim_to_content(img).size
  68. library["characters"].append({
  69. "id": cid,
  70. "png": f"characters/{cid}.png",
  71. "w": w, "h": h,
  72. "animations": spine_builder.anim_data(anims),
  73. "files": [f"characters/{cid}.json", f"characters/{cid}.atlas",
  74. f"characters/{cid}.png"],
  75. })
  76. log(f"✅ [{cid}] 完成 ({anims})")
  77. progress(f"{cid}")
  78. except Exception as e:
  79. log(f"❌ [{cid}] 失败: {e}")
  80. progress(f"{cid}")
  81. # ---- A2. UI 美术(背景 / Logo / 卷轴框 / 按钮 等整图)----
  82. ui_art_out = os.path.join(base_out, "ui_art")
  83. for a in manifest.get("ui_art", []):
  84. aid = a.get("id", "art")
  85. if not creds.get("api_key"):
  86. log(f"⚠️ 未填 key,跳过 UI 美术 {aid}")
  87. continue
  88. try:
  89. transparent = a.get("transparent", True)
  90. extra = (transparent_prompt("single clean UI element, no text")
  91. if transparent
  92. else "full-bleed illustration, no text, no UI elements")
  93. full_prompt = ", ".join(x for x in [a.get("prompt", ""), style if a.get("use_style") else "", extra] if x)
  94. log(f"🖼 [{aid}] 生成 UI 美术…")
  95. img = providers.generate(creds["provider"], full_prompt, creds["api_key"],
  96. creds.get("base_url", "https://api.openai.com/v1"),
  97. creds.get("model", "gpt-image-2"),
  98. a.get("size", creds.get("size", "1024x1024")))
  99. img = background_remover.remove_background(img, log=log, label=aid, enabled=remove_bg_enabled and transparent,
  100. image_url=img.info.get("source_url"))
  101. os.makedirs(ui_art_out, exist_ok=True)
  102. img.save(os.path.join(ui_art_out, f"{aid}.png"))
  103. library["ui_art"].append({"id": aid, "file": f"ui_art/{aid}.png",
  104. "w": img.width, "h": img.height,
  105. "transparent": transparent})
  106. log(f"✅ [{aid}] UI 美术完成")
  107. progress(f"{aid}")
  108. except Exception as e:
  109. log(f"❌ [{aid}] UI 美术失败: {e}")
  110. progress(f"{aid}")
  111. # ---- B. 粒子 VFX ----
  112. for v in manifest.get("vfx", []):
  113. vid = v.get("id", "vfx")
  114. try:
  115. path = particle_builder.build_particle(
  116. vid, v.get("template", "burst"), v.get("color", [255, 255, 255]), vfx_out)
  117. cfg = json.load(open(path, encoding="utf-8"))
  118. library["vfx"].append({"id": vid, "template": v.get("template"),
  119. "file": f"vfx/{vid}.particle.json", "config": cfg})
  120. log(f"✨ [{vid}] 粒子配置完成")
  121. progress(f"{vid}")
  122. except Exception as e:
  123. log(f"❌ [{vid}] 粒子失败: {e}")
  124. progress(f"{vid}")
  125. # ---- C. UI Tween ----
  126. ui = manifest.get("ui", [])
  127. if ui:
  128. used = [u.get("preset") for u in ui if u.get("preset")]
  129. try:
  130. tween_builder.build_tweens(used, ui_out)
  131. for u in ui:
  132. library["ui"].append({"id": u.get("id"), "preset": u.get("preset"),
  133. "params": u.get("params", {})})
  134. log(f"🎛 TweenPresets.ts 完成 ({used})")
  135. progress("TweenPresets")
  136. except Exception as e:
  137. log(f"❌ Tween 失败: {e}")
  138. progress("TweenPresets")
  139. os.makedirs(base_out, exist_ok=True)
  140. with open(os.path.join(base_out, "library.json"), "w", encoding="utf-8") as f:
  141. json.dump(library, f, ensure_ascii=False, indent=2)
  142. log("—— 完成 ——")
  143. return library, base_out