// ───────────────────────────────────────────────────────────────────────────── // WLPixelCameraSettings.cs — Critter 3D Pixel Camera 리그 값의 SOT (C45) // // PD 지시 #814(「도트 게임처럼」) · 발주서 WL-814b §1-3 (2026-09-10) // // 값의 SOT = Assets/WL/Combat/Settings/Resources/WL/WLPixelCameraSettings.asset // → Resources.Load("WL/WLPixelCameraSettings") 1회 로드 후 캐시(기존 WL 설정 SO 관례). // // 🔴 C8 — enabled = 0 (또는 RuntimeDisabled = true) 이면 리그가 아예 조립되지 않고, // 이미 조립돼 있으면 다음 틱에 원본 카메라 값이 100 % 복원된다(원본 동작과 동일). // // 🔴 원본 스크립트 0 · 원본 프리팹/씬 디스크 수정 0. 이 SO 와 PixelCameraRig.cs 만이 WL 신규 파일이다. // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.PixelArt { /// Followed 프록시를 어떻게 원본 카메라에 붙일지. public enum PixelProxyMode { /// 원본 Main Camera 의 자식(localPos 0 · localRot identity). 지연 0 · 프레임당 비용 0 · GC 0. 권장. ChildOfCamera = 0, /// 독립 오브젝트 + 미러 스크립트([DefaultExecutionOrder(-100)] LateUpdate). 1프레임 지연. MirrorScript = 1, } /// Critter 픽셀 카메라 리그 값. 전부 여기서만 바꾼다(코드 상수 0). [CreateAssetMenu(fileName = "WLPixelCameraSettings", menuName = "WL/Pixel Camera Settings", order = 814)] public sealed class WLPixelCameraSettings : ScriptableObject { public const string ResourcesPath = "WL/WLPixelCameraSettings"; static WLPixelCameraSettings s_cached; static bool s_lookupDone; /// Resources 에서 1회 로드 후 캐시. 없으면 null(= 기능 off). public static WLPixelCameraSettings Instance { get { if (!s_lookupDone) { s_lookupDone = true; s_cached = Resources.Load(ResourcesPath); } return s_cached; } } /// 진단·A/B 전용 런타임 스위치(WLHitFeel.RuntimeDisabled 선례). 에셋을 안 건드리고 이 기능만 끈다. public static bool RuntimeDisabled; /// 이 기능이 살아 있는가(에셋 존재 + enabled + !RuntimeDisabled). 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 이면 조립/복원 로그를 1줄씩 남긴다. LogError 는 쓰지 않는다(813z).")] public bool verboseLog = false; // ───────────────────────────────────────── 조립 경계 [Header("조립 경계 (811q EffectWarmup / 811o CameraFramingOverride 와 같은 판정)")] [Tooltip("맵 진입 경계(메인 PC 교체 또는 맵 ID 변경)에서 자동 조립한다. 0 이면 프로브만 조립할 수 있다.")] public bool assembleOnMapEnter = true; [Tooltip("로비에서도 조립할지. 기본 0 = 인게임 맵에서만.")] public bool assembleInLobby = false; [Tooltip("경계 폴링 주기(초).")] public float pollSeconds = 0.25f; // ───────────────────────────────────────── 리그 원본 [Header("Critter 리그")] [Tooltip("Assets/3DPixelCamera/URP/PixelCameraSystem.prefab (URP.unitypackage 해제로 생긴 프리팹).")] public GameObject pixelCameraSystemPrefab; [Tooltip("리그 전용 레이어 인덱스. 게임 카메라는 이 레이어를 빼고, 뷰 카메라는 이 레이어만 그린다. 프로젝트 미명명 레이어를 쓴다(TagManager 수정 0).")] [Range(0, 31)] public int rigLayer = 31; [Tooltip("프록시 방식. ChildOfCamera = 원본 카메라의 자식(지연 0 · 권장).")] public PixelProxyMode proxyMode = PixelProxyMode.ChildOfCamera; // ───────────────────────────────────────── 투영·해상도 [Header("직교 투영 · 픽셀 해상도")] [Tooltip("원본 Main Camera 에 적용할 orthographicSize(= 화면 세로 절반의 월드 단위). " + "기본 3.8 = 원근 fov 60 · 카메라 거리 6.58 m 의 반높이(tan30° × 6.58).")] [Range(1f, 12f)] public float orthoSize = 3.8f; [Tooltip("저해상도 RT 의 세로 픽셀 수(SetHeight). 가로는 화면 비율로 자동(9:16 · 480 → 270×480).")] [Range(60, 1080)] public int pixelHeight = 480; [Tooltip("Critter 뷰 카메라가 캔버스 가장자리를 넘지 않으려고 (1 − 2/pixelHeight) 만큼 줌아웃하는 것을 보정한다. " + "1 이면 GameCameraZoom = orthoSize / (1 − 2/pixelHeight) 로 올려 화면 매핑을 orthoSize 와 정확히 맞춘다.")] public bool compensateViewZoom = true; // ───────────────────────────────────────── Critter 값 [Header("Critter PixelCameraManager 값")] [Tooltip("픽셀 격자(voxel grid) 스냅. 정지 물체의 픽셀이 떨리지 않게 한다.")] public bool voxelGridMovement = true; [Tooltip("서브픽셀 보정. 격자 스냅의 뚝뚝 끊김을 뷰 카메라 오프셋으로 되돌린다.")] public bool subpixelAdjustments = true; [Tooltip("followed 트랜스폼의 회전도 따라간다.")] public bool followRotation = true; [Tooltip("Critter 가 게임 카메라의 orthographicSize 를 직접 제어한다.")] public bool controlGameZoom = true; [Tooltip("뷰 카메라 줌(−1~1). 1 = 캔버스를 화면에 꽉 채운다.")] [Range(-1f, 1f)] public float viewCameraZoom = 1f; [Tooltip("뷰 카메라를 원본 Main Camera 의 URP 카메라 스택에 Overlay 로 끼운다(권장). " + "0 이면 Base 로 두고 depth 를 원본보다 1 높인다.")] public bool viewCameraInMainStack = true; // ───────────────────────────────────────── 회전 중 스냅 해제 [Header("회전 중 스냅 해제 (Critter 문서 권고)")] [Tooltip("카메라가 빠르게 돌 때 격자 스냅·서브픽셀 보정을 잠시 끈다(811o 구도 전환 · 보스 연출).")] public bool disableSnapWhileRotating = true; [Tooltip("이 각속도(도/초)를 넘으면 회전 중으로 본다.")] public float rotationSnapOffDegPerSec = 30f; [Tooltip("회전이 멎고 이 시간(초)이 지나면 스냅을 다시 켠다.")] public float rotationSnapResumeSeconds = 0.25f; // ───────────────────────────────────────── 원본 카메라 처리 [Header("원본 Main Camera 처리 (MainCamera 태그·RealCamera·AudioListener 전부 유지)")] [Tooltip("원본 카메라가 그릴 레이어. 기본 0 = 월드를 그리지 않는다(인게임 UI 캔버스는 UICamera Overlay 가 그린다).")] public int mainCameraCullingMask = 0; [Tooltip("원본 카메라의 포스트프로세싱을 끈다(픽셀 결과를 다시 후처리하지 않게). 게임 카메라 쪽에서 처리한다.")] public bool mainCameraPostProcessingOff = true; [Tooltip("원본 카메라의 포스트프로세싱 설정을 저해상도 게임 카메라로 복사한다(블룸 등이 픽셀 해상도로 들어간다).")] public bool copyPostProcessingToGameCamera = true; [Tooltip("원본 카메라의 배경을 단색 검정으로 바꾼다(스카이박스 클리어 비용 제거). 복원 시 원값으로 되돌린다.")] public bool mainCameraSolidBlack = true; } }