// WL-814j 셋업 (게임 코드 아님 · 에디트 모드 전용) // asset: unity command run_script --file AgentScripts/WL814j_Setup.cs --entry WL814j_Setup.MakeSettings // feature: unity command run_script --file AgentScripts/WL814j_Setup.cs --entry WL814j_Setup.AddFeature // 🔴 URP 에셋 디스크 수정은 「인게임 렌더러 피처 1건」만(814c AddOutlineFeature 선례 그대로 · 디스크 기본 m_Active: 0). using System; using System.IO; using System.Text; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using WL.Look.Palette; public static class WL814j_Setup { const string kSoDir = "Assets/WL/Look/Palette/Resources/WL"; const string kSoPath = kSoDir + "/WLPaletteSettings.asset"; const string kRendererPath = "Assets/Settings/URP-Balanced-Renderer.asset"; static StringBuilder s_sb; static int s_pass, s_fail; static void L(string s) { s_sb.AppendLine(s); Debug.Log("[WL814j] " + s); } static void Chk(string n, bool ok, string d) { if (ok) s_pass++; else s_fail++; L((ok ? " PASS " : " FAIL ") + n + " — " + d); } static void Write(string p) { File.WriteAllText(p, s_sb.ToString(), new UTF8Encoding(false)); Debug.Log("[WL814j] → " + p); } // ═════════════════════════════════════════════════════════════════════════ // ① 값 SO 생성 (발주서 기본값 · C8 enabled_ = 1) // ═════════════════════════════════════════════════════════════════════════ public static void MakeSettings() { s_sb = new StringBuilder(); s_pass = 0; s_fail = 0; L("WL-814j 팔레트 설정 SO 생성 · " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); try { Directory.CreateDirectory(kSoDir); AssetDatabase.Refresh(); var so = AssetDatabase.LoadAssetAtPath(kSoPath); bool made = false; if (so == null) { so = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(so, kSoPath); made = true; } // 발주서 §1 기본값 — 권장 기본 = levels 6 · dither 0.6 · 4×4 · 보정 전부 무보정 so.enabled_ = true; so.verboseLog = false; so.minCameraMode = 1; so.requirePixelRig = true; so.mode = WLPaletteMode.ChannelLevels; so.levels = 6; so.paletteTex = null; so.paletteCount = 0; so.ditherStrength = 0.6f; so.dither8x8 = false; so.saturation = 1f; so.contrast = 1f; so.tintColor = Color.white; so.tintStrength = 0f; so.afterPostProcessing = true; so.quantizeInGammaSpace = true; EditorUtility.SetDirty(so); AssetDatabase.SaveAssetIfDirty(so); AssetDatabase.ImportAsset(kSoPath, ImportAssetOptions.ForceUpdate); WLPaletteSettings.ClearCache(); var re = WLPaletteSettings.Instance; Chk("S SO 로드", re != null, (made ? "새로 만듦 " : "기존 갱신 ") + kSoPath); Chk("S Resources 경로", re != null, "Resources.Load(\"" + WLPaletteSettings.ResourcesPath + "\") = " + (re != null ? re.name : "null")); if (re != null) L(" 값: enabled " + re.enabled_ + " · minCameraMode " + re.minCameraMode + " · levels " + re.levels + " · dither " + re.ditherStrength + " (" + (re.dither8x8 ? "8x8" : "4x4") + ")" + " · sat " + re.saturation + " · contrast " + re.contrast + " · tint " + re.tintStrength + " · afterPost " + re.afterPostProcessing + " · gamma " + re.quantizeInGammaSpace); } catch (Exception ex) { s_fail++; L("EXCEPTION " + ex); } L(""); L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " · " + s_pass + "/" + (s_pass + s_fail) + " · FAIL " + s_fail); Write("AgentScripts/WL814j_SETUP.txt"); } // ═════════════════════════════════════════════════════════════════════════ // ② 렌더러 피처 추가 (🔴 URP 에셋 디스크 수정은 이 1건만) // ═════════════════════════════════════════════════════════════════════════ public static void AddFeature() { s_sb = new StringBuilder(); s_pass = 0; s_fail = 0; L("WL-814j 팔레트 렌더러 피처 추가 · " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); try { var urp = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset; L(" 활성 URP 에셋 = " + (urp != null ? AssetDatabase.GetAssetPath(urp) : "(없음)")); var rd = AssetDatabase.LoadAssetAtPath(kRendererPath); Chk("F 렌더러 에셋", rd != null, kRendererPath); if (rd == null) return; L(" 추가 전 피처 " + rd.rendererFeatures.Count + "개:"); foreach (var f in rd.rendererFeatures) L(" - " + (f == null ? "(null)" : f.name + " [" + f.GetType().Name + "] active=" + f.isActive)); var type = typeof(PaletteDitherFeature); foreach (var f in rd.rendererFeatures) if (f != null && f.GetType() == type) { Chk("F 이미 있음(추가 0)", true, f.name + " active=" + f.isActive); Write("AgentScripts/WL814j_FEATURE.txt"); return; } var feature = (ScriptableRendererFeature)ScriptableObject.CreateInstance(type); feature.name = "WLPaletteDither"; feature.hideFlags = HideFlags.HideInHierarchy; feature.SetActive(false); // 🔴 디스크 기본값 = off → 타이틀/로비 비용 0 · 런타임이 도트 모드에서만 켠다 AssetDatabase.AddObjectToAsset(feature, rd); var so = new SerializedObject(rd); var feats = so.FindProperty("m_RendererFeatures"); var map = so.FindProperty("m_RendererFeatureMap"); feats.arraySize++; feats.GetArrayElementAtIndex(feats.arraySize - 1).objectReferenceValue = feature; // m_RendererFeatureMap = 각 피처 localFileId 를 8바이트 리틀엔디언으로 이어붙인 hex 문자열 long id; string guid; AssetDatabase.TryGetGUIDAndLocalFileIdentifier(feature, out guid, out id); map.stringValue = map.stringValue + Hex8(id); so.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(rd); AssetDatabase.SaveAssets(); AssetDatabase.ImportAsset(kRendererPath, ImportAssetOptions.ForceUpdate); var reload = AssetDatabase.LoadAssetAtPath(kRendererPath); int n = 0; foreach (var f in reload.rendererFeatures) if (f != null && f.GetType() == type) n++; Chk("F 추가 확인", n == 1, "피처 " + reload.rendererFeatures.Count + "개 · WLPaletteDither " + n + "개 · map=" + new SerializedObject(reload).FindProperty("m_RendererFeatureMap").stringValue); foreach (var f in reload.rendererFeatures) L(" - " + (f == null ? "(null)" : f.name + " [" + f.GetType().Name + "] active=" + f.isActive)); foreach (var f in reload.rendererFeatures) if (f != null && f.GetType() == type) Chk("F 디스크 기본 off", !f.isActive, "m_Active=" + (f.isActive ? 1 : 0)); } catch (Exception ex) { s_fail++; L("EXCEPTION " + ex); } L(""); L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " · " + s_pass + "/" + (s_pass + s_fail) + " · FAIL " + s_fail); Write("AgentScripts/WL814j_FEATURE.txt"); } // ═════════════════════════════════════════════════════════════════════════ // ③ 손으로 넣은 YAML 을 다시 읽어 확인 (🔴 SaveAssets 0 — 저장하면 Unity 가 m_AssetVersion 2→3 으로 올린다) // ═════════════════════════════════════════════════════════════════════════ public static void Reimport() { s_sb = new StringBuilder(); s_pass = 0; s_fail = 0; L("WL-814j 렌더러 에셋 재임포트 확인 · " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); try { AssetDatabase.ImportAsset(kRendererPath, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport); var rd = AssetDatabase.LoadAssetAtPath(kRendererPath); Chk("R 로드", rd != null, kRendererPath); if (rd == null) return; int pal = 0, nulls = 0; for (int i = 0; i < rd.rendererFeatures.Count; i++) { var f = rd.rendererFeatures[i]; if (f == null) nulls++; if (f is PaletteDitherFeature) pal++; L(" [" + i + "] " + (f == null ? "(null)" : f.name + " [" + f.GetType().Name + "] active=" + f.isActive)); } Chk("R 피처 3건", rd.rendererFeatures.Count == 3, rd.rendererFeatures.Count + "건"); Chk("R null 0", nulls == 0, nulls + "건"); Chk("R 팔레트 1건 · 디스크 off", pal == 1, "WLPaletteDither " + pal + "개"); var so = new SerializedObject(rd); var mp = so.FindProperty("m_RendererFeatureMap"); Chk("R 피처맵 길이 = 피처 수", mp != null && mp.arraySize == rd.rendererFeatures.Count, (mp != null ? mp.arraySize.ToString() : "?") + " / " + rd.rendererFeatures.Count); if (mp != null) for (int i = 0; i < mp.arraySize; i++) { long id = 0; string guid; if (rd.rendererFeatures[i] != null) AssetDatabase.TryGetGUIDAndLocalFileIdentifier(rd.rendererFeatures[i], out guid, out id); Chk("R 맵[" + i + "]", mp.GetArrayElementAtIndex(i).longValue == id, "map " + mp.GetArrayElementAtIndex(i).longValue + " vs localId " + id); } L(" 🔴 AssetDatabase.SaveAssets 호출 0 — 디스크 m_AssetVersion 은 2 그대로."); } catch (Exception ex) { s_fail++; L("EXCEPTION " + ex); } L(""); L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " · " + s_pass + "/" + (s_pass + s_fail) + " · FAIL " + s_fail); Write("AgentScripts/WL814j_REIMPORT.txt"); } static string Hex8(long id) { var b = BitConverter.GetBytes(id); var sb = new StringBuilder(16); for (int i = 0; i < 8; i++) sb.Append(b[i].ToString("x2")); return sb.ToString(); } }