using UnityEngine; using Unity.Cinemachine; using UnityRawInput; using UnityEngine.Rendering; using System; using System.Collections.Generic; [DefaultExecutionOrder(2)] public class CameraControlSystem : MonoBehaviour { [Header("FOV Physics Settings")] [SerializeField] private float fovForce = 400f; // 포스 강도 (고정값) - 2배 증가 [SerializeField] private float fovDamping = 6f; // 감쇠력 [SerializeField] private float fovMaxVelocity = 200f; // 최대 속도 - 2배 증가 [SerializeField] private float minFOV = 0.1f; [SerializeField] private float maxFOV = 60f; [Header("DOF Physics Settings")] [SerializeField] private float dofStep = 0.01f; // DOF Focal Length 증가/감소 단위 [SerializeField] private float minDOF = 0.01f; // Focal Length 최소값 [SerializeField] private float maxDOF = 1f; // Focal Length 최대값 [Header("Screenshot Settings")] [SerializeField] private string screenshotPath = "Screenshots"; [SerializeField] private int screenshotResolutionMultiplier = 1; private CameraManager cameraManager; private CinemachineCamera currentVirtualCamera; private CameraInfoUI cameraInfoUI; private GameObject cameraInfoUIGameObject; // UI GameObject 참조 저장 (정리용) // FOV 물리 시스템 변수들 private float fovVelocity = 0f; // 현재 FOV 변화 속도 private float targetFOV = 60f; // 목표 FOV private bool isApplyingForce = false; // 현재 포스가 적용 중인지 // DOF 값 변수 private float currentDOFValue = 0.3f; // 현재 DOF Focal Length 값 // Beautify Volume Override 참조 private object beautifyOverride; private bool isDOFEnabled = false; // DOF 활성화 상태 (기본값: 꺼짐) // F13/F14 제어 모드 전환 (true: FOV, false: DOF) private bool isFOVMode = true; // F18 더블클릭 감지용 private float lastF18ClickTime = 0f; private const float doubleClickThreshold = 0.3f; // DOF 타겟팅용 private Transform currentDOFTarget = null; private List availableDOFTargets = new List(); private int currentTargetIndex = 0; private void Awake() { // 스크린샷 폴더 생성 if (!System.IO.Directory.Exists(screenshotPath)) { System.IO.Directory.CreateDirectory(screenshotPath); } } private void Start() { cameraManager = FindObjectOfType(); if (cameraManager != null) { cameraManager.OnCameraChanged += OnCameraChanged; UpdateCurrentCamera(); } // CameraInfoUI 생성 (정리를 위해 참조 저장) cameraInfoUIGameObject = new GameObject("CameraInfoUI"); cameraInfoUI = cameraInfoUIGameObject.AddComponent(); // Beautify 컴포넌트 찾기 Debug.Log("[CameraControlSystem] Beautify 초기화 시작"); InitializeBeautify(); // DOF 타겟 찾기 FindDOFTargets(); InitializeRawInput(); } private void OnDestroy() { // RawInput 정리 if (RawInput.IsRunning) { try { RawInput.OnKeyDown -= HandleRawKeyDown; // 다른 컴포넌트가 사용 중일 수 있으므로 Stop은 호출하지 않음 } catch (System.Exception ex) { // RawInput 정리 실패 무시 } } // CameraManager 이벤트 해제 if (cameraManager != null) { cameraManager.OnCameraChanged -= OnCameraChanged; } // CameraInfoUI GameObject 정리 if (cameraInfoUIGameObject != null) { Destroy(cameraInfoUIGameObject); cameraInfoUIGameObject = null; cameraInfoUI = null; } } private void InitializeRawInput() { try { if (!RawInput.IsRunning) { RawInput.Start(); RawInput.WorkInBackground = true; // 백그라운드에서도 키 입력 감지 RawInput.InterceptMessages = false; // 다른 앱으로 키 메시지 전달 } // 이벤트 중복 등록 방지 RawInput.OnKeyDown -= HandleRawKeyDown; RawInput.OnKeyDown += HandleRawKeyDown; } catch (System.Exception ex) { // RawInput 실패 시 Unity Input으로 대체 } } private void Update() { HandleFOVControl(); UpdateFOVPhysics(); UpdateDOFPhysics(); UpdateDOFTargetTracking(); } private void HandleRawKeyDown(RawKey key) { switch (key) { case RawKey.F13: if (isFOVMode) IncreaseFOV(); else IncreaseDOF(); break; case RawKey.F14: if (isFOVMode) DecreaseFOV(); else DecreaseDOF(); break; case RawKey.F15: ToggleControlMode(); break; case RawKey.F16: TakeScreenshot(); break; case RawKey.F17: ToggleCameraUI(); break; case RawKey.F18: HandleF18Click(); break; } } private void HandleFOVControl() { // Unity Input으로도 처리 (RawInput과 병행하여 안정성 확보) if (Input.GetKeyDown(KeyCode.F13)) { if (isFOVMode) IncreaseFOV(); else IncreaseDOF(); } else if (Input.GetKeyDown(KeyCode.F14)) { if (isFOVMode) DecreaseFOV(); else DecreaseDOF(); } else if (Input.GetKeyDown(KeyCode.F15)) { ToggleControlMode(); } else if (Input.GetKeyDown(KeyCode.F16)) { TakeScreenshot(); } else if (Input.GetKeyDown(KeyCode.F17)) { ToggleCameraUI(); } else if (Input.GetKeyDown(KeyCode.F18)) { HandleF18Click(); } } private void OnCameraChanged(CameraManager.CameraPreset oldPreset, CameraManager.CameraPreset newPreset) { UpdateCurrentCamera(); } private void UpdateCurrentCamera() { if (cameraManager?.CurrentPreset?.virtualCamera != null) { currentVirtualCamera = cameraManager.CurrentPreset.virtualCamera; } } private void IncreaseFOV() { if (currentVirtualCamera == null) return; // 현재 FOV를 targetFOV로 초기화 (처음 호출 시) if (!isApplyingForce) { targetFOV = currentVirtualCamera.Lens.FieldOfView; isApplyingForce = true; } ApplyFOVForce(fovForce); // 양의 포스 적용 } private void DecreaseFOV() { if (currentVirtualCamera == null) return; // 현재 FOV를 targetFOV로 초기화 (처음 호출 시) if (!isApplyingForce) { targetFOV = currentVirtualCamera.Lens.FieldOfView; isApplyingForce = true; } ApplyFOVForce(-fovForce); // 음의 포스 적용 } private void ApplyFOVForce(float force) { // 포스를 속도에 더함 (가속도 = 포스, 질량 = 1로 가정) fovVelocity += force * Time.deltaTime; // 최대 속도 제한 fovVelocity = Mathf.Clamp(fovVelocity, -fovMaxVelocity, fovMaxVelocity); } private void UpdateFOVPhysics() { if (currentVirtualCamera == null) return; var lens = currentVirtualCamera.Lens; float currentFOV = lens.FieldOfView; // 더 부드러운 감쇠력 적용 (지수적 감쇠) float dampingFactor = Mathf.Exp(-fovDamping * Time.deltaTime); fovVelocity *= dampingFactor; // 속도가 거의 0에 가까우면 완전히 정지 (더 작은 임계값) if (Mathf.Abs(fovVelocity) < 0.05f) { fovVelocity = 0f; isApplyingForce = false; } // 속도가 있을 때만 FOV 업데이트 if (Mathf.Abs(fovVelocity) > 0.001f) { float newFOV = currentFOV + (fovVelocity * Time.deltaTime); // FOV 범위 제한 및 경계에서 부드러운 반발 if (newFOV <= minFOV) { newFOV = minFOV; fovVelocity = Mathf.Max(0f, fovVelocity * 0.3f); // 부드러운 반발 } else if (newFOV >= maxFOV) { newFOV = maxFOV; fovVelocity = Mathf.Min(0f, fovVelocity * 0.3f); // 부드러운 반발 } lens.FieldOfView = newFOV; currentVirtualCamera.Lens = lens; targetFOV = newFOV; } } private void InitializeBeautify() { Debug.Log("[CameraControlSystem] Volume 생성 또는 찾기 시작..."); // 같은 게임오브젝트에서 Volume 컴포넌트 찾기 또는 생성 Volume volume = GetComponent(); if (volume == null) { Debug.Log("[CameraControlSystem] Volume이 없어서 새로 생성합니다"); volume = gameObject.AddComponent(); volume.priority = 10f; // 우선순위 설정 volume.isGlobal = true; // 전역 볼륨으로 설정 // VolumeProfile 생성 volume.sharedProfile = ScriptableObject.CreateInstance(); volume.sharedProfile.name = "CameraControl_BeautifyProfile"; Debug.Log("[CameraControlSystem] Volume 생성 완료 (우선순위: 10)"); } else { // 기존 Volume의 우선순위 확인/설정 if (volume.priority != 10f) { volume.priority = 10f; Debug.Log("[CameraControlSystem] 기존 Volume의 우선순위를 10으로 설정"); } } if (volume != null) { Debug.Log($"[CameraControlSystem] Volume 발견: {volume.name}"); if (volume.sharedProfile != null) { Debug.Log($"[CameraControlSystem] Profile: {volume.sharedProfile.name}"); // 정확한 어셈블리명으로 Beautify 타입 찾기 System.Type beautifyType = System.Type.GetType("Beautify.Universal.Beautify, Unity.RenderPipelines.Universal.Runtime"); if (beautifyType != null) { Debug.Log($"[CameraControlSystem] Beautify 타입 발견: {beautifyType.FullName}"); } else { Debug.LogError("[CameraControlSystem] Beautify 타입을 찾을 수 없음 (어셈블리명 포함)"); } if (beautifyType != null) { try { // VolumeProfile.TryGet을 올바른 시그니처로 호출 Debug.Log("[CameraControlSystem] VolumeProfile.TryGet 직접 호출"); // out 파라미터를 위한 배열 object[] parameters = { null }; // TryGet(out T component) 메서드 찾기 var methods = volume.sharedProfile.GetType().GetMethods(); System.Reflection.MethodInfo tryGetMethod = null; foreach (var method in methods) { if (method.Name == "TryGet" && method.IsGenericMethodDefinition) { var paramTypes = method.GetParameters(); if (paramTypes.Length == 1 && paramTypes[0].ParameterType.IsByRef) { tryGetMethod = method; Debug.Log("[CameraControlSystem] TryGet 메서드 발견"); break; } } } if (tryGetMethod != null) { var genericMethod = tryGetMethod.MakeGenericMethod(beautifyType); // Invoke 호출 object[] args = new object[1]; bool result = (bool)genericMethod.Invoke(volume.sharedProfile, args); if (result && args[0] != null) { beautifyOverride = args[0]; Debug.Log($"[CameraControlSystem] Beautify Override 발견! {beautifyOverride.GetType().Name}"); SetupDOFField(beautifyType); return; // 성공! } else { Debug.LogWarning("[CameraControlSystem] Profile에 Beautify가 없음, 새로 생성합니다"); CreateBeautifyOverride(volume, beautifyType); return; } } else { Debug.LogError("[CameraControlSystem] 올바른 TryGet 메서드를 찾을 수 없음"); } } catch (System.Exception ex) { Debug.LogError($"[CameraControlSystem] TryGet 호출 실패: {ex.Message}"); } } else { Debug.LogError("[CameraControlSystem] Beautify 타입을 찾을 수 없음"); } } else { Debug.LogError("[CameraControlSystem] Volume에 Profile이 없음"); } } else { Debug.LogError("[CameraControlSystem] 같은 게임오브젝트에 Volume이 없음"); } // 실패한 경우 BeautifySettings 방식 시도 (백업) try { var beautifySettingsType = System.Type.GetType("Beautify.Universal.BeautifySettings, Beautify"); if (beautifySettingsType != null) { Debug.Log("[CameraControlSystem] BeautifySettings 타입 발견"); var sharedSettingsProperty = beautifySettingsType.GetProperty("sharedSettings", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); if (sharedSettingsProperty != null) { beautifyOverride = sharedSettingsProperty.GetValue(null); if (beautifyOverride != null) { Debug.Log($"[CameraControlSystem] Beautify Override 발견: {beautifyOverride.GetType().Name}"); // depthOfFieldDistance 필드 확인 var dofDistanceField = beautifyOverride.GetType().GetField("depthOfFieldDistance"); if (dofDistanceField != null) { Debug.Log($"[CameraControlSystem] depthOfFieldDistance 필드 발견: {dofDistanceField.FieldType}"); var fieldValue = dofDistanceField.GetValue(beautifyOverride); if (fieldValue != null) { Debug.Log($"[CameraControlSystem] 필드 값 타입: {fieldValue.GetType()}"); // FloatParameter 타입의 value 속성 가져오기 var valueProperty = fieldValue.GetType().GetProperty("value"); if (valueProperty != null && valueProperty.PropertyType == typeof(float)) { currentDOFValue = (float)valueProperty.GetValue(fieldValue); Debug.Log($"[CameraControlSystem] 초기 DOF 거리: {currentDOFValue}"); } else { Debug.LogWarning("[CameraControlSystem] value 속성을 찾을 수 없음"); } } else { Debug.LogWarning("[CameraControlSystem] depthOfFieldDistance 필드 값이 null"); } } else { Debug.LogWarning("[CameraControlSystem] depthOfFieldDistance 필드를 찾을 수 없음"); // 모든 필드 출력해보기 var allFields = beautifyOverride.GetType().GetFields(); Debug.Log($"[CameraControlSystem] 사용 가능한 모든 필드 ({allFields.Length}개):"); foreach (var field in allFields) { if (field.Name.ToLower().Contains("depth") || field.Name.ToLower().Contains("dof") || field.Name.ToLower().Contains("focus")) { var fieldValue = field.GetValue(beautifyOverride); if (fieldValue != null) { var valueProperty = fieldValue.GetType().GetProperty("value"); var overrideProperty = fieldValue.GetType().GetProperty("overrideState"); if (valueProperty != null && overrideProperty != null) { var currentValue = valueProperty.GetValue(fieldValue); var isOverridden = overrideProperty.GetValue(fieldValue); Debug.Log($" - {field.Name} ({field.FieldType}) = {currentValue}, Override: {isOverridden}"); } else { Debug.Log($" - {field.Name} ({field.FieldType}) = {fieldValue}"); } } else { Debug.Log($" - {field.Name} ({field.FieldType}) = null"); } } } } } else { Debug.LogWarning("[CameraControlSystem] BeautifySettings.sharedSettings가 null"); } } else { Debug.LogWarning("[CameraControlSystem] sharedSettings 속성을 찾을 수 없음"); } } else { Debug.LogWarning("[CameraControlSystem] BeautifySettings 타입을 찾을 수 없음"); } } catch (System.Exception ex) { Debug.LogError($"[CameraControlSystem] Beautify 초기화 실패: {ex.Message}"); } } // DOF 제어 메서드들 private void IncreaseDOF() { if (beautifyOverride == null || !isDOFEnabled) return; currentDOFValue = Mathf.Clamp(currentDOFValue + dofStep, minDOF, maxDOF); SetDOFValue(currentDOFValue); Debug.Log($"[CameraControlSystem] DOF 증가: {currentDOFValue:F3}"); } private void DecreaseDOF() { if (beautifyOverride == null || !isDOFEnabled) return; currentDOFValue = Mathf.Clamp(currentDOFValue - dofStep, minDOF, maxDOF); SetDOFValue(currentDOFValue); Debug.Log($"[CameraControlSystem] DOF 감소: {currentDOFValue:F3}"); } private void UpdateDOFPhysics() { // 더 이상 물리 시스템 사용 안 함 } // DOF Focal Length 값을 가져오는 헬퍼 메서드 private float? GetCurrentDOFValue() { if (beautifyOverride == null) return null; string[] possibleFieldNames = { "depthOfFieldFocalLength", "focalLength", "dofFocalLength" }; var overrideType = beautifyOverride.GetType(); foreach (string fieldName in possibleFieldNames) { var dofField = overrideType.GetField(fieldName); if (dofField != null) { var fieldValue = dofField.GetValue(beautifyOverride); if (fieldValue != null) { // VolumeParameter 타입 var valueProperty = fieldValue.GetType().GetProperty("value"); if (valueProperty != null && valueProperty.PropertyType == typeof(float)) { return (float)valueProperty.GetValue(fieldValue); } // 직접 float 타입 else if (fieldValue is float) { return (float)fieldValue; } } } } return null; } // DOF Focal Length 값을 설정하는 헬퍼 메서드 private void SetDOFValue(float newValue) { if (beautifyOverride == null) { Debug.LogWarning("[CameraControlSystem] beautifyOverride가 null입니다"); return; } string[] possibleFieldNames = { "depthOfFieldFocalLength", "focalLength", "dofFocalLength" }; var overrideType = beautifyOverride.GetType(); foreach (string fieldName in possibleFieldNames) { var dofField = overrideType.GetField(fieldName); if (dofField != null) { var fieldValue = dofField.GetValue(beautifyOverride); if (fieldValue != null) { // VolumeParameter 타입 var valueProperty = fieldValue.GetType().GetProperty("value"); if (valueProperty != null && valueProperty.PropertyType == typeof(float)) { if (valueProperty.CanWrite) { // Override 상태를 먼저 활성화 var overrideState = fieldValue.GetType().GetProperty("overrideState"); if (overrideState != null && overrideState.CanWrite) { overrideState.SetValue(fieldValue, true); } valueProperty.SetValue(fieldValue, newValue); return; } } // 직접 float 타입 else if (fieldValue is float && dofField.FieldType == typeof(float)) { dofField.SetValue(beautifyOverride, newValue); return; } } } } Debug.LogError("[CameraControlSystem] DOF Focal Length 필드를 찾을 수 없거나 설정할 수 없습니다"); } private void CreateBeautifyOverride(Volume volume, System.Type beautifyType) { try { Debug.Log("[CameraControlSystem] Beautify Override 생성 중..."); // Beautify 인스턴스를 ScriptableObject.CreateInstance로 생성 beautifyOverride = ScriptableObject.CreateInstance(beautifyType); if (beautifyOverride != null) { Debug.Log("[CameraControlSystem] Beautify Override 인스턴스 생성 성공 (ScriptableObject)"); // VolumeProfile에 추가하는 다양한 방법 시도 bool addSuccess = false; // 방법 1: Add() 메서드들 확인 var methods = volume.sharedProfile.GetType().GetMethods(); foreach (var method in methods) { if (method.Name == "Add") { Debug.Log($"[CameraControlSystem] Add 메서드 발견: {method}"); var parameters = method.GetParameters(); foreach (var param in parameters) { Debug.Log($" - 파라미터: {param.ParameterType} {param.Name}"); } // T Add(bool overrides = false) where T : VolumeComponent, new() 시그니처 찾기 if (method.IsGenericMethodDefinition && parameters.Length <= 1) { try { var genericMethod = method.MakeGenericMethod(beautifyType); object[] args = parameters.Length == 0 ? new object[0] : new object[] { false }; var addedComponent = genericMethod.Invoke(volume.sharedProfile, args); if (addedComponent != null) { beautifyOverride = addedComponent; Debug.Log("[CameraControlSystem] Beautify Override가 Profile에 추가됨 (Add)"); addSuccess = true; break; } } catch (System.Exception ex) { Debug.Log($"[CameraControlSystem] Add 시도 실패: {ex.Message}"); } } } } // 방법 2: components 리스트에 직접 추가 if (!addSuccess) { try { var componentsField = volume.sharedProfile.GetType().GetField("components", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (componentsField != null) { var components = (System.Collections.IList)componentsField.GetValue(volume.sharedProfile); components.Add(beautifyOverride); Debug.Log("[CameraControlSystem] Beautify Override가 Profile에 추가됨 (직접 추가)"); addSuccess = true; } } catch (System.Exception ex) { Debug.LogError($"[CameraControlSystem] 직접 추가 실패: {ex.Message}"); } } if (addSuccess) { SetupDOFField(beautifyType); } else { Debug.LogError("[CameraControlSystem] Beautify Override 추가 실패"); } } else { Debug.LogError("[CameraControlSystem] Beautify Override 인스턴스 생성 실패"); } } catch (System.Exception ex) { Debug.LogError($"[CameraControlSystem] Beautify Override 생성 실패: {ex.Message}"); } } private void SetupDOFField(System.Type beautifyType) { // 모든 DOF 관련 설정들 string[] dofFieldsToEnable = { "depthOfField", // DOF 메인 활성화 "depthOfFieldDistance", // DOF 거리 "depthOfFieldBokeh", // 보케 효과 "depthOfFieldForegroundBlur", // 전경 블러 "depthOfFieldMaxSamples", // 최대 샘플 "depthOfFieldDownsampling", // 다운샘플링 "depthOfFieldMaxBrightness", // 최대 밝기 "depthOfFieldFocalLength" // 포커스 렌즈 길이 }; foreach (string fieldName in dofFieldsToEnable) { var field = beautifyType.GetField(fieldName); if (field != null) { var fieldValue = field.GetValue(beautifyOverride); if (fieldValue != null) { var valueProperty = fieldValue.GetType().GetProperty("value"); var overrideProperty = fieldValue.GetType().GetProperty("overrideState"); if (valueProperty != null && overrideProperty != null) { // Override 상태 활성화 overrideProperty.SetValue(fieldValue, true); // 필드별 기본값 설정 switch (fieldName) { case "depthOfField": valueProperty.SetValue(fieldValue, false); // 기본값: 비활성화 break; case "depthOfFieldDistance": valueProperty.SetValue(fieldValue, 1f); currentDOFValue = 1f; break; case "depthOfFieldBokeh": valueProperty.SetValue(fieldValue, true); break; case "depthOfFieldForegroundBlur": valueProperty.SetValue(fieldValue, true); break; case "depthOfFieldMaxSamples": if (valueProperty.PropertyType == typeof(int)) { valueProperty.SetValue(fieldValue, 8); } break; case "depthOfFieldDownsampling": if (valueProperty.PropertyType == typeof(int)) { valueProperty.SetValue(fieldValue, 2); } break; case "depthOfFieldMaxBrightness": if (valueProperty.PropertyType == typeof(float)) { valueProperty.SetValue(fieldValue, 1000f); } break; case "depthOfFieldFocalLength": if (valueProperty.PropertyType == typeof(float)) { valueProperty.SetValue(fieldValue, 0.3f); // 포커스 렌즈 길이 0.3 currentDOFValue = 0.3f; } break; } } } } } } private void TakeScreenshot() { StartCoroutine(TakeScreenshotCoroutine()); } private System.Collections.IEnumerator TakeScreenshotCoroutine() { // UI 임시 숨기기 bool wasUIVisible = cameraInfoUI?.IsUIVisible ?? false; if (wasUIVisible && cameraInfoUI != null) { cameraInfoUI.ToggleUI(); } // 한 프레임 대기 (UI 숨김 적용) yield return new WaitForEndOfFrame(); string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); string filename = $"Screenshot_{timestamp}.png"; string fullPath = System.IO.Path.Combine(screenshotPath, filename); // 고해상도 스크린샷 촬영 (플래시 효과 전에!) ScreenCapture.CaptureScreenshot(fullPath, screenshotResolutionMultiplier); // 스크린샷 촬영 후 플래시 효과 시작 if (cameraInfoUI != null) { cameraInfoUI.TriggerScreenshotFlash(); } // 0.1초 후 UI 복원 (스크린샷 완료 보장) yield return new WaitForSeconds(0.1f); if (wasUIVisible && cameraInfoUI != null) { cameraInfoUI.ToggleUI(); } } private void ToggleCameraUI() { if (cameraInfoUI != null) { cameraInfoUI.ToggleUI(); } } private void ToggleDOF() { if (beautifyOverride == null) return; isDOFEnabled = !isDOFEnabled; var overrideType = beautifyOverride.GetType(); var dofField = overrideType.GetField("depthOfField"); if (dofField != null) { var fieldValue = dofField.GetValue(beautifyOverride); if (fieldValue != null) { var valueProperty = fieldValue.GetType().GetProperty("value"); var overrideProperty = fieldValue.GetType().GetProperty("overrideState"); if (valueProperty != null && overrideProperty != null) { overrideProperty.SetValue(fieldValue, true); valueProperty.SetValue(fieldValue, isDOFEnabled); Debug.Log($"[CameraControlSystem] DOF {(isDOFEnabled ? "활성화" : "비활성화")}"); } } } } private void ToggleControlMode() { isFOVMode = !isFOVMode; Debug.Log($"[CameraControlSystem] 제어 모드 전환: {(isFOVMode ? "FOV" : "DOF")} 모드"); } private void HandleF18Click() { float currentTime = Time.time; float timeSinceLastClick = currentTime - lastF18ClickTime; if (timeSinceLastClick <= doubleClickThreshold) { // 더블클릭: DOF ON/OFF ToggleDOF(); } else { // 싱글클릭: DOF 타겟 전환 CycleDOFTarget(); } lastF18ClickTime = currentTime; } private void FindDOFTargets() { availableDOFTargets.Clear(); // SystemController에서 글로벌하게 관리하는 Head 타겟 목록 가져오기 var systemController = SystemController.Instance; if (systemController != null) { var headTargets = systemController.GetAvatarHeadTargets(); if (headTargets != null && headTargets.Count > 0) { availableDOFTargets.AddRange(headTargets); Debug.Log($"[CameraControlSystem] SystemController에서 DOF 타겟 {availableDOFTargets.Count}개 가져옴"); return; } } // SystemController가 없거나 타겟이 비어있으면 직접 검색 (폴백) var retargetingScripts = FindObjectsByType(FindObjectsSortMode.None); foreach (var script in retargetingScripts) { if (script.targetAnimator == null) continue; Transform headBone = script.targetAnimator.GetBoneTransform(HumanBodyBones.Head); if (headBone == null) continue; if (!headBone.TryGetComponent(out _)) { var collider = headBone.gameObject.AddComponent(); collider.radius = 0.1f; collider.isTrigger = true; } availableDOFTargets.Add(headBone); } Debug.Log($"[CameraControlSystem] DOF 타겟 {availableDOFTargets.Count}개 발견 (폴백)"); } private void CycleDOFTarget() { if (currentVirtualCamera == null) { Debug.LogWarning("[CameraControlSystem] 가상 카메라가 없습니다."); return; } // 카메라 중앙에서 레이 발사 Camera mainCamera = Camera.main; if (mainCamera == null) { Debug.LogWarning("[CameraControlSystem] 메인 카메라를 찾을 수 없습니다."); return; } Ray ray = mainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); RaycastHit[] hits = Physics.RaycastAll(ray, 100f); Debug.Log($"[CameraControlSystem] 레이캐스트: {hits.Length}개의 오브젝트 감지"); // SphereCollider가 있는 Head 본만 필터링 List validHits = new List(); foreach (var hit in hits) { if (hit.collider is SphereCollider && hit.transform.name.Contains("Head")) { validHits.Add(hit); Debug.Log($"[CameraControlSystem] 유효한 타겟 발견: {hit.transform.name} (거리: {hit.distance:F2}m)"); } } if (validHits.Count == 0) { Debug.LogWarning("[CameraControlSystem] 레이에 맞은 Head 타겟이 없습니다."); return; } // 거리순으로 정렬 validHits.Sort((a, b) => a.distance.CompareTo(b.distance)); // 현재 타겟 찾기 int nextIndex = 0; if (currentDOFTarget != null) { for (int i = 0; i < validHits.Count; i++) { if (validHits[i].transform == currentDOFTarget) { nextIndex = (i + 1) % validHits.Count; break; } } } // 다음 타겟 설정 currentDOFTarget = validHits[nextIndex].transform; float distance = validHits[nextIndex].distance; // depthOfFieldDistance 설정 SetDOFDistance(distance); Debug.Log($"[CameraControlSystem] DOF 타겟 설정: {currentDOFTarget.name} (거리: {distance:F2}m)"); } private void SetDOFDistance(float distance) { if (beautifyOverride == null) { Debug.LogWarning("[CameraControlSystem] beautifyOverride가 null입니다"); return; } var overrideType = beautifyOverride.GetType(); var dofDistanceField = overrideType.GetField("depthOfFieldDistance"); if (dofDistanceField != null) { var fieldValue = dofDistanceField.GetValue(beautifyOverride); if (fieldValue != null) { var valueProperty = fieldValue.GetType().GetProperty("value"); var overrideProperty = fieldValue.GetType().GetProperty("overrideState"); if (valueProperty != null && overrideProperty != null) { overrideProperty.SetValue(fieldValue, true); valueProperty.SetValue(fieldValue, distance); } } } } private void UpdateDOFTargetTracking() { // 현재 타겟이 설정되어 있고, DOF가 활성화되어 있으면 지속적으로 거리 업데이트 if (currentDOFTarget != null && isDOFEnabled && currentVirtualCamera != null) { float distance = Vector3.Distance(currentVirtualCamera.transform.position, currentDOFTarget.position); SetDOFDistance(distance); } } // Public 메서드들 (외부에서 호출 가능) public void SetFOV(float fov) { if (currentVirtualCamera == null) return; var lens = currentVirtualCamera.Lens; lens.FieldOfView = Mathf.Clamp(fov, minFOV, maxFOV); currentVirtualCamera.Lens = lens; } public float GetCurrentFOV() { return currentVirtualCamera?.Lens.FieldOfView ?? 0f; } public void SetFOVLimits(float min, float max) { minFOV = min; maxFOV = max; } public void SetScreenshotPath(string path) { screenshotPath = path; if (!System.IO.Directory.Exists(screenshotPath)) { System.IO.Directory.CreateDirectory(screenshotPath); } } // UI에서 사용할 Public 메서드들 public float GetCurrentForce() { return fovForce; } public float GetCurrentVelocity() { return fovVelocity; } // DOF UI에서 사용할 Public 메서드들 public float GetCurrentDOF() { var dofValue = GetCurrentDOFValue(); return dofValue.HasValue ? dofValue.Value : 0f; } public float GetCurrentDofForce() { return dofStep; } public float GetCurrentDofVelocity() { return 0f; // 더 이상 velocity 사용 안 함 } public bool IsFOVMode() { return isFOVMode; } }