"""Anim Studio —— 填入大模型 key,点 Start 即可批量生成游戏动画资产。 启动: pip install -r requirements.txt python app.py 然后浏览器打开终端里给出的本地地址。 """ import json import os import traceback import gradio as gr import providers import spine_builder import particle_builder import tween_builder HERE = os.path.dirname(os.path.abspath(__file__)) DEFAULT_MANIFEST_PATH = os.path.join(HERE, "animation_manifest.json") def _load_default_manifest(): try: with open(DEFAULT_MANIFEST_PATH, "r", encoding="utf-8") as f: return f.read() except Exception: return '{\n "game": "demo",\n "characters": [],\n "vfx": [],\n "ui": []\n}' def run_pipeline(provider, api_key, base_url, model, size, manifest_text, out_dir, progress=gr.Progress()): logs = [] gallery = [] def log(msg): logs.append(msg) return "\n".join(logs) try: manifest = json.loads(manifest_text) except Exception as e: return f"❌ manifest 不是合法 JSON:{e}", [] out_dir = out_dir.strip() or os.path.join(HERE, "out") game = manifest.get("game", "game") base_out = os.path.join(out_dir, game) chars_out = os.path.join(base_out, "characters") vfx_out = os.path.join(base_out, "vfx") ui_out = os.path.join(base_out, "ui") style = manifest.get("style", "") characters = manifest.get("characters", []) vfx = manifest.get("vfx", []) ui = manifest.get("ui", []) log(f"输出目录: {base_out}") # ---- A. Spine 角色(需要图像 API)---- if characters: if not api_key.strip(): log("⚠️ 未填 key,跳过角色生成(VFX / UI 仍会生成)。") else: for i, c in enumerate(characters): cid = c.get("id", f"char_{i}") progress((i + 1) / max(1, len(characters)), desc=f"生成角色 {cid}") try: full_prompt = ", ".join(x for x in [ c.get("prompt", ""), style, "single character, transparent background, no text, no shadow on ground" ] if x) log(f"🎨 [{cid}] 调用图像模型…") img = providers.generate(provider, full_prompt, api_key.strip(), base_url.strip(), model.strip(), size) png = spine_builder.build_character( cid, img, chars_out, c.get("animations", ["idle"])) gallery.append(png) log(f"✅ [{cid}] 已生成 Spine 三件套 + 动画 {c.get('animations', ['idle'])}") except Exception as e: log(f"❌ [{cid}] 失败: {e}") # ---- B. 粒子 VFX(本地)---- for v in vfx: vid = v.get("id", "vfx") try: p = particle_builder.build_particle( vid, v.get("template", "burst"), v.get("color", [255, 255, 255]), vfx_out) log(f"✨ [{vid}] 粒子配置已生成 ({v.get('template')})") except Exception as e: log(f"❌ [{vid}] 粒子失败: {e}") # ---- C. UI Tween(本地)---- if ui: used = [u.get("preset") for u in ui if u.get("preset")] try: path, missing = tween_builder.build_tweens(used, ui_out) log(f"🎛 TweenPresets.ts 已生成 (用到: {used})") if missing: log(f"⚠️ manifest 引用了未定义预设: {missing}(已忽略,可在 tween_builder.py 添加)") except Exception as e: log(f"❌ Tween 失败: {e}") log("\n—— 完成 ——") log(f"把 {base_out} 下的资源导入 Cocos 工程(建议放 page-main-res 远程包)。") log("Spine 三件套(.json/.atlas/.png) 直接作为 sp.SkeletonData 使用。") return "\n".join(logs), gallery def build_ui(): with gr.Blocks(title="Anim Studio") as demo: gr.Markdown("## 🍬 Anim Studio — 游戏动画自动化生成\n" "填入大模型 key → 编辑/沿用默认 manifest → 点 **开始**。\n" "角色用图像模型生成并自动套上 Spine 果冻抖动动画;粒子/UI 动效本地生成。") with gr.Row(): with gr.Column(scale=1): provider = gr.Dropdown(list(providers.PROVIDERS.keys()), value=list(providers.PROVIDERS.keys())[0], label="图像模型 Provider") api_key = gr.Textbox(label="大模型 API Key", type="password", placeholder="sk-...") base_url = gr.Textbox(label="API Base URL", value="https://api.openai.com/v1") model = gr.Textbox(label="模型名", value="gpt-image-2") size = gr.Dropdown(["1024x1024", "1024x1536", "1536x1024"], value="1024x1024", label="生成尺寸") out_dir = gr.Textbox(label="输出目录", value=os.path.join(HERE, "out")) start = gr.Button("▶ 开始", variant="primary") with gr.Column(scale=2): manifest = gr.Code(label="animation_manifest.json", language="json", value=_load_default_manifest(), lines=22) log_box = gr.Textbox(label="运行日志", lines=14) gallery = gr.Gallery(label="生成的角色图", columns=4, height=260) start.click(run_pipeline, inputs=[provider, api_key, base_url, model, size, manifest, out_dir], outputs=[log_box, gallery]) return demo if __name__ == "__main__": build_ui().launch()