Replace TownSquare with HomeCamp art and land humanoid bots, LittleBear, and LmGirl review assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-11-03 20:33:08 +08:00
co-authored by Cursor
parent 45e62132c8
commit 9eec364f30
838 changed files with 57978 additions and 12225 deletions
@@ -0,0 +1,178 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Animations;
using UnityEngine.Playables;
public static class DiagnoseSharedHumanoidWalk
{
const string Boy="Assets/Bundles/Characters/Human/test/Boy_mixamo/CampGuideStudio.fbx";
const string Girl="Assets/Bundles/Characters/Human/test/girl/LmGirlTpose.fbx";
const string Walk="Assets/Bundles/Characters/Human/Common/Walking.fbx";
static string Output=>Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/lmgirl_tpose_studio/shared_walk_audit");
static float[] V(Vector3 v)=>new[]{v.x,v.y,v.z};
static string P(UnityEngine.Object o)=>o?AssetDatabase.GetAssetPath(o):null;
static readonly HumanBodyBones[] BoneIds={HumanBodyBones.Hips,HumanBodyBones.LeftUpperLeg,HumanBodyBones.RightUpperLeg,HumanBodyBones.LeftLowerLeg,HumanBodyBones.RightLowerLeg,HumanBodyBones.LeftFoot,HumanBodyBones.RightFoot,HumanBodyBones.LeftToes,HumanBodyBones.RightToes};
static object[] Pose(Animator a)=>BoneIds.Select(id=>{var t=a.GetBoneTransform(id);return (object)new{bone=id.ToString(),name=t?t.name:null,position=t?V(a.transform.InverseTransformPoint(t.position)):null,rotation=t?V(t.localEulerAngles):null};}).ToArray();
public static string Inspect()
{
var scene=EditorSceneManager.NewPreviewScene();
try
{
var assets=new List<object>();
foreach(var path in new[]{Boy,Girl,Walk})
{
var importer=(ModelImporter)AssetImporter.GetAtPath(path);var hd=importer.humanDescription;
var avatar=AssetDatabase.LoadAllAssetsAtPath(path).OfType<Avatar>().FirstOrDefault();
var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(path),scene);
var animator=go.GetComponent<Animator>();if(animator)animator.runtimeAnimatorController=null;
var ahd=avatar?avatar.humanDescription:new HumanDescription();
assets.Add(new{path,importer.animationType,importer.avatarSetup,sourceAvatar=P(importer.sourceAvatar),avatar=avatar?avatar.name:null,valid=avatar&&avatar.isValid,
importerHuman=hd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),importerSkeleton=hd.skeleton.Select(b=>new{b.name,p=V(b.position),r=V(b.rotation.eulerAngles),s=V(b.scale)}).ToArray(),
avatarHuman=ahd.human==null?null:ahd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),avatarSkeleton=ahd.skeleton==null?null:ahd.skeleton.Select(b=>new{b.name,p=V(b.position),r=V(b.rotation.eulerAngles),s=V(b.scale)}).ToArray(),
pose=animator&&animator.isHuman?Pose(animator):null});
UnityEngine.Object.DestroyImmediate(go);
}
Directory.CreateDirectory(Output);var json=JsonConvert.SerializeObject(assets,Formatting.Indented);File.WriteAllText(Path.Combine(Output,"avatars.json"),json);return "Saved "+Output+"/avatars.json";
}
finally{EditorSceneManager.ClosePreviewScene(scene);}
}
public static string Compare()
{
var scene=EditorSceneManager.NewPreviewScene();var results=new List<object>();
try
{
foreach(var path in new[]{Boy,Girl})foreach(bool footIK in new[]{false,true})
{
var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(path),scene);
var a=go.GetComponent<Animator>();a.runtimeAnimatorController=null;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate;
var clip=AssetDatabase.LoadAllAssetsAtPath(Walk).OfType<AnimationClip>().First(c=>!c.name.StartsWith("__preview__"));
var graph=PlayableGraph.Create("SharedWalkAudit");graph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
try
{
var playable=AnimationClipPlayable.Create(graph,clip);playable.SetApplyFootIK(footIK);playable.SetApplyPlayableIK(false);
var output=AnimationPlayableOutput.Create(graph,"Animation",a);output.SetSourcePlayable(playable);graph.Play();
var frames=new List<object>();float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0;
for(int i=0;i<125;i++)
{
playable.SetTime(i/120.0);graph.Evaluate(0);
var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position);
float gap=right.x-left.x;minGap=Mathf.Min(minGap,gap);maxGap=Mathf.Max(maxGap,gap);if(gap<0)crossed++;
frames.Add(new{t=i/120.0,gap,pose=Pose(a)});
}
results.Add(new{path,footIK,minGap,maxGap,crossed,frames});
}
finally{graph.Destroy();UnityEngine.Object.DestroyImmediate(go);}
}
Directory.CreateDirectory(Output);var json=JsonConvert.SerializeObject(results,Formatting.Indented);File.WriteAllText(Path.Combine(Output,"baseline.json"),json);
return "Saved "+Output+"/baseline.json";
}
finally{EditorSceneManager.ClosePreviewScene(scene);}
}
public static string VerifyController()
{
const string controllerPath="Assets/Bundles/Characters/Human/Common/Man.controller";
var controller=AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
var scene=EditorSceneManager.NewPreviewScene();var results=new List<object>();
var units=new List<GameObject>();var controllers=new List<AnimatorController>();
var material=new Material(Shader.Find("Universal Render Pipeline/Lit"));material.color=new Color(.65f,.7f,.76f);
try
{
int column=0;
foreach(var path in new[]{Boy,Girl})foreach(bool footIK in new[]{false,true})
{
// A transient controller contains only the actual walk state and clip.
var c=new AnimatorController();controllers.Add(c);c.AddLayer("Base Layer");
var state=c.layers[0].stateMachine.AddState("walk");state.motion=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state.motion;state.iKOnFeet=footIK;c.layers[0].stateMachine.defaultState=state;
var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(path),scene);units.Add(go);
go.transform.position=new Vector3((1.5f-column++)*.85f,0,0);
var a=go.GetComponent<Animator>();a.runtimeAnimatorController=c;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate;
foreach(var r in go.GetComponentsInChildren<Renderer>())r.sharedMaterial=material;
a.Rebind();a.Update(0);
float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0,minFrame=0;
for(int i=0;i<125;i++)
{
a.Play("Base Layer.walk",0,(i/120f)/state.motion.averageDuration);a.Update(0);
var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position);
float gap=right.x-left.x;if(gap<minGap){minGap=gap;minFrame=i;}maxGap=Mathf.Max(maxGap,gap);if(gap<0)crossed++;
}
results.Add(new{path,footIK,minGap,maxGap,crossed,minTime=minFrame/120f});
// Show all variants at the same phase, near the observed crossing.
a.Play("Base Layer.walk",0,(32/120f)/state.motion.averageDuration);a.Update(0);
var hips=a.GetBoneTransform(HumanBodyBones.Hips);var hp=hips.position;hp.x=go.transform.position.x;hp.z=0;hips.position=hp;
}
var light=new GameObject("AuditLight").AddComponent<Light>();SceneManager.MoveGameObjectToScene(light.gameObject,scene);light.type=LightType.Directional;light.intensity=1.5f;light.transform.rotation=Quaternion.Euler(35,135,0);
var camera=new GameObject("AuditCamera").AddComponent<Camera>();SceneManager.MoveGameObjectToScene(camera.gameObject,scene);camera.scene=scene;camera.orthographic=true;camera.orthographicSize=.8f;
camera.transform.position=new Vector3(0,.65f,5);camera.transform.LookAt(new Vector3(0,.65f,0));camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.19f,.22f,.26f);camera.nearClipPlane=.01f;camera.farClipPlane=20;
var rt=new RenderTexture(1600,720,24);var tex=new Texture2D(1600,720,TextureFormat.RGB24,false);var previous=RenderTexture.active;
Directory.CreateDirectory(Output);
try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,1600,720),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(Output,"foot_ik_comparison.png"),tex.EncodeToPNG());}
finally{RenderTexture.active=previous;camera.targetTexture=null;rt.Release();UnityEngine.Object.DestroyImmediate(rt);UnityEngine.Object.DestroyImmediate(tex);}
var json=JsonConvert.SerializeObject(new{order=new[]{"Boy FootIK off","Boy FootIK on","Girl FootIK off","Girl FootIK on"},controllerPath,controllerDirty=EditorUtility.IsDirty(controller),stateDirty=EditorUtility.IsDirty(controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state),results},Formatting.Indented);
File.WriteAllText(Path.Combine(Output,"controller_verification.json"),json);return json;
}
finally
{
foreach(var go in units)UnityEngine.Object.DestroyImmediate(go);
foreach(var c in controllers){foreach(var l in c.layers){foreach(var s in l.stateMachine.states)UnityEngine.Object.DestroyImmediate(s.state);UnityEngine.Object.DestroyImmediate(l.stateMachine);}UnityEngine.Object.DestroyImmediate(c);}
EditorSceneManager.ClosePreviewScene(scene);UnityEngine.Object.DestroyImmediate(material);
}
}
public static string FixFootIK()
{
const string controllerPath="Assets/Bundles/Characters/Human/Common/Man.controller";
var controller=AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
var state=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state;
if(EditorUtility.IsDirty(controller)||EditorUtility.IsDirty(state))throw new Exception("Controller has unsaved edits; refusing to save unrelated edits.");
var scenesBefore=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray();
bool previous=state.iKOnFeet;
state.iKOnFeet=true;EditorUtility.SetDirty(state);AssetDatabase.SaveAssetIfDirty(state);
var importer=(ModelImporter)AssetImporter.GetAtPath(Walk);
if(!state.iKOnFeet || P(state.motion)!=Walk || P(importer.sourceAvatar)!=Boy)throw new Exception("Unexpected controller/source setup");
var json=JsonConvert.SerializeObject(new{controllerPath,state=state.name,previousFootIK=previous,footIK=state.iKOnFeet,walk=P(state.motion),sourceAvatar=P(importer.sourceAvatar),
controllerDirty=EditorUtility.IsDirty(controller),stateDirty=EditorUtility.IsDirty(state),
actors=UnityEngine.Object.FindObjectsOfType<Animator>(true).Where(a=>a.runtimeAnimatorController==controller).Select(a=>new{a.name,avatar=P(a.avatar),a.applyRootMotion}).ToArray(),
scenesBefore,scenesAfter=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray()},Formatting.Indented);
File.WriteAllText(Path.Combine(Output,"fix.json"),json);return json;
}
public static string VerifyApplied()
{
var controller=AssetDatabase.LoadAssetAtPath<AnimatorController>("Assets/Bundles/Characters/Human/Common/Man.controller");
var state=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state;
if(!state.iKOnFeet)throw new Exception("Foot IK disabled");
var scene=EditorSceneManager.NewPreviewScene();var results=new List<object>();
try
{
foreach(var path in new[]{Boy,Girl})
{
var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(path),scene);
try
{
var a=go.GetComponent<Animator>();a.runtimeAnimatorController=controller;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate;a.Rebind();a.Update(0);
float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0;
for(int i=0;i<125;i++)
{
a.Play("Base Layer.walk",0,(i/120f)/state.motion.averageDuration);a.Update(0);
var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position);
float gap=right.x-left.x;minGap=Mathf.Min(minGap,gap);maxGap=Mathf.Max(maxGap,gap);if(gap<0)crossed++;
}
if(crossed>0)throw new Exception("Crossing after fix: "+path);
results.Add(new{path,avatar=P(a.avatar),samples=125,minGap,maxGap,crossed});
}
finally{UnityEngine.Object.DestroyImmediate(go);}
}
var json=JsonConvert.SerializeObject(new{controller=P(controller),state=state.name,footIK=state.iKOnFeet,clip=P(state.motion),results},Formatting.Indented);
File.WriteAllText(Path.Combine(Output,"validation.json"),json);return json;
}
finally{EditorSceneManager.ClosePreviewScene(scene);}
}
}
#endif