Add prototype gameplay loop with character/battlefield assets and server game APIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-10-20 18:50:00 +08:00
co-authored by Cursor
parent 79369e4473
commit 6c64210331
833 changed files with 998079 additions and 1042 deletions
+32 -10
View File
@@ -1,19 +1,22 @@
#!/usr/bin/env python3
"""将角色 XPbr / 烘焙产物安装到 AIClient/Assets/Bundles/Characters/{ActorName}/。
"""将角色 XPbr / 烘焙产物安装到 Bundles/Characters。
人物:Characters/Human/{ActorName}/
不传 --kind 时落到 Characters/{ActorName}/(兼容 MaleExplorer)。
优先识别 XPbr 产物(推荐):
{ActorName}.fbx
{ActorName}_base.png # RGB albedo, A=AO/透明
{ActorName}_mix.png # RG sphere-normal, B=metallic, A=roughness
{ActorName}.mat # Test/XPbr
{ActorName}.prefab # Unity 菜单生成
{ActorName}.prefab # Pipeline SetupCharacter 生成
概念图留在 Doc/Design/Character/,不要拷进 Bundles。
兼容旧五通道烘焙目录(_color/_normal/_ao/_metallic/_roughness)。
用法:
python Tools/install_character_bundles.py -a MaleExplorer -b Tools/output/actor_game/actor_xpbr
python Tools/install_character_bundles.py -a CaoCao --kind Human -b Tools/output/caocao_game/caocao_xpbr -c Doc/Design/Character/CaoCao/caocao.png
"""
from __future__ import annotations
@@ -35,22 +38,32 @@ def _pick_first(files: list[Path], predicate) -> Path | None:
return None
def _safe_seg(name: str, label: str) -> str:
text = (name or "").strip()
if not text or any(ch in text for ch in r'\/:*?"<>|'):
raise ValueError(f"非法 {label}: {name!r}")
return text
def install(
*,
actor: str,
bake_dir: Path,
kind: str | None = None,
concept: Path | None = None,
dry_run: bool = False,
) -> dict:
actor = actor.strip()
if not actor or any(ch in actor for ch in r'\/:*?"<>|'):
raise ValueError(f"非法 ActorName: {actor!r}")
actor = _safe_seg(actor, "ActorName")
kind = _safe_seg(kind, "Kind") if kind else None
bake_dir = bake_dir.expanduser().resolve()
if not bake_dir.is_dir():
raise FileNotFoundError(f"资源目录不存在: {bake_dir}")
dest = _repo_root() / "AIClient" / "Assets" / "Bundles" / "Characters" / actor
dest = _repo_root() / "AIClient" / "Assets" / "Bundles" / "Characters"
if kind:
dest = dest / kind
dest = dest / actor
files = list(bake_dir.iterdir())
sources: dict[str, Path] = {}
@@ -126,12 +139,14 @@ def install(
result = {
"ok": True,
"actor": actor,
"kind": kind,
"mode": mode,
"dest": str(dest.relative_to(_repo_root())).replace("\\", "/"),
"files": copied,
"hint": (
f"在 Unity 执行菜单 AIGame/M3/Setup Character Prefab + Art Collector "
f"生成挂 XPbr 的 {actor}.prefab;并登记 Doc/ResDesc_Model.md"
"Unity Pipeline: unity command run_script --file AgentScripts/SetupCharacter.cs "
f"--entry SetupCharacter.Run --args {json.dumps([kind or '', actor])}"
";并登记 Doc/ResDesc_Model.md"
),
}
if concept is not None:
@@ -141,7 +156,13 @@ def install(
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="安装角色资源到 Bundles/Characters/{Actor}")
p.add_argument("--actor", "-a", required=True, help="角色目录名 / 寻址名,如 MaleExplorer")
p.add_argument("--actor", "-a", required=True, help="角色目录名 / 寻址名,如 CaoCao")
p.add_argument(
"--kind",
"-k",
default=None,
help="人物用 Human,落到 Characters/{Kind}/{Actor};省略则为 Characters/{Actor}",
)
p.add_argument(
"--bake-dir",
"-b",
@@ -160,6 +181,7 @@ def main(argv: list[str] | None = None) -> int:
result = install(
actor=args.actor,
bake_dir=Path(args.bake_dir),
kind=args.kind,
concept=Path(args.concept) if args.concept else None,
dry_run=args.dry_run,
)