Project_WL/AgentScripts/WL791_FootProbe.cs

92 lines
5.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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();
}
}