// ───────────────────────────────────────────────────────────────────────────── // WLPixelLookSettings.cs — 도트(픽셀아트) 룩 스위치 값의 단일 출처 (SOT · C45) // // PD 지시 #814(「Critter 3D Pixel Art Toolkit 으로 우리 게임을 도트 게임처럼 보이게」) // 발주서 WL-814a §1-2 (2026-09-10) // // ■ 왜 URP 기본 기능인가 (실측) // 임포트된 것은 `Assets/Critter 3D Pixel Art Toolkit/README.pdf` **1개뿐**이다 // (번들 3종 = Critter 3D Pixel Camera · Environment · Volumetric Lighting 은 Asset Store 에서 별도 다운로드). // → 에셋이 도착하기 전까지 URP 17.3 이 이미 가진 두 값만으로 「도트 룩」 1차 시안을 만든다: // UniversalRenderPipelineAsset.renderScale … 낮은 해상도로 그린다(= 픽셀이 커진다) // UniversalRenderPipelineAsset.upscalingFilter … Point(Nearest-Neighbor) 로 확대한다(= 뭉개지지 않는다) // 이 둘은 **파이프라인 에셋의 런타임 프로퍼티**라 코드에서 켜고 끌 수 있다(셰이더·머티리얼·포스트 0). // // ■ 🔴 절대 규칙 — 디스크에 쓰지 않는다 // 파이프라인 에셋은 프로젝트 에셋이다. 에디터에서 그 오브젝트의 값을 바꾸면 **메모리에 남고**, // PD 가 「Save Project」 를 누르면 `Assets/Settings/URP-*.asset` 에 그대로 기록된다. // 그래서 이 기능은 // · `EditorUtility.SetDirty` 를 **부르지 않는다**(호출 0) // · 맵 이탈 · `OnApplicationQuit` · 에디터 플레이모드 종료에서 **원래 값으로 되돌린다** // · 되돌릴 값(에셋 경로 + renderScale + filter)을 에디터 `SessionState` 에도 적어 둬서 // 도메인 리로드로 static 이 날아가도 다음 로드에서 복원한다(§PixelLook.EditorSafetyNet) // // ■ C8 롤백 // `enabled = 0` → 러너 오브젝트가 아예 뜨지 않고 파이프라인 에셋을 한 번도 건드리지 않는다(원본 100 %). // // 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLPixelLookSettings.asset // → Resources.Load("WL/WLPixelLookSettings") 1회 로드 후 캐시(기존 WL 설정 SO 관례). // // 🔴 어셈블리 주의: Assets/WL/Look/ 에 .asmdef 를 만들지 말 것(MyValue·InGameInfo 가 Assembly-CSharp 에 있다). // 🔴 이 파일은 Debug.LogError / Debug.LogException 을 쓰지 않는다(813z 규칙). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.Look { /// 낮은 해상도를 화면 크기로 확대할 때 쓰는 필터. URP UpscalingFilterSelection 으로 매핑된다. public enum PixelUpscaleFilter { /// Nearest-Neighbor — 픽셀이 네모로 남는다(도트 룩의 핵심). Point = 0, /// Bilinear — 뭉개진다(비교용). Linear = 1, /// URP 자동 선택(렌더 스케일·해상도에 따라 Point 또는 Linear). Auto = 2, /// AMD FSR 1.0(선명하게 복원 = 도트 룩과 반대 방향 · 비교용). FSR = 3, } [CreateAssetMenu(fileName = "WLPixelLookSettings", menuName = "WL/Pixel Look Settings", order = 324)] public sealed class WLPixelLookSettings : ScriptableObject { public const string ResourcesPath = "WL/WLPixelLookSettings"; static WLPixelLookSettings s_cached; static bool s_lookupDone; /// 설정 에셋(1회 로드 후 캐시). 없으면 null = 기능 전체 무동작. public static WLPixelLookSettings Instance { get { if (!s_lookupDone) { s_cached = Resources.Load(ResourcesPath); s_lookupDone = true; } return s_cached; } } /// 진단·A/B 전용 런타임 스위치(WLCameraFramingSettings.RuntimeDisabled 선례). public static bool RuntimeDisabled; /// 기능이 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off. public static bool Enabled { get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; } } /// 테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다. public static void ClearCache() { s_cached = null; s_lookupDone = false; } // ─────────────────────────────────────────── 전체 스위치 [Header("전체 스위치 (C8)")] [Tooltip("0 이면 러너 오브젝트도 뜨지 않고 파이프라인 에셋을 한 번도 건드리지 않는다 = 원본 100 %.")] public bool enabled = true; [Tooltip("적용/복원 1줄 로그. 프레임당 로그는 없다(경계에서만).")] public bool verboseLog = false; // ─────────────────────────────────────────── 픽셀 크기 [Header("픽셀 크기")] [Tooltip("세로로 몇 픽셀을 그릴 것인가. renderScale = targetHeightPx ÷ Screen.height.\n" + "1080×1920 기준: 1920=원본 · 640≈0.333 · 480=0.25 · 360≈0.1875")] [Range(64, 4096)] public int targetHeightPx = 480; [Tooltip("확대 필터. 도트 룩은 Point(Nearest-Neighbor).")] public PixelUpscaleFilter filter = PixelUpscaleFilter.Point; [Tooltip("renderScale 하한(URP 자체 하한도 0.1). 0.1 미만은 URP 가 어차피 0.1 로 잘라낸다.")] [Range(0.05f, 1f)] public float minRenderScale = 0.1f; [Tooltip("renderScale 상한. 1 이면 「원본보다 크게 그리는 것」을 막는다(=슈퍼샘플링 방지).")] [Range(0.5f, 3f)] public float maxRenderScale = 1f; // ─────────────────────────────────────────── 적용 범위 [Header("적용 범위 (인게임 한정)")] [Tooltip("1 이면 전투 맵(Stage/Dungeon)에서만 켠다. 로비·타이틀·농장은 선명하게 둔다.")] public bool applyOnlyInBattleMap = true; [Tooltip("1 이면 맵 이탈(타이틀/로비 복귀) · 종료 · 플레이모드 종료에서 원래 값으로 되돌린다. 🔴 0 으로 두지 말 것.")] public bool restoreOnExit = true; [Tooltip("맵 진입/이탈 경계를 보는 주기(초). 프레임당 비용 0(시간 비교 1회).")] [Range(0.05f, 2f)] public float pollSeconds = 0.25f; [Tooltip("1 이면 해상도가 바뀌었을 때(회전·창 크기) renderScale 을 다시 계산한다.")] public bool trackScreenSize = true; } }