Project_WL/AgentScripts/WL838_BowRelease.cs

71 lines
4.5 KiB
C#
Raw Permalink Normal View History

// WL838_BowRelease.cs — 활 클립(.anim 또는 FBX)에서 「시위를 놓는 순간」을 수치로 찾는다 (에디트 모드 · 프리뷰 씬)
// run_script --file AgentScripts/WL838_BowRelease.cs --entry WL838_BowRelease.Run --args '["Assets/WL/Combat/Anim/FR/FR_Bow0.anim", ""]'
// 프레임(30 fps)마다 양손 거리(당김) · 오른손 속도 · 왼손(활팔) 앞 뻗음을 찍는다. 당김이 최대인 뒤 양손 거리가 급감하는 프레임 = 발사(Projectile) 후보.
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
public static class WL838_BowRelease
{
public static string Run(string src, string clipName)
{
string pcName = WL.Character.WLCharacterSwapSettings.Resolve("Ai01");
var model = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Res_Addr/PC/" + pcName + ".prefab");
if (model == null) return "PC 프리팹 없음";
AnimationClip clip;
if (src.EndsWith(".anim")) clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(src);
else
{
var clips = AssetDatabase.LoadAllAssetsAtPath(src).OfType<AnimationClip>().Where(c => !c.name.StartsWith("__preview")).ToArray();
clip = string.IsNullOrEmpty(clipName) ? clips.FirstOrDefault() : (clips.FirstOrDefault(c => c.name == clipName) ?? clips.FirstOrDefault(c => c.name.Contains(clipName)));
}
if (clip == null) return "클립 없음 " + src;
var scene = EditorSceneManager.NewPreviewScene();
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) return "Animator 없음";
AnimationMode.StartAnimationMode(); animMode = true;
var sb = new StringBuilder();
sb.Append("clip=").Append(clip.name).Append(" len=").Append(clip.length.ToString("0.000")).Append("s human=").Append(clip.isHumanMotion).Append('\n');
sb.Append("f t norm handDist rHandSpd lHandFwd rHandFwd rHandUp\n");
int frames = Mathf.Max(2, Mathf.RoundToInt(clip.length * 30f));
Vector3 prevR = Vector3.zero; float maxDist = 0f; int maxF = -1;
var rows = new System.Collections.Generic.List<string>();
var dists = new float[frames + 1];
for (int f = 0; f <= frames; f++)
{
float t = Mathf.Min(f / 30f, clip.length);
AnimationMode.BeginSampling(); AnimationMode.SampleAnimationClip(anim.gameObject, clip, t); AnimationMode.EndSampling();
var lh = anim.GetBoneTransform(HumanBodyBones.LeftHand); var rh = anim.GetBoneTransform(HumanBodyBones.RightHand); var hips = anim.GetBoneTransform(HumanBodyBones.Hips);
if (lh == null || rh == null || hips == null) return "손/힙 본 없음";
Vector3 fwd = go.transform.forward, up = Vector3.up;
float d = Vector3.Distance(lh.position, rh.position);
dists[f] = d;
float spd = f == 0 ? 0f : Vector3.Distance(rh.position, prevR) * 30f;
prevR = rh.position;
if (d > maxDist) { maxDist = d; maxF = f; }
rows.Add(string.Format("{0,-3} {1,5:0.000} {2,5:0.00} {3,8:0.000} {4,9:0.00} {5,9:0.000} {6,9:0.000} {7,8:0.000}", f, t, t / clip.length, d, spd,
Vector3.Dot(lh.position - hips.position, fwd), Vector3.Dot(rh.position - hips.position, fwd), Vector3.Dot(rh.position - hips.position, up)));
}
foreach (var r in rows) sb.Append(r).Append('\n');
// 발사 후보 = 최대 당김 이후 양손 거리가 최대의 85% 아래로 처음 떨어지는 프레임
int rel = -1;
for (int f = maxF + 1; f <= frames; f++) if (dists[f] < maxDist * 0.85f) { rel = f; break; }
sb.Append("maxDraw f").Append(maxF).Append(" (norm ").Append((maxF / 30f / clip.length).ToString("0.00")).Append(") dist ").Append(maxDist.ToString("0.000"))
.Append(" · release candidate f").Append(rel).Append(rel >= 0 ? " (norm " + (rel / 30f / clip.length).ToString("0.00") + ")" : "");
return sb.ToString();
}
finally
{
if (animMode) AnimationMode.StopAnimationMode();
EditorSceneManager.ClosePreviewScene(scene);
}
}
}