Project_WL/AgentScripts/WL887_Assets.cs

197 lines
12 KiB
C#

// 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
{
/// <summary>에셋 미리보기 PNG 저장(AssetPreview 캐시 — 첫 호출은 생성만 걸릴 수 있어 두 번 부른다).</summary>
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<Object>(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<ShieldModel> 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; }
/// <summary>#891 재질 복사본(원본 무수정) — 어두운 면 색 · 그림자 받기.</summary>
static Material MakeMat(ShieldMat s, StringBuilder sb)
{
if (s == null || string.IsNullOrEmpty(s.from) || string.IsNullOrEmpty(s.@out)) return null;
var src = AssetDatabase.LoadAssetAtPath<Material>(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<Material>(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;
}
/// <summary>방패 래퍼 프리팹 생성(자식 = 팩 프리팹 인스턴스 · 배율/회전/위치는 자식) + Addressables 등록 (값 = AgentScripts/WL887_Shield.json · 멱등).</summary>
public static object BuildShield()
{
var spec = Newtonsoft.Json.JsonConvert.DeserializeObject<ShieldSpec>(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<GameObject>(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<Renderer>(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();
}
/// <summary>VFX 래퍼 프리팹(Resources) — 자식 = 팩 이펙트 프리팹 인스턴스(원본 무수정 · 런타임 Resources.Load 용). pairs = "출력경로=원본경로|…".</summary>
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<GameObject>(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();
}
/// <summary>프리팹만 미리보기 씬에 세워 +Z · -Z · +X 쪽에서 찍는다(조명 = 카메라 뒤 방향광). 파일 = 접두 + 방향.png</summary>
public static object ModelShots(string prefabPath, string prefix, int size)
{
var p = AssetDatabase.LoadAssetAtPath<GameObject>(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<Renderer>(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<Camera>(); 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<Light>(); 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();
}
/// <summary>컨트롤러 0번 레이어 상태 → 클립 이름(원본 파일) · 속도.</summary>
public static object States(string ctrlPaths)
{
var sb = new StringBuilder();
foreach (var cp in ctrlPaths.Split('|'))
{
var c = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(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();
}
/// <summary>프리팹 구조 · 렌더러 · 재질 · 크기(바운즈) · 루트 회전.</summary>
public static object Describe(string paths)
{
var sb = new StringBuilder();
foreach (var p in paths.Split('|'))
{
var go = AssetDatabase.LoadAssetAtPath<GameObject>(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<Renderer>(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<Renderer>(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();
}
}