| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- """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()
|