// WL789_Import.cs — Knight 공격 FBX 의 Root Motion Node 를 으로 (에디트 모드 · 메타 백업 후) // unity command run_script --file AgentScripts/WL789_Import.cs --entry WL789_Import.Show // unity command run_script --file AgentScripts/WL789_Import.cs --entry WL789_Import.SetMotionNodeNone // unity command run_script --file AgentScripts/WL789_Import.cs --entry WL789_Import.Restore (백업 메타 복원) // 근거: 런타임 실측(2026-09-07 00:3x) — applyRootMotion=true 인데도 Knight@Attack1~3_S 클립의 animator.deltaPosition 합 = 0.000 m // (Chase_Start 1.996 m · Run 0.124 m 는 정상). 클립에 MotionT 커브(hasMotionCurves=True)가 있어 Unity 가 RootT 대신 // 움직이지 않는 모션 노드 커브를 루트모션으로 쓰기 때문 → 모션 노드를 None 으로 두면 RootT(실측 0.30 m 등)를 쓴다. using System.IO; using System.Text; using UnityEditor; using UnityEngine; public static class WL789_Import { 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", }; const string BackupDir = "AgentScripts/staging/_backup_wl789_meta"; public static object Show() { var sb = new StringBuilder(); foreach (var p in Paths) { var imp = AssetImporter.GetAtPath(p) as ModelImporter; sb.AppendLine(Path.GetFileName(p) + " motionNodeName='" + (imp != null ? imp.motionNodeName : "?") + "' animationType=" + (imp != null ? imp.animationType.ToString() : "?")); } return sb.ToString(); } public static object SetMotionNodeNone() { if (EditorApplication.isPlaying) return "ABORT: Play 중"; Directory.CreateDirectory(BackupDir); var sb = new StringBuilder(); foreach (var p in Paths) { var meta = p + ".meta"; File.Copy(meta, Path.Combine(BackupDir, Path.GetFileName(meta)), true); var imp = AssetImporter.GetAtPath(p) as ModelImporter; if (imp == null) { sb.AppendLine(p + " importer null"); continue; } string before = imp.motionNodeName; imp.motionNodeName = ""; imp.SaveAndReimport(); var imp2 = AssetImporter.GetAtPath(p) as ModelImporter; sb.AppendLine(Path.GetFileName(p) + " motionNode '" + before + "' → '" + (imp2 != null ? imp2.motionNodeName : "?") + "'"); } AssetDatabase.SaveAssets(); return sb.ToString(); } public static object Restore() { if (EditorApplication.isPlaying) return "ABORT: Play 중"; int n = 0; foreach (var p in Paths) { var b = Path.Combine(BackupDir, Path.GetFileName(p) + ".meta"); if (!File.Exists(b)) continue; File.Copy(b, p + ".meta", true); n++; AssetDatabase.ImportAsset(p, ImportAssetOptions.ForceUpdate); } return "restored " + n + " meta(s)"; } }