// WL789_RootProbe.cs — Knight 공격 클립의 루트 이동(RootT) 곡선 실측 (에디트 모드 · 읽기 전용) // unity command run_script --file AgentScripts/WL789_RootProbe.cs --entry WL789_RootProbe.Dump --args '["Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack1_S.FBX"]' // unity command run_script --file AgentScripts/WL789_RootProbe.cs --entry WL789_RootProbe.All // 휴머노이드 클립의 RootT.x/y/z · RootQ 커브를 8등분 샘플로 출력한다(정규화 시간 · 미터). 이동 거리 = 시작→끝 XZ 변위. using System.Linq; using System.Text; using UnityEditor; using UnityEngine; public static class WL789_RootProbe { static readonly string[] Paths = { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack1_S.FBX", "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack2_S.FBX", "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack3_S.FBX", "Assets/Res_Addr/Animations/Animation/Common_PlatformAnim/Stander@Chase_Start.FBX", "Assets/Res_Addr/Animations/Animation/Paladin/attack.FBX", }; public static object All() { var sb = new StringBuilder(); foreach (var p in Paths) sb.Append(Dump(p)); return sb.ToString(); } public static object Dump(string path) { var sb = new StringBuilder(); var clips = AssetDatabase.LoadAllAssetsAtPath(path).OfType().Where(c => !c.name.StartsWith("__preview")).ToArray(); sb.AppendLine("== " + path + " clips=" + clips.Length); foreach (var clip in clips) { var bindings = AnimationUtility.GetCurveBindings(clip); AnimationCurve rx = null, ry = null, rz = null; foreach (var b in bindings) { if (b.propertyName == "RootT.x") rx = AnimationUtility.GetEditorCurve(clip, b); else if (b.propertyName == "RootT.y") ry = AnimationUtility.GetEditorCurve(clip, b); else if (b.propertyName == "RootT.z") rz = AnimationUtility.GetEditorCurve(clip, b); } sb.Append(" - " + clip.name + " len=" + clip.length.ToString("F3") + "s root=" + (rx != null && rz != null ? "yes" : "no") + " hasRootCurves=" + clip.hasRootCurves + " hasMotionCurves=" + clip.hasMotionCurves + " humanMotion=" + clip.humanMotion); if (rx != null && rz != null) { float x0 = rx.Evaluate(0f), z0 = rz.Evaluate(0f); float xe = rx.Evaluate(clip.length), ze = rz.Evaluate(clip.length); sb.Append(" | XZ 변위 = " + Mathf.Sqrt((xe - x0) * (xe - x0) + (ze - z0) * (ze - z0)).ToString("F3") + " m (dx " + (xe - x0).ToString("F3") + ", dz " + (ze - z0).ToString("F3") + ")"); sb.AppendLine(); sb.Append(" n: "); for (int i = 0; i <= 8; i++) { float t = clip.length * i / 8f; float dx = rx.Evaluate(t) - x0, dz = rz.Evaluate(t) - z0; sb.Append((i / 8f).ToString("F2") + "→" + Mathf.Sqrt(dx * dx + dz * dz).ToString("F2") + " "); } if (ry != null) sb.Append("| y " + ry.Evaluate(0f).ToString("F2") + "→max " + Enumerable.Range(0, 9).Max(i => ry.Evaluate(clip.length * i / 8f)).ToString("F2")); } sb.AppendLine(); } var imp = AssetImporter.GetAtPath(path) as ModelImporter; if (imp != null) { var c0 = imp.clipAnimations.Length > 0 ? imp.clipAnimations[0] : (imp.defaultClipAnimations.Length > 0 ? imp.defaultClipAnimations[0] : null); if (c0 != null) sb.AppendLine(" importer clip0=" + c0.name + " lockRootPositionXZ=" + c0.lockRootPositionXZ + " keepOriginalPositionXZ=" + c0.keepOriginalPositionXZ + " lockRootHeightY=" + c0.lockRootHeightY + " keepOriginalPositionY=" + c0.keepOriginalPositionY + " lockRootRotation=" + c0.lockRootRotation); } return sb.ToString(); } }