181 lines
12 KiB
C#
181 lines
12 KiB
C#
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
// WLCameraFramingSettings.cs — 카메라 가림 해소(구도 값 + 가림 소품 숨김) 값의 단일 출처(SOT · C45)
|
|||
|
|
//
|
|||
|
|
// PD 지시 #813 · 발주서 WL-811o (2026-09-09) · 근거 = qa/W2-4b_재채점_v2.md D-8 · 채점 7-3 = 0
|
|||
|
|
// 「지형 바위가 PC 를 완전히 가림(01_enter_hud ≈40 %) · 대형 이펙트가 ≈70 %(PD_07)」
|
|||
|
|
//
|
|||
|
|
// 에셋 경로(고정): Assets/WL/Camera/Resources/WL/WLCameraFramingSettings.asset
|
|||
|
|
// → Resources.Load<WLCameraFramingSettings>("WL/WLCameraFramingSettings") 로 1회 로드 후 캐시(기존 설정 SO 관례).
|
|||
|
|
// 에셋이 없거나 enabled = false 면 러너 자체가 뜨지 않는다 = 원본 카메라·소품 100 %(C8).
|
|||
|
|
//
|
|||
|
|
// ■ 왜 새 SO 인가 (실측 · 발주서 §1-1 조건)
|
|||
|
|
// 구도 값(거리·궤도 높이·피치 파라미터)은 **원본 프리팹의 arr_cCamData 에만** 있다:
|
|||
|
|
// Assets/ResWork/Prefabs/Ingame/Main Camera.prefab
|
|||
|
|
// arr_cCamData[0](일반) minDistance 3 · maxDistance 3 · DistanceUp 2 · cameraHeight 65
|
|||
|
|
// arr_cCamData[1](보스/락타겟) minDistance 2 · maxDistance 3 · DistanceUp 3.5 · cameraHeight 78
|
|||
|
|
// m_LookAt (0, 1.06, 0) · smooth 4 · FOV 60(세로) · rotateAround = MapZoneData.CamRot_Portal(WL_Nature = 233)
|
|||
|
|
// 기존 WL 카메라 SO 2개에는 이 값이 **없다**:
|
|||
|
|
// · CameraLeadSettings = 이동 리드/후진 피치 **가산분**만(기준 구도 값 없음)
|
|||
|
|
// · WLCombatCameraSettings = 킬캠 배율 + arr_cCamData[**1**] 보스 프리셋만(일반 프리셋 [0] 은 안 건드린다)
|
|||
|
|
// → 발주서 §1-1 의 「WL 설정이 없으면 WL 오버라이드 SO(NEW)」 경로. 프리팹은 수정하지 않는다.
|
|||
|
|
//
|
|||
|
|
// ■ 궤도 수식(RealCamera.Update_Cam 실측) — 이 값들이 화면에서 뭘 바꾸는지
|
|||
|
|
// rotateVector = Quaternion.Euler(h, r, 0) * Vector3.one (|rotateVector| = √3 · h 와 무관)
|
|||
|
|
// 카메라 = 타겟 + up*DistanceUp − rotateVector*DistanceAway
|
|||
|
|
// · 타겟 발밑 기준 높이 = DistanceUp + D*(sin h − cos h)
|
|||
|
|
// · 수평 거리 = D*√(1 + (sin h + cos h)²)
|
|||
|
|
// · 주시점(0,1.06,0) 내림각 = atan((높이 − 1.06) / 수평거리)
|
|||
|
|
// 실측 h = 65 → 높이 3.451 m · 수평 4.990 m · **내림각 25.6°** ← 지금(가림 발생)
|
|||
|
|
// 참고 h = 78 → 높이 4.311 m · 수평 4.654 m · 내림각 34.9° ← 원본 커밋 d5df1b2c9 「카메라 수정」 이전 값
|
|||
|
|
// 내림각이 낮을수록 카메라~PC 선분이 지면에 가깝게 깔려 **키 2~3 m 짜리 바위/덤불이 PC 를 덮는다.**
|
|||
|
|
// 선분 높이 y(x) = 1.06 + tan(내림각)·x (x = PC 로부터 카메라 쪽 수평거리)
|
|||
|
|
// h 65 → y(2.5 m) = 2.26 m (3 m 바위가 전 구간에서 가린다)
|
|||
|
|
// h 74 → y(2.5 m) = 2.66 m
|
|||
|
|
//
|
|||
|
|
// 🔴 어셈블리 주의: Assets/WL/Camera/ 에 .asmdef 를 만들지 말 것(RealCamera 가 Assembly-CSharp 에 있다).
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
namespace WL.CameraFraming
|
|||
|
|
{
|
|||
|
|
/// <summary>가림 소품을 숨기는 방식.</summary>
|
|||
|
|
public enum OccluderHideMode
|
|||
|
|
{
|
|||
|
|
/// <summary>머티리얼 큐를 보고 자동 선택(투명 큐 + 색 프로퍼티 → 알파 페이드 · 그 외 → 그림자만).</summary>
|
|||
|
|
Auto = 0,
|
|||
|
|
/// <summary>ShadowCastingMode.ShadowsOnly — 렌더러는 안 보이고 그림자는 남는다(불투명 셰이더 기본 경로 · 머티리얼 무변경).</summary>
|
|||
|
|
ShadowsOnly = 1,
|
|||
|
|
/// <summary>Renderer.enabled = false — 그림자까지 사라진다.</summary>
|
|||
|
|
RendererOff = 2,
|
|||
|
|
/// <summary>MaterialPropertyBlock 알파 페이드 — 셰이더가 투명 큐일 때만 실제로 보인다(새 셰이더·머티리얼 0).</summary>
|
|||
|
|
MpbAlpha = 3,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[CreateAssetMenu(fileName = "WLCameraFramingSettings", menuName = "WL/Camera Framing Settings", order = 322)]
|
|||
|
|
public sealed class WLCameraFramingSettings : ScriptableObject
|
|||
|
|
{
|
|||
|
|
public const string ResourcesPath = "WL/WLCameraFramingSettings";
|
|||
|
|
|
|||
|
|
static WLCameraFramingSettings s_cached;
|
|||
|
|
static bool s_lookupDone;
|
|||
|
|
|
|||
|
|
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null.</summary>
|
|||
|
|
public static WLCameraFramingSettings Instance
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
if (!s_lookupDone)
|
|||
|
|
{
|
|||
|
|
s_cached = Resources.Load<WLCameraFramingSettings>(ResourcesPath);
|
|||
|
|
s_lookupDone = true;
|
|||
|
|
}
|
|||
|
|
return s_cached;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>진단·A/B 전용 런타임 스위치(WLHitFeel.RuntimeDisabled 선례). 에셋을 안 건드리고 이 기능만 끈다.</summary>
|
|||
|
|
public static bool RuntimeDisabled;
|
|||
|
|
|
|||
|
|
/// <summary>기능이 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off.</summary>
|
|||
|
|
public static bool Enabled
|
|||
|
|
{
|
|||
|
|
get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다.</summary>
|
|||
|
|
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── 전체 스위치
|
|||
|
|
[Header("전체 스위치")]
|
|||
|
|
[Tooltip("끄면 구도 오버라이드·가림 소품 숨김이 전부 비활성 — 원본 카메라 값(arr_cCamData) · 소품 100 %. 롤백 1순위.")]
|
|||
|
|
public bool enabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("적용/복원/숨김 결정을 Console 에 남긴다(검증용 · 기본 off · GC 발생).")]
|
|||
|
|
public bool verboseLog = false;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ① 구도 값 오버라이드
|
|||
|
|
[Header("① 구도 오버라이드 — 맵 진입 경계에서 1회 적용(원값 스냅샷 후 복원 가능)")]
|
|||
|
|
[Tooltip("구도 오버라이드 사용 여부. off 면 arr_cCamData 원값(65° · 3 m · 2)을 그대로 쓴다.")]
|
|||
|
|
public bool framingEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("arr_cCamData[0](일반 프리셋)의 cameraHeight 로 쓸 궤도 파라미터(도).\n" +
|
|||
|
|
"실제 고도각 = asin((sin h − cos h)/√3) · 실측 65 = 16.2° · 74 = 22.6° · 78 = 26.4°.\n" +
|
|||
|
|
"올릴수록 더 내려다봐서 지형 소품 위로 시선이 지나간다(= 가림 감소). 너무 올리면 탑뷰가 되어 액션 체감이 떨어진다.")]
|
|||
|
|
[Range(50f, 85f)] public float cameraHeight = 74f;
|
|||
|
|
|
|||
|
|
[Tooltip("arr_cCamData[0].minDistance = maxDistance 로 쓸 궤도 거리(m · 실측 원값 3).\n" +
|
|||
|
|
"소폭만 늘린다 — 거리를 늘리면 카메라~PC 선분이 길어져 그 사이에 들어올 수 있는 소품 수도 늘어난다.")]
|
|||
|
|
[Range(2f, 6f)] public float distance = 3.4f;
|
|||
|
|
|
|||
|
|
[Tooltip("arr_cCamData[0].DistanceUp — 궤도 중심을 발밑에서 띄우는 높이(m · 실측 원값 2). 음수면 원값을 유지한다.")]
|
|||
|
|
public float distanceUp = 2.2f;
|
|||
|
|
|
|||
|
|
[Tooltip("맵 진입 경계 검사 주기(초 · unscaled).")]
|
|||
|
|
public float framingPollSeconds = 0.25f;
|
|||
|
|
|
|||
|
|
[Tooltip("로비에서도 적용한다. 기본 off — 로비는 전투가 없어 가림이 문제되지 않고, 연출 구도를 건드리지 않는다.")]
|
|||
|
|
public bool framingInLobby = false;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ② 가림 소품 자동 숨김
|
|||
|
|
[Header("② 가림 소품 자동 숨김 — 카메라→PC SphereCast")]
|
|||
|
|
[Tooltip("가림 소품 숨김 사용 여부.")]
|
|||
|
|
public bool occluderFadeEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("카메라→PC 선분 검사 반경(m). PC 실루엣 폭 정도. 0 이면 Linecast.")]
|
|||
|
|
[Range(0f, 2f)] public float sphereRadius = 0.6f;
|
|||
|
|
|
|||
|
|
[Tooltip("검사 주기(초 · unscaled). 0.1 = 10 Hz.")]
|
|||
|
|
public float pollSeconds = 0.1f;
|
|||
|
|
|
|||
|
|
[Tooltip("숨김까지의 시간(초). 불투명 셰이더 경로(ShadowsOnly/RendererOff)에서는 **지연**으로 동작한다\n" +
|
|||
|
|
"— 스쳐 지나가는 소품이 한 프레임 깜빡이는 것을 막는다. MpbAlpha 경로에서는 알파 램프 시간.")]
|
|||
|
|
public float fadeOutSeconds = 0.2f;
|
|||
|
|
|
|||
|
|
[Tooltip("가림이 풀린 뒤 복귀까지 기다리는 시간(초).")]
|
|||
|
|
public float restoreDelaySeconds = 0.3f;
|
|||
|
|
|
|||
|
|
[Tooltip("동시에 숨길 수 있는 소품 수 상한. 넘으면 새 소품은 숨기지 않는다(맵이 통째로 사라지는 사고 방지).")]
|
|||
|
|
[Range(1, 8)] public int maxConcurrent = 4;
|
|||
|
|
|
|||
|
|
[Tooltip("숨김 방식. Auto = 머티리얼이 투명 큐면 알파 페이드, 아니면 그림자만 남기고 숨김.")]
|
|||
|
|
public OccluderHideMode hideMode = OccluderHideMode.Auto;
|
|||
|
|
|
|||
|
|
[Tooltip("검사 레이어 마스크. 실측 레이어: 0 Default · 3 Actor(PC·몹) · 4 Water · 5 UI · 6 Map · 8 GrassFX.\n" +
|
|||
|
|
"기본 = Default + Map (몹·PC·이펙트·UI 제외). 인스펙터에서 바꾼다.")]
|
|||
|
|
public LayerMask occluderMask = (1 << 0) | (1 << 6);
|
|||
|
|
|
|||
|
|
[Tooltip("이 문자열들이 이름에 들어간 오브젝트는 **숨기지 않는다**(바닥·지형·물). 소문자 비교 · 쉼표 구분.")]
|
|||
|
|
public string excludeNameContains = "terrain,ground,floor,water,river,navmesh,plane,road,path";
|
|||
|
|
|
|||
|
|
[Tooltip("렌더러 바운드 높이가 이 값(m) 미만이면 숨기지 않는다(잔디·자갈은 가리지 않는다).")]
|
|||
|
|
public float minOccluderHeight = 0.8f;
|
|||
|
|
|
|||
|
|
[Tooltip("렌더러 바운드 최대 변이 이 값(m)을 넘으면 숨기지 않는다(지형 통짜 메시·배경 산 보호).")]
|
|||
|
|
public float maxOccluderSize = 30f;
|
|||
|
|
|
|||
|
|
[Tooltip("SkinnedMeshRenderer(=캐릭터·몹)는 절대 숨기지 않는다. 안전장치라 켠 채로 둔다.")]
|
|||
|
|
public bool skipSkinnedRenderers = true;
|
|||
|
|
|
|||
|
|
[Tooltip("Actor 컴포넌트를 가진 오브젝트(PC·몹)는 절대 숨기지 않는다. 안전장치라 켠 채로 둔다.")]
|
|||
|
|
public bool skipActors = true;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ②-b 바운드 스캔 (콜라이더 없는 소품 대응)
|
|||
|
|
[Header("②-b 바운드 스캔 — 🔴 실측: 가림 후보 소품 979개 중 콜라이더 보유는 121개(12.4 %)")]
|
|||
|
|
[Tooltip("SphereCast 는 콜라이더가 있는 것만 잡는다. WL_Nature 의 나무·덤불·바위는 대부분 콜라이더가 없어\n" +
|
|||
|
|
"물리 검사만으로는 화면을 가리는 소품의 12 % 밖에 못 잡는다(프로브 A 절 실측).\n" +
|
|||
|
|
"켜면 맵 진입 때 한 번 훑어 둔 소품 렌더러의 **바운드**로도 가림을 판정한다(콜라이더 불요).")]
|
|||
|
|
public bool boundsScanEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("바운드 판정에 쓰는 선분 반경(m). PC 실루엣 폭 — sphereRadius 와 같은 뜻이지만 따로 조절할 수 있게 뒀다. 0 이면 sphereRadius 를 쓴다.")]
|
|||
|
|
public float boundsScanRadius = 0f;
|
|||
|
|
|
|||
|
|
[Tooltip("스캔 목록 재구축 주기(초). 0 = 맵/PC 경계에서만 재구축(정상 · 이때만 할당이 있고 폴링은 GC 0).\n" +
|
|||
|
|
"0 보다 크면 그 주기로도 다시 훑는다(런타임에 소품이 생기는 맵용).")]
|
|||
|
|
public float scanRebuildSeconds = 0f;
|
|||
|
|
|
|||
|
|
[Tooltip("스캔 목록 상한(맵 소품 수). WL_Nature 실측 가림 후보 979개.")]
|
|||
|
|
public int scanMax = 2048;
|
|||
|
|
}
|
|||
|
|
}
|