// WL887_Assets.cs — PD 지시 #887 검 + 방패 · 방패 치기 이펙트 교체 — 에셋 확인 도구(에디터 · 읽기 전용) // unity command run_script --file AgentScripts/WL887_Assets.cs --entry WL887_Assets.Previews --args '[출력 폴더, "경로1|경로2|..."]' using System.Text; using UnityEditor; using UnityEngine; public static class WL887_Assets { /// 에셋 미리보기 PNG 저장(AssetPreview 캐시 — 첫 호출은 생성만 걸릴 수 있어 두 번 부른다). public static object Previews(string outDir, string paths) { var sb = new StringBuilder(); System.IO.Directory.CreateDirectory(outDir); foreach (var p in paths.Split('|')) { var o = AssetDatabase.LoadAssetAtPath(p); if (o == null) { sb.Append("없음 ").Append(p).Append('\n'); continue; } var tex = AssetPreview.GetAssetPreview(o); if (tex == null) { sb.Append(AssetPreview.IsLoadingAssetPreview(o.GetInstanceID()) ? "생성 중 " : "null ").Append(p).Append('\n'); continue; } var copy = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false); copy.SetPixels(tex.GetPixels()); copy.Apply(); var name = System.IO.Path.GetFileNameWithoutExtension(p); System.IO.File.WriteAllBytes(System.IO.Path.Combine(outDir, name + ".png"), copy.EncodeToPNG()); Object.DestroyImmediate(copy); sb.Append("저장 ").Append(name).Append(' ').Append(tex.width).Append('x').Append(tex.height).Append('\n'); } return sb.ToString(); } class ShieldSpec { public string outDir; public System.Collections.Generic.List models; } class ShieldModel { public string name; public string source; public float[] scale; public float[] euler; public float[] offset; public ShieldMat material; } class ShieldMat { public string from; public string @out; public float goochDark = -1f; public int receiveShadows = 1; } /// #891 재질 복사본(원본 무수정) — 어두운 면 색 · 그림자 받기. static Material MakeMat(ShieldMat s, StringBuilder sb) { if (s == null || string.IsNullOrEmpty(s.from) || string.IsNullOrEmpty(s.@out)) return null; var src = AssetDatabase.LoadAssetAtPath(s.from); if (src == null) { sb.Append("재질 원본 없음 ").Append(s.from).Append('\n'); return null; } System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(s.@out)); var m = AssetDatabase.LoadAssetAtPath(s.@out); if (m == null) { m = new Material(src); AssetDatabase.CreateAsset(m, s.@out); } else m.CopyPropertiesFromMaterial(src); m.shaderKeywords = src.shaderKeywords; if (s.goochDark >= 0f && m.HasProperty("_GoochDarkColor")) m.SetColor("_GoochDarkColor", new Color(s.goochDark, s.goochDark, s.goochDark, 1f)); if (m.HasProperty("_ReceiveShadows")) m.SetFloat("_ReceiveShadows", s.receiveShadows); if (s.receiveShadows == 0) { m.DisableKeyword("_MK_RECEIVE_SHADOWS"); m.DisableKeyword("_RECEIVESHADOWS_ON"); } EditorUtility.SetDirty(m); sb.Append("재질 ").Append(s.@out).Append(" goochDark=").Append(s.goochDark).Append(" receive=").Append(s.receiveShadows).Append('\n'); return m; } /// 방패 래퍼 프리팹 생성(자식 = 팩 프리팹 인스턴스 · 배율/회전/위치는 자식) + Addressables 등록 (값 = AgentScripts/WL887_Shield.json · 멱등). public static object BuildShield() { var spec = Newtonsoft.Json.JsonConvert.DeserializeObject(System.IO.File.ReadAllText("AgentScripts/WL887_Shield.json")); var sb = new StringBuilder(); var settings = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings; foreach (var m in spec.models) { var src = AssetDatabase.LoadAssetAtPath(m.source); if (src == null) { sb.Append("원본 없음 ").Append(m.source).Append('\n'); continue; } string path = spec.outDir + "/" + m.name + ".prefab"; var root = new GameObject(m.name); try { var child = (GameObject)PrefabUtility.InstantiatePrefab(src); child.transform.SetParent(root.transform, false); child.transform.localPosition = new Vector3(m.offset[0], m.offset[1], m.offset[2]); child.transform.localRotation = Quaternion.Euler(m.euler[0], m.euler[1], m.euler[2]); child.transform.localScale = new Vector3(m.scale[0], m.scale[1], m.scale[2]); var mat = MakeMat(m.material, sb); foreach (var r in child.GetComponentsInChildren(true)) { if (mat != null) r.sharedMaterial = mat; if (m.material != null) r.receiveShadows = m.material.receiveShadows != 0; } PrefabUtility.SaveAsPrefabAsset(root, path); } finally { Object.DestroyImmediate(root); } if (settings != null) { var e = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(path), settings.DefaultGroup, false, false); e.address = path; } sb.Append("프리팹 ").Append(path).Append(" ← ").Append(System.IO.Path.GetFileNameWithoutExtension(m.source)).Append(" scale(").Append(string.Join(",", m.scale)).Append(") euler(").Append(string.Join(",", m.euler)).Append(") · 주소 등록\n"); } if (settings != null) settings.SetDirty(UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.ModificationEvent.EntryMoved, null, true, true); AssetDatabase.SaveAssets(); return sb.ToString(); } /// VFX 래퍼 프리팹(Resources) — 자식 = 팩 이펙트 프리팹 인스턴스(원본 무수정 · 런타임 Resources.Load 용). pairs = "출력경로=원본경로|…". public static object BuildFx(string pairs) { var sb = new StringBuilder(); foreach (var pr in pairs.Split('|')) { var kv = pr.Split('='); if (kv.Length != 2) continue; var src = AssetDatabase.LoadAssetAtPath(kv[1]); if (src == null) { sb.Append("원본 없음 ").Append(kv[1]).Append('\n'); continue; } System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(kv[0])); var root = new GameObject(System.IO.Path.GetFileNameWithoutExtension(kv[0])); try { var child = (GameObject)PrefabUtility.InstantiatePrefab(src); child.transform.SetParent(root.transform, false); PrefabUtility.SaveAsPrefabAsset(root, kv[0]); } finally { Object.DestroyImmediate(root); } sb.Append("VFX 래퍼 ").Append(kv[0]).Append(" ← ").Append(System.IO.Path.GetFileName(kv[1])).Append('\n'); } AssetDatabase.SaveAssets(); return sb.ToString(); } /// 프리팹만 미리보기 씬에 세워 +Z · -Z · +X 쪽에서 찍는다(조명 = 카메라 뒤 방향광). 파일 = 접두 + 방향.png public static object ModelShots(string prefabPath, string prefix, int size) { var p = AssetDatabase.LoadAssetAtPath(prefabPath); if (p == null) return "없음 " + prefabPath; var scene = UnityEditor.SceneManagement.EditorSceneManager.NewPreviewScene(); var sb = new StringBuilder(); try { var go = (GameObject)PrefabUtility.InstantiatePrefab(p, scene); var b = new Bounds(go.transform.position, Vector3.zero); bool has = false; foreach (var r in go.GetComponentsInChildren(true)) { if (!has) { b = r.bounds; has = true; } else b.Encapsulate(r.bounds); } var camGo = new GameObject("cam"); UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(camGo, scene); var cam = camGo.AddComponent(); cam.scene = scene; cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.35f, 0.4f, 0.45f); cam.fieldOfView = 30f; cam.nearClipPlane = 0.01f; cam.farClipPlane = 100f; var lightGo = new GameObject("light"); UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(lightGo, scene); var lt = lightGo.AddComponent(); lt.type = LightType.Directional; lt.intensity = 1.2f; var rt = new RenderTexture(size, size, 24); cam.targetTexture = rt; float dist = b.extents.magnitude * 4f; foreach (var dir in new[] { "+Z", "-Z", "+X" }) { Vector3 v = dir == "+Z" ? Vector3.forward : dir == "-Z" ? Vector3.back : Vector3.right; cam.transform.position = b.center + v * dist; cam.transform.LookAt(b.center); lightGo.transform.rotation = cam.transform.rotation; cam.Render(); var prev = RenderTexture.active; RenderTexture.active = rt; var tex = new Texture2D(size, size, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, size, size), 0, 0); tex.Apply(); RenderTexture.active = prev; System.IO.File.WriteAllBytes(prefix + dir + ".png", tex.EncodeToPNG()); Object.DestroyImmediate(tex); sb.Append("저장 ").Append(dir).Append(' '); } cam.targetTexture = null; rt.Release(); Object.DestroyImmediate(rt); } finally { UnityEditor.SceneManagement.EditorSceneManager.ClosePreviewScene(scene); } return sb.ToString(); } /// 컨트롤러 0번 레이어 상태 → 클립 이름(원본 파일) · 속도. public static object States(string ctrlPaths) { var sb = new StringBuilder(); foreach (var cp in ctrlPaths.Split('|')) { var c = AssetDatabase.LoadAssetAtPath(cp); if (c == null) { sb.Append("없음 ").Append(cp).Append('\n'); continue; } sb.Append("## ").Append(System.IO.Path.GetFileNameWithoutExtension(cp)).Append('\n'); foreach (var cs in c.layers[0].stateMachine.states) { var m = cs.state.motion; var path = m != null ? AssetDatabase.GetAssetPath(m) : ""; var dir = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(path)); sb.Append(cs.state.name).Append('=').Append(m != null ? m.name : "-").Append('(').Append(dir).Append('/').Append(System.IO.Path.GetFileName(path)).Append(") "); } sb.Append('\n'); } return sb.ToString(); } /// 프리팹 구조 · 렌더러 · 재질 · 크기(바운즈) · 루트 회전. public static object Describe(string paths) { var sb = new StringBuilder(); foreach (var p in paths.Split('|')) { var go = AssetDatabase.LoadAssetAtPath(p); if (go == null) { sb.Append("없음 ").Append(p).Append('\n'); continue; } var b = new Bounds(go.transform.position, Vector3.zero); bool has = false; foreach (var r in go.GetComponentsInChildren(true)) { if (!has) { b = r.bounds; has = true; } else b.Encapsulate(r.bounds); } sb.Append(System.IO.Path.GetFileNameWithoutExtension(p)).Append(" root rot=").Append(go.transform.localEulerAngles).Append(" scale=").Append(go.transform.localScale) .Append(" bounds c=").Append(b.center.ToString("F3")).Append(" size=").Append(b.size.ToString("F3")).Append(" children=").Append(go.transform.childCount); foreach (var r in go.GetComponentsInChildren(true)) sb.Append("\n ").Append(r.name).Append(" lpos=").Append(r.transform.localPosition.ToString("F3")).Append(" lrot=").Append(r.transform.localEulerAngles.ToString("F0")).Append(" lscale=").Append(r.transform.localScale.ToString("F2")) .Append(" mat=").Append(r.sharedMaterial != null ? r.sharedMaterial.name + "/" + (r.sharedMaterial.shader != null ? r.sharedMaterial.shader.name : "-") : "null"); var guid = AssetDatabase.AssetPathToGUID(p); var settings = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings; var entry = settings != null ? settings.FindAssetEntry(guid) : null; sb.Append("\n addressable=").Append(entry != null ? entry.address + " @" + entry.parentGroup.Name : "아님").Append('\n'); } return sb.ToString(); } }