// ───────────────────────────────────────────────────────────────────────────── // WLLookModeSettings.cs — 인게임 룩 토글(카메라 모드 · 배경 룩)의 값 단일 출처 (WL-814d · #814) // // PD 제안(2026-09-10) 「토글 형태로 카메라를 바꿔서 볼 수 있게」 · 발주서 WL-814d §1. // // ■ 왜 이 에셋인가 (C45) // 프리셋 값(투영 · 궤도 각 · 거리 · 직교 반높이 · 픽셀 높이)과 버튼 좌표·문구를 코드 상수로 두지 않는다. // 게임이 읽는 값은 `Resources/WL/WLLookModeSettings.asset` 이다(PD 가 인스펙터에서 바꾼다). // 🔴 여기 있는 값은 **비교용 프리셋**이다. 확정 값은 814b `WLPixelCameraSettings`(직교·픽셀)와 // 811o `WLCameraFramingSettings`(구도)가 계속 소유한다 — 이 파일은 그 둘에 **런타임으로 얹기만** 한다. // // ■ 좌표계 (px) // 813c/813q 와 같은 **세로 1080×1920 기준 화면 픽셀**. px → 캔버스 유닛 환산은 // `WLHudLayoutSettings.UnitsPerPx(canvas)` 하나만 쓴다(산식 중복 0). // // ■ C8 롤백 // `enabled_ = 0` → 버튼 0 · 모드 0 고정 · 814b/811o 신규 API 호출 0 = 두 기능 원래 동작 100 %. // // 🔴 어셈블리 주의: Assets/WL/Look/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.Look.Toggle { /// 룩 모드 하나 = 카메라 프리셋 1줄(발주서 §1-1 표). [System.Serializable] public sealed class WLLookModePreset { [Tooltip("버튼에 찍히는 이름(예: 기본 · 도트A).")] public string name = "기본"; [Tooltip("켜면 814b 픽셀 카메라 리그(직교 · 저해상도 RT)를 강제로 조립한다. 끄면 원근(리그 해제).")] public bool pixelRig = false; [Tooltip("켜면 아래 구도 3값을 쓰지 않고 811o WLCameraFramingSettings 의 SO 값을 그대로 쓴다(= 지금 구도). 0번 기본 모드용.")] public bool useSoFraming = false; [Tooltip("811o 궤도 파라미터 cameraHeight(도). 클수록 위에서 내려다본다.")] public float cameraHeight = 74f; [Tooltip("811o 궤도 거리(m) — min/max 에 같이 들어간다.")] public float distance = 3.4f; [Tooltip("811o 궤도 중심 높이 DistanceUp(m).")] public float distanceUp = 2.2f; [Tooltip("814b 직교 반높이 orthoSize(m). pixelRig 가 꺼져 있으면 쓰이지 않는다.")] public float orthoSize = 3.8f; [Tooltip("814b 저해상도 RT 세로 픽셀 수. pixelRig 가 꺼져 있으면 쓰이지 않는다.")] public int pixelHeight = 480; [Tooltip("envFollowsCameraMode 가 켜져 있을 때 이 모드에서 배경 룩(814c)을 켤지.")] public bool envLookOn = false; } [CreateAssetMenu(fileName = "WLLookModeSettings", menuName = "WL/Look Mode Settings", order = 35)] public sealed class WLLookModeSettings : ScriptableObject { /// Resources 경로 — 런타임 로드 키. public const string ResourcesPath = "WL/WLLookModeSettings"; private static WLLookModeSettings s_instance; private static bool s_tried; /// 에셋이 없으면 null(호출부는 null 이면 전부 건너뛴다 = C8 롤백). public static WLLookModeSettings Instance { get { if (s_instance == null && !s_tried) { s_tried = true; s_instance = Resources.Load(ResourcesPath); } return s_instance; } } /// 에디터에서 에셋을 새로 만들거나 값을 바꾼 뒤 캐시를 버린다. public static void ClearCache() { s_instance = null; s_tried = false; } /// 프로브·QA 용 런타임 스위치(에셋을 건드리지 않고 끈다). public static bool RuntimeDisabled; /// 룩 토글 축이 살아 있는가 = 에셋 존재 · enabled_ · 런타임 스위치 off. public static bool Enabled { get { if (RuntimeDisabled) return false; var s = Instance; return s != null && s.enabled_; } } // ── 공통 ────────────────────────────────────────────────────────────── [Header("공통 (C8 롤백)")] [Tooltip("끄면 버튼 0 · 모드 0 고정 · 814b/811o 신규 API 호출 0 = 두 기능 원래 동작 100 %.")] public bool enabled_ = true; [Tooltip("켜면 모드 전환마다 Console 에 1줄 남긴다(문자열은 이 값으로 막고 만든다 · GC 주의).")] public bool verboseLog = false; // ── 모드 ────────────────────────────────────────────────────────────── [Header("카메라 모드 (0 = 기본 원근 · 1~3 = 도트 프리셋)")] [Tooltip("앱을 켰을 때 시작 모드. 0 = 지금과 같은 원근.")] public int startMode = 0; [Tooltip("맵을 다시 들어가도 같은 모드를 유지한다(앱 세션 동안).")] public bool keepModeAcrossMaps = true; [Tooltip("로비·타이틀에서는 카메라를 기본(0)으로 되돌리고, 인게임에 다시 들어가면 고른 모드를 복원한다.")] public bool defaultInLobby = true; [Tooltip("도트 모드로 바뀔 때 배경 룩(814c)을 프리셋의 envLookOn 값으로 함께 바꾼다.")] public bool envFollowsCameraMode = true; [Tooltip("배경 룩(814c) 시작 상태.")] public bool envLookOnAtStart = false; [Tooltip("모드 배열 — 버튼이 0 → 1 → 2 → … → 0 으로 순환한다. 0번은 기본(원근)이어야 한다.")] public WLLookModePreset[] presets = new WLLookModePreset[] { // 0번은 「지금 화면 그대로」 = 811o SO 구도 · 리그 해제(useSoFraming 1 이라 아래 3값은 참고용). new WLLookModePreset { name = "기본", pixelRig = false, useSoFraming = true, cameraHeight = 76f, distance = 3.0f, distanceUp = 2.4f, orthoSize = 3.8f, pixelHeight = 480, envLookOn = false }, new WLLookModePreset { name = "도트A", pixelRig = true, useSoFraming = false, cameraHeight = 74f, distance = 3.4f, distanceUp = 2.2f, orthoSize = 3.8f, pixelHeight = 480, envLookOn = true }, new WLLookModePreset { name = "도트B", pixelRig = true, useSoFraming = false, cameraHeight = 55f, distance = 4.5f, distanceUp = 1.8f, orthoSize = 4.2f, pixelHeight = 480, envLookOn = true }, new WLLookModePreset { name = "도트C", pixelRig = true, useSoFraming = false, cameraHeight = 45f, distance = 5.5f, distanceUp = 1.5f, orthoSize = 4.6f, pixelHeight = 480, envLookOn = true }, }; // ── HUD 버튼 ────────────────────────────────────────────────────────── [Header("HUD 버튼 (813q RunHud 방식 · 프리팹 0 · Safe Area 안)")] [Tooltip("끄면 버튼 노드를 만들지 않는다(시연이 끝나면 여기만 끈다 — 모드 전환은 코드/프로브로 계속 가능).")] public bool showButtons = true; [Tooltip("버튼을 붙일 부모 노드의 UI 루트 기준 경로(813y·813q 실측 = IngameUIs/WL_HUD · SafeAreaFitter 아래).")] public string buttonParentPath = "IngameUIs/WL_HUD"; [Tooltip("전용 Canvas(overrideSorting)로 띄운다 — 813q 결과창(40)·813z 부활 팝업(60)보다 아래.")] public bool ownCanvas = true; [Tooltip("전용 Canvas 의 sortingOrder. 결과창 40 · 부활 60 보다 낮게 둔다.")] public int sortingOrder = 10; [Tooltip("Safe Area 상단에서 버튼 위쪽까지 여백(px). 813c 보스 바(190~256)·813q 런 HUD(272~394)·813tj 보스 배너(300~450) 아래.")] public float topMarginPx = 470f; [Tooltip("Safe Area 오른쪽에서 버튼 오른쪽 끝까지 여백(px).")] public float rightMarginPx = 24f; [Tooltip("버튼 하나의 크기(px · 발주서 하한 96×48). 폭 200 이면 런 HUD 폭 560(중앙 ±280) 오른쪽으로 36 px 비켜 있다.")] public Vector2 buttonSizePx = new Vector2(200f, 64f); [Tooltip("두 버튼 사이 세로 간격(px).")] public float buttonGapPx = 12f; [Tooltip("버튼 글자 크기(px).")] public float buttonFontPx = 26f; [Tooltip("버튼 배경색(꺼짐 상태).")] public Color buttonBgColor = new Color(0.06f, 0.07f, 0.09f, 0.82f); [Tooltip("버튼 배경색(켜짐 상태 = 도트 모드 · 배경 룩 ON).")] public Color buttonBgOnColor = new Color(0.16f, 0.42f, 0.86f, 0.9f); [Tooltip("버튼 글자색.")] public Color buttonTextColor = new Color(0.95f, 0.96f, 0.98f, 1f); [Tooltip("카메라 버튼 문구 — {0} 에 프리셋 이름이 들어간다.")] public string cameraLabelFormat = "카메라: {0}"; [Tooltip("배경 버튼 문구 — {0} 에 아래 두 문구가 들어간다.")] public string envLabelFormat = "배경: {0}"; [Tooltip("배경 룩 켜짐 문구.")] public string envOnText = "ON"; [Tooltip("배경 룩 꺼짐 문구.")] public string envOffText = "OFF"; [Tooltip("버튼 글자 테두리(813y WLTextFxUtil.ApplyEdge 재사용).")] public WL.UI.WLTextEdge buttonEdge = new WL.UI.WLTextEdge { enabled = true, outlineWidth = 0.24f, outlineColor = new Color(0.05f, 0.03f, 0.02f, 1f), shadowOffset = new Vector2(0.4f, -0.4f), shadowColor = new Color(0f, 0f, 0f, 0.75f), shadowSoftness = 0.18f, faceDilate = 0.06f, }; // ── 조회 ────────────────────────────────────────────────────────────── /// 모드 개수(배열이 비어 있으면 1 = 기본만). public int ModeCount { get { return presets != null && presets.Length > 0 ? presets.Length : 1; } } /// 모드 번호를 배열 범위로 감싼다(0 → 1 → … → 0 순환). public int Wrap(int mode) { int n = ModeCount; int m = mode % n; return m < 0 ? m + n : m; } /// 모드 프리셋(없으면 null). public WLLookModePreset Preset(int mode) { if (presets == null || presets.Length == 0) return null; return presets[Wrap(mode)]; } /// 모드 이름(없으면 번호). public string ModeName(int mode) { var p = Preset(mode); return p != null && !string.IsNullOrEmpty(p.name) ? p.name : mode.ToString(); } } }