app.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """Anim Studio —— 填入大模型 key,点 Start 即可批量生成游戏动画资产。
  2. 启动:
  3. pip install -r requirements.txt
  4. python app.py
  5. 然后浏览器打开终端里给出的本地地址。
  6. """
  7. import json
  8. import os
  9. import traceback
  10. import gradio as gr
  11. import providers
  12. import spine_builder
  13. import particle_builder
  14. import tween_builder
  15. HERE = os.path.dirname(os.path.abspath(__file__))
  16. DEFAULT_MANIFEST_PATH = os.path.join(HERE, "animation_manifest.json")
  17. def _load_default_manifest():
  18. try:
  19. with open(DEFAULT_MANIFEST_PATH, "r", encoding="utf-8") as f:
  20. return f.read()
  21. except Exception:
  22. return '{\n "game": "demo",\n "characters": [],\n "vfx": [],\n "ui": []\n}'
  23. def run_pipeline(provider, api_key, base_url, model, size, manifest_text, out_dir,
  24. progress=gr.Progress()):
  25. logs = []
  26. gallery = []
  27. def log(msg):
  28. logs.append(msg)
  29. return "\n".join(logs)
  30. try:
  31. manifest = json.loads(manifest_text)
  32. except Exception as e:
  33. return f"❌ manifest 不是合法 JSON:{e}", []
  34. out_dir = out_dir.strip() or os.path.join(HERE, "out")
  35. game = manifest.get("game", "game")
  36. base_out = os.path.join(out_dir, game)
  37. chars_out = os.path.join(base_out, "characters")
  38. vfx_out = os.path.join(base_out, "vfx")
  39. ui_out = os.path.join(base_out, "ui")
  40. style = manifest.get("style", "")
  41. characters = manifest.get("characters", [])
  42. vfx = manifest.get("vfx", [])
  43. ui = manifest.get("ui", [])
  44. log(f"输出目录: {base_out}")
  45. # ---- A. Spine 角色(需要图像 API)----
  46. if characters:
  47. if not api_key.strip():
  48. log("⚠️ 未填 key,跳过角色生成(VFX / UI 仍会生成)。")
  49. else:
  50. for i, c in enumerate(characters):
  51. cid = c.get("id", f"char_{i}")
  52. progress((i + 1) / max(1, len(characters)), desc=f"生成角色 {cid}")
  53. try:
  54. full_prompt = ", ".join(x for x in [
  55. c.get("prompt", ""), style,
  56. "single character, transparent background, no text, no shadow on ground"
  57. ] if x)
  58. log(f"🎨 [{cid}] 调用图像模型…")
  59. img = providers.generate(provider, full_prompt, api_key.strip(),
  60. base_url.strip(), model.strip(), size)
  61. png = spine_builder.build_character(
  62. cid, img, chars_out, c.get("animations", ["idle"]))
  63. gallery.append(png)
  64. log(f"✅ [{cid}] 已生成 Spine 三件套 + 动画 {c.get('animations', ['idle'])}")
  65. except Exception as e:
  66. log(f"❌ [{cid}] 失败: {e}")
  67. # ---- B. 粒子 VFX(本地)----
  68. for v in vfx:
  69. vid = v.get("id", "vfx")
  70. try:
  71. p = particle_builder.build_particle(
  72. vid, v.get("template", "burst"), v.get("color", [255, 255, 255]), vfx_out)
  73. log(f"✨ [{vid}] 粒子配置已生成 ({v.get('template')})")
  74. except Exception as e:
  75. log(f"❌ [{vid}] 粒子失败: {e}")
  76. # ---- C. UI Tween(本地)----
  77. if ui:
  78. used = [u.get("preset") for u in ui if u.get("preset")]
  79. try:
  80. path, missing = tween_builder.build_tweens(used, ui_out)
  81. log(f"🎛 TweenPresets.ts 已生成 (用到: {used})")
  82. if missing:
  83. log(f"⚠️ manifest 引用了未定义预设: {missing}(已忽略,可在 tween_builder.py 添加)")
  84. except Exception as e:
  85. log(f"❌ Tween 失败: {e}")
  86. log("\n—— 完成 ——")
  87. log(f"把 {base_out} 下的资源导入 Cocos 工程(建议放 page-main-res 远程包)。")
  88. log("Spine 三件套(.json/.atlas/.png) 直接作为 sp.SkeletonData 使用。")
  89. return "\n".join(logs), gallery
  90. def build_ui():
  91. with gr.Blocks(title="Anim Studio") as demo:
  92. gr.Markdown("## 🍬 Anim Studio — 游戏动画自动化生成\n"
  93. "填入大模型 key → 编辑/沿用默认 manifest → 点 **开始**。\n"
  94. "角色用图像模型生成并自动套上 Spine 果冻抖动动画;粒子/UI 动效本地生成。")
  95. with gr.Row():
  96. with gr.Column(scale=1):
  97. provider = gr.Dropdown(list(providers.PROVIDERS.keys()),
  98. value=list(providers.PROVIDERS.keys())[0],
  99. label="图像模型 Provider")
  100. api_key = gr.Textbox(label="大模型 API Key", type="password",
  101. placeholder="sk-...")
  102. base_url = gr.Textbox(label="API Base URL",
  103. value="https://api.openai.com/v1")
  104. model = gr.Textbox(label="模型名", value="gpt-image-2")
  105. size = gr.Dropdown(["1024x1024", "1024x1536", "1536x1024"],
  106. value="1024x1024", label="生成尺寸")
  107. out_dir = gr.Textbox(label="输出目录", value=os.path.join(HERE, "out"))
  108. start = gr.Button("▶ 开始", variant="primary")
  109. with gr.Column(scale=2):
  110. manifest = gr.Code(label="animation_manifest.json", language="json",
  111. value=_load_default_manifest(), lines=22)
  112. log_box = gr.Textbox(label="运行日志", lines=14)
  113. gallery = gr.Gallery(label="生成的角色图", columns=4, height=260)
  114. start.click(run_pipeline,
  115. inputs=[provider, api_key, base_url, model, size, manifest, out_dir],
  116. outputs=[log_box, gallery])
  117. return demo
  118. if __name__ == "__main__":
  119. build_ui().launch()