92 lines
5.8 KiB
C#
92 lines
5.8 KiB
C#
// WL791_FootProbe.cs — #791 공격 클립의 발 움직임 실측 → 자연스러운 프레임당 이동값 도출 (에디트 모드 · 읽기 전용 · 프리뷰 씬)
|
||
// unity command run_script --file AgentScripts/WL791_FootProbe.cs --entry WL791_FootProbe.Run --args '["Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack1_S.FBX", "Attack1"]'
|
||
// unity command run_script --file AgentScripts/WL791_FootProbe.cs --entry WL791_FootProbe.All
|
||
// 방법: 실제 플레이어 모델(Assets/Res_Addr/PC/Ai01.prefab · Humanoid)을 프리뷰 씬에 띄우고 AnimationMode 로 클립을 30fps 프레임마다 샘플(루트모션 미적용)
|
||
// → 좌/우 발(LeftFoot/RightFoot)의 루트 기준 XZ 를 기록. "접지" = 발 높이 ≤ 최저+5 cm. 접지한 발이 루트 기준으로 뒤로 흐르는 양(−dz) = 그 프레임에
|
||
// 캐릭터가 앞으로 가야 발이 땅에 붙어 보이는 양(제자리 애니메이션의 발 슬라이딩 보정). 접지 발이 앞으로 가는 프레임(딛는 순간)은 0.
|
||
using System.Linq;
|
||
using System.Text;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine;
|
||
|
||
public static class WL791_FootProbe
|
||
{
|
||
const string ModelPath = "Assets/Res_Addr/PC/Ai01.prefab";
|
||
static readonly string[][] Targets =
|
||
{
|
||
new[] { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack1_S.FBX", "Attack1" },
|
||
new[] { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack2_S.FBX", "Attack2" },
|
||
new[] { "Assets/Res_Addr/Animations/Animation/OneHand/Knight@Attack3_S.FBX", "Attack3" },
|
||
};
|
||
|
||
public static object All()
|
||
{
|
||
var sb = new StringBuilder();
|
||
foreach (var t in Targets) sb.Append(Run(t[0], t[1])).AppendLine();
|
||
return sb.ToString();
|
||
}
|
||
|
||
public static object Run(string fbxPath, string clipName)
|
||
{
|
||
var model = AssetDatabase.LoadAssetAtPath<GameObject>(ModelPath);
|
||
var clip = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<AnimationClip>().FirstOrDefault(c => c.name == clipName);
|
||
if (model == null || clip == null) return "load fail model=" + (model != null) + " clip=" + (clip != null) + " " + fbxPath;
|
||
var scene = EditorSceneManager.NewPreviewScene();
|
||
var sb = new StringBuilder();
|
||
bool animMode = false;
|
||
try
|
||
{
|
||
var go = (GameObject)PrefabUtility.InstantiatePrefab(model, scene);
|
||
go.transform.position = Vector3.zero; go.transform.rotation = Quaternion.identity;
|
||
var anim = go.GetComponentInChildren<Animator>();
|
||
if (anim == null || anim.avatar == null) return "animator/avatar null on " + ModelPath;
|
||
var animGo = anim.gameObject;
|
||
anim.applyRootMotion = false;
|
||
var lf = anim.GetBoneTransform(HumanBodyBones.LeftFoot);
|
||
var rf = anim.GetBoneTransform(HumanBodyBones.RightFoot);
|
||
var hips = anim.GetBoneTransform(HumanBodyBones.Hips);
|
||
if (lf == null || rf == null || hips == null) return "foot/hips bones null avatar=" + anim.avatar.name;
|
||
float fps = clip.frameRate; int n = Mathf.RoundToInt(clip.length * fps);
|
||
var L = new Vector3[n + 1]; var R = new Vector3[n + 1]; var H = new Vector3[n + 1];
|
||
AnimationMode.StartAnimationMode(); animMode = true;
|
||
for (int i = 0; i <= n; i++)
|
||
{
|
||
AnimationMode.BeginSampling();
|
||
AnimationMode.SampleAnimationClip(animGo, clip, i / fps);
|
||
AnimationMode.EndSampling();
|
||
L[i] = animGo.transform.InverseTransformPoint(lf.position);
|
||
R[i] = animGo.transform.InverseTransformPoint(rf.position);
|
||
H[i] = animGo.transform.InverseTransformPoint(hips.position);
|
||
}
|
||
float minY = Mathf.Min(L.Min(p => p.y), R.Min(p => p.y));
|
||
float groundThr = minY + 0.05f;
|
||
sb.AppendLine("== " + clipName + " len=" + clip.length.ToString("F3") + "s frames=" + n + " fps=" + fps + " model=" + anim.avatar.name + " minFootY=" + minY.ToString("F3") + " ==");
|
||
sb.AppendLine(" f | Lz Ly | Rz Ry | 접지 | 루트전진(cm) | 누적(cm) | hipsZ(cm)");
|
||
float accum = 0f; var perFrame = new float[n + 1];
|
||
for (int i = 1; i <= n; i++)
|
||
{
|
||
bool lG = L[i].y <= groundThr, rG = R[i].y <= groundThr;
|
||
float lDz = L[i].z - L[i - 1].z, rDz = R[i].z - R[i - 1].z;
|
||
float step = 0f; string g = "-";
|
||
if (lG && rG) { step = Mathf.Max(-lDz, -rDz); g = "LR"; }
|
||
else if (lG) { step = -lDz; g = "L"; }
|
||
else if (rG) { step = -rDz; g = "R"; }
|
||
if (step < 0f) step = 0f;
|
||
perFrame[i] = step; accum += step;
|
||
sb.AppendLine(string.Format(" {0,2} | {1,5:F2} {2,5:F2} | {3,5:F2} {4,5:F2} | {5,-3} | {6,7:F1} | {7,7:F1} | {8,6:F1}",
|
||
i, L[i].z, L[i].y, R[i].z, R[i].y, g, step * 100f, accum * 100f, H[i].z * 100f));
|
||
}
|
||
sb.AppendLine("합계 전진 = " + (accum * 100f).ToString("F1") + " cm · 프레임당(cm): " + string.Join(",", perFrame.Skip(1).Select(v => (v * 100f).ToString("F1"))));
|
||
float maxStride = Enumerable.Range(0, n + 1).Max(i => Mathf.Abs(L[i].z - R[i].z));
|
||
sb.AppendLine("최대 보폭(두 발 z 차) = " + (maxStride * 100f).ToString("F1") + " cm · hips z 범위 " + (H.Min(p => p.z) * 100f).ToString("F0") + "~" + (H.Max(p => p.z) * 100f).ToString("F0") + " cm · 발 z 범위 L " + (L.Min(p => p.z) * 100f).ToString("F0") + "~" + (L.Max(p => p.z) * 100f).ToString("F0") + " R " + (R.Min(p => p.z) * 100f).ToString("F0") + "~" + (R.Max(p => p.z) * 100f).ToString("F0"));
|
||
}
|
||
finally
|
||
{
|
||
if (animMode) AnimationMode.StopAnimationMode();
|
||
EditorSceneManager.ClosePreviewScene(scene);
|
||
}
|
||
return sb.ToString();
|
||
}
|
||
}
|