115 lines
5.7 KiB
C#
115 lines
5.7 KiB
C#
// WL762_RootCurve.cs — PD 지시 #762 M1 실측 (에디트 모드 · Assets 무수정)
|
|
// unity command run_script --file AgentScripts/WL762_RootCurve.cs --entry WL762_RootCurve.Run
|
|
// 휴머노이드 클립은 AnimationMode.SampleAnimationClip 으로 본 위치를 못 읽는 경우가 있어
|
|
// (실측 2026-09-06 — 전 구간 0.00), 클립의 **루트 모션 커브(RootT.x/y/z)** 를 직접 읽는다.
|
|
// 아울러 서브클립 .meta 에 걸린 애니메이션 이벤트(Move·ShowEffect) 시각도 함께 덤프한다.
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
public static class WL762_RootCurve
|
|
{
|
|
public static object Run()
|
|
{
|
|
var sb = new StringBuilder();
|
|
// 원본 FBX (Paladin = Sword&Shield 계열 · Knight = OneHand 계열)
|
|
string[] fbx =
|
|
{
|
|
"Assets/Res_Addr/Animations/Animation/Paladin/attack.FBX",
|
|
"Assets/Res_Addr/Animations/Animation/Paladin/Stander@Sword&Shield_Attack2.FBX",
|
|
"Assets/Res_Addr/Animations/Animation/Paladin/Stander@Sword&Shield_Attack3.FBX",
|
|
"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",
|
|
};
|
|
foreach (var p in fbx) sb.Append(DumpFbx(p));
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>서브클립을 품은 FBX 를 이름으로 찾아 전부 덤프한다.</summary>
|
|
public static object Find(string clipNameFilter)
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var guid in AssetDatabase.FindAssets("t:Model"))
|
|
{
|
|
string path = AssetDatabase.GUIDToAssetPath(guid);
|
|
var imp = AssetImporter.GetAtPath(path) as ModelImporter;
|
|
if (imp == null) continue;
|
|
if (!imp.clipAnimations.Any(c => c.name.Contains(clipNameFilter))) continue;
|
|
sb.Append(DumpFbx(path));
|
|
}
|
|
return sb.Length == 0 ? "no fbx with clip containing: " + clipNameFilter : sb.ToString();
|
|
}
|
|
|
|
static string DumpFbx(string fbxPath)
|
|
{
|
|
var sb = new StringBuilder();
|
|
var imp = AssetImporter.GetAtPath(fbxPath) as ModelImporter;
|
|
if (imp == null) return "importer not found: " + fbxPath + "\n";
|
|
sb.AppendLine("### " + fbxPath + " animType=" + imp.animationType);
|
|
|
|
var clips = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<AnimationClip>()
|
|
.Where(c => !c.name.StartsWith("__preview__")).ToDictionary(c => c.name, c => c);
|
|
|
|
foreach (var ca in imp.clipAnimations)
|
|
{
|
|
AnimationClip clip;
|
|
clips.TryGetValue(ca.name, out clip);
|
|
sb.AppendLine(string.Format(" -- {0} len={1} frames[{2}..{3}] bakeXZ(loopBlendPositionXZ)={4} bakeY={5} bakeRot={6} keepOrigXZ={7} keepOrigY={8}",
|
|
ca.name, clip != null ? clip.length.ToString("F3") : "?", ca.firstFrame, ca.lastFrame,
|
|
ca.lockRootPositionXZ, ca.lockRootHeightY, ca.lockRootRotation, ca.keepOriginalPositionXZ, ca.keepOriginalPositionY));
|
|
|
|
if (clip != null) sb.Append(DumpRoot(clip));
|
|
|
|
var evs = ca.events;
|
|
if (evs != null && evs.Length > 0)
|
|
{
|
|
var parts = evs.Select(e => string.Format("{0}@{1:F5}(i={2},f={3:F3},s='{4}')",
|
|
e.functionName, e.time, e.intParameter, e.floatParameter, e.stringParameter));
|
|
sb.AppendLine(" events: " + string.Join(" | ", parts.ToArray()));
|
|
}
|
|
else sb.AppendLine(" events: (none)");
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>클립의 RootT/RootQ 커브를 직접 읽어 전진량을 잰다.</summary>
|
|
static string DumpRoot(AnimationClip clip)
|
|
{
|
|
var sb = new StringBuilder();
|
|
var binds = AnimationUtility.GetCurveBindings(clip);
|
|
var names = new List<string>();
|
|
AnimationCurve cx = null, cy = null, cz = null;
|
|
foreach (var b in binds)
|
|
{
|
|
if (b.propertyName == "RootT.x") cx = AnimationUtility.GetEditorCurve(clip, b);
|
|
else if (b.propertyName == "RootT.y") cy = AnimationUtility.GetEditorCurve(clip, b);
|
|
else if (b.propertyName == "RootT.z") cz = AnimationUtility.GetEditorCurve(clip, b);
|
|
if (b.propertyName.StartsWith("Root") || b.propertyName.StartsWith("Motion")) names.Add(b.propertyName);
|
|
}
|
|
sb.AppendLine(" rootBindings=[" + string.Join(",", names.Distinct().ToArray()) + "] hasRootCurves=" + clip.hasRootCurves + " hasMotion=" + clip.hasMotionCurves);
|
|
if (cz == null) { sb.AppendLine(" (RootT.z curve 없음 — 루트 전진 커브 미존재)"); return sb.ToString(); }
|
|
|
|
float z0 = cz.Evaluate(0f), x0 = cx != null ? cx.Evaluate(0f) : 0f;
|
|
float maxZ = 0f, minZ = 0f, tMax = 0f, maxX = 0f;
|
|
var rows = new List<string>();
|
|
int n = 10;
|
|
for (int i = 0; i <= n; i++)
|
|
{
|
|
float t = clip.length * i / n;
|
|
float dz = cz.Evaluate(t) - z0;
|
|
float dx = cx != null ? cx.Evaluate(t) - x0 : 0f;
|
|
if (dz > maxZ) { maxZ = dz; tMax = t; }
|
|
if (dz < minZ) minZ = dz;
|
|
if (Mathf.Abs(dx) > Mathf.Abs(maxX)) maxX = dx;
|
|
rows.Add(string.Format("{0:F2}:{1:F3}", t, dz));
|
|
}
|
|
sb.AppendLine(" RootT.z Δ: " + string.Join(" ", rows.ToArray()));
|
|
sb.AppendLine(string.Format(" => 루트 전진 max +{0:F3} m (t={1:F2}s), min {2:F3} m, 최종 {3:F3} m, 측면 max {4:F3} m",
|
|
maxZ, tMax, minZ, cz.Evaluate(clip.length) - z0, maxX));
|
|
return sb.ToString();
|
|
}
|
|
}
|