// WL834_PoseCapture.cs — 클립의 포즈를 에디트 모드에서 렌더해 한 장(프레임 띠)으로 저장 (Play 타이밍 운에 기대지 않고 모션 자체를 본다) // unity command run_script --file AgentScripts/WL834_PoseCapture.cs --entry WL834_PoseCapture.Run --args "[\"\", \"\", \"\", , \"\", 8]" // 예: --args "[\"Assets/ExplosiveLLC/Archer Warrior Mecanim Animation Pack/Animations/Archer@Attack1_S.FBX\", \"\", \"PP_Theme_05_Bow_003\", 6, \"bow_attack1\", 8]" // 치비(LH_M05 · 런타임 스케일)에 무기를 끼우고 클립을 균등 N 프레임 샘플 → 카메라(앞-왼 3/4 · 내려보기 30°)로 렌더 → Assets/Screenshots_WL/pose_.png (가로 띠). using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; public static class WL834_PoseCapture { public static object Run(string fbx, string clipName, string weaponPrefab, int socketIndex, string outName, int frames) { return RunR(fbx, clipName, weaponPrefab, socketIndex, outName, frames, "0,0,0"); } /// 무기 로컬 회전(오일러 "x,y,z")을 더해 렌더 — 소켓 방향 규약 보정 후보를 비교한다. public static object RunR(string fbx, string clipName, string weaponPrefab, int socketIndex, string outName, int frames, string euler) { Vector3 e = Vector3.zero; try { var p = euler.Split(','); if (p.Length == 3) e = new Vector3(float.Parse(p[0]), float.Parse(p[1]), float.Parse(p[2])); } catch (System.Exception) { } string pcName = WL.Character.WLCharacterSwapSettings.Resolve("Ai01"); var model = AssetDatabase.LoadAssetAtPath("Assets/Res_Addr/PC/" + pcName + ".prefab"); if (model == null) return "PC 프리팹 없음"; var clips = fbx.EndsWith(".anim") // #838 FR .anim 에셋도 받는다 ? new[] { AssetDatabase.LoadAssetAtPath(fbx) }.Where(c => c != null).ToArray() : AssetDatabase.LoadAllAssetsAtPath(fbx).OfType().Where(c => !c.name.StartsWith("__preview")).ToArray(); var clip = string.IsNullOrEmpty(clipName) ? clips.FirstOrDefault() : (clips.FirstOrDefault(c => c.name == clipName) ?? clips.FirstOrDefault(c => c.name.Contains(clipName))); if (clip == null) return "클립 없음 " + fbx + " [" + string.Join(",", clips.Select(c => c.name).ToArray()) + "]"; float rootScale = 1f; try { var arr = Newtonsoft.Json.Linq.JArray.Parse(System.IO.File.ReadAllText("Assets/ResWork/Table/Export/ClassConfig.json")); foreach (var row in arr) if ((string)row["n_ClassID"] == "10101") { float f; float.TryParse((string)row["f_Scale"], out f); rootScale = Mathf.Max(f, 0.01f) * Mathf.Max(WL.Character.WLCharacterSwapSettings.HeightCompensation, 0.01f); break; } } catch (System.Exception) { } int n = Mathf.Clamp(frames, 2, 16); int tw = 300, th = 400; var scene = EditorSceneManager.NewPreviewScene(); bool animMode = false; RenderTexture rt = null; Texture2D sheet = null; try { var go = (GameObject)PrefabUtility.InstantiatePrefab(model, scene); go.transform.position = Vector3.zero; go.transform.rotation = Quaternion.identity; go.transform.localScale = Vector3.one * rootScale; var pc = go.GetComponentInChildren(true); if (pc == null || pc.tfs_weapon == null || socketIndex < 0 || socketIndex >= pc.tfs_weapon.Length) return "PCActor/tfs_weapon 없음 또는 소켓 범위 밖"; if (!string.IsNullOrEmpty(weaponPrefab)) { var wp = AssetDatabase.LoadAssetAtPath("Assets/Res_Addr/Weapon/" + weaponPrefab + ".prefab"); if (wp == null) return "무기 프리팹 없음 " + weaponPrefab; var w = (GameObject)PrefabUtility.InstantiatePrefab(wp, scene); w.transform.SetParent(pc.tfs_weapon[socketIndex], false); w.transform.localPosition = Vector3.zero; w.transform.localRotation = Quaternion.identity; w.transform.localScale = Vector3.one; var fitter = go.GetComponent(); if (fitter == null) fitter = go.AddComponent(); try { fitter.Fit(w.transform, pc.tfs_weapon[socketIndex]); } catch (System.Exception) { } if (e != Vector3.zero) w.transform.localRotation = Quaternion.Euler(e) * w.transform.localRotation; } var anim = go.GetComponentInChildren(); var animGo = anim.gameObject; anim.applyRootMotion = false; var lightGo = new GameObject("light"); SceneManager_Move(lightGo, scene); var light = lightGo.AddComponent(); light.type = LightType.Directional; light.intensity = 1.2f; lightGo.transform.rotation = Quaternion.Euler(50f, -30f, 0f); var camGo = new GameObject("cam"); SceneManager_Move(camGo, scene); var cam = camGo.AddComponent(); cam.scene = scene; cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.55f, 0.65f, 0.45f); cam.fieldOfView = 30f; cam.nearClipPlane = 0.05f; cam.farClipPlane = 50f; Vector3 look = new Vector3(0f, 0.55f * rootScale, 0f); camGo.transform.position = look + Quaternion.Euler(25f, -35f, 0f) * new Vector3(0f, 0f, -3.2f); camGo.transform.LookAt(look); rt = new RenderTexture(tw, th, 24); cam.targetTexture = rt; sheet = new Texture2D(tw * n, th, TextureFormat.RGB24, false); AnimationMode.StartAnimationMode(); animMode = true; for (int i = 0; i < n; i++) { float t = clip.length * i / (n - 1); AnimationMode.BeginSampling(); AnimationMode.SampleAnimationClip(animGo, clip, Mathf.Min(t, clip.length - 0.0001f)); AnimationMode.EndSampling(); cam.Render(); var prev = RenderTexture.active; RenderTexture.active = rt; var tex = new Texture2D(tw, th, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, tw, th), 0, 0); tex.Apply(); RenderTexture.active = prev; sheet.SetPixels(i * tw, 0, tw, th, tex.GetPixels()); Object.DestroyImmediate(tex); } AnimationMode.StopAnimationMode(); animMode = false; sheet.Apply(); string outPath = "Assets/Screenshots_WL/pose_" + outName + ".png"; System.IO.Directory.CreateDirectory("Assets/Screenshots_WL"); System.IO.File.WriteAllBytes(outPath, sheet.EncodeToPNG()); return "저장 " + outPath + " · 클립 " + clip.name + " len=" + clip.length.ToString("F2") + "s · 프레임 " + n + " · 무기 " + weaponPrefab + "@" + socketIndex + " euler=" + euler; } finally { if (animMode) AnimationMode.StopAnimationMode(); if (rt != null) { RenderTexture.active = null; rt.Release(); Object.DestroyImmediate(rt); } if (sheet != null) Object.DestroyImmediate(sheet); EditorSceneManager.ClosePreviewScene(scene); } } static void SceneManager_Move(GameObject go, UnityEngine.SceneManagement.Scene scene) { UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(go, scene); } }