using UnityEngine; using Unity.Cinemachine; using UnityRawInput; using UnityEngine.Rendering; using System; public class CameraControlSystem : MonoBehaviour { [Header("FOV Physics Settings")] [SerializeField] private float[] fovForces = {80f, 200f, 400f}; // 포스 강도 [SerializeField] private int currentForceIndex = 1; // 기본값: 200 [SerializeField] private float fovDamping = 6f; // 감쇠력 [SerializeField] private float fovMaxVelocity = 100f; // 최대 속도 [SerializeField] private float minFOV = 0.1f; [SerializeField] private float maxFOV = 60f; [Header("DOF Physics Settings")] [SerializeField] private float[] dofForces = {10f, 20f, 50f}; // DOF 포스 강도 [SerializeField] private int currentDofForceIndex = 1; // 기본값: 0.5 [SerializeField] private float dofDamping = 6f; // DOF 감쇠력 [SerializeField] private float dofMaxVelocity = 5f; // DOF 최대 속도 [SerializeField] private float minDOF = 0.1f; [SerializeField] private float maxDOF = 50f; [Header("Screenshot Settings")] [SerializeField] private string screenshotPath = "Screenshots"; [SerializeField] private int screenshotResolutionMultiplier = 1; private CameraManager cameraManager; private CinemachineCamera currentVirtualCamera; private CameraInfoUI cameraInfoUI; // FOV 물리 시스템 변수들 private float fovVelocity = 0f; // 현재 FOV 변화 속도 private float targetFOV = 60f; // 목표 FOV private bool isApplyingForce = false; // 현재 포스가 적용 중인지 // DOF 물리 시스템 변수들 private float dofVelocity = 0f; // 현재 DOF 변화 속도 private float targetDOF = 1f; // 목표 DOF private bool isApplyingDofForce = false; // 현재 DOF 포스가 적용 중인지 // Beautify Volume Override 참조 private object beautifyOverride; private bool isDOFEnabled = false; // DOF 활성화 상태 (기본값: 꺼짐) 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 생성 GameObject uiGO = new GameObject("CameraInfoUI"); cameraInfoUI = uiGO.AddComponent(); // Beautify 컴포넌트 찾기 Debug.Log("[CameraControlSystem] Beautify 초기화 시작"); InitializeBeautify(); 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; } } 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(); } private void HandleRawKeyDown(RawKey key) { switch (key) { case RawKey.F13: IncreaseFOV(); break; case RawKey.F14: DecreaseFOV(); break; case RawKey.F15: CycleFOVSpeed(); break; case RawKey.F16: TakeScreenshot(); break; case RawKey.F17: ToggleCameraUI(); break; case RawKey.F18: ToggleDOF(); break; case RawKey.F19: Debug.Log("[CameraControlSystem] F19 키 입력 감지 (RawInput)"); CycleDOFSpeed(); break; case RawKey.F20: Debug.Log("[CameraControlSystem] F20 키 입력 감지 (RawInput)"); IncreaseDOF(); break; case RawKey.F21: Debug.Log("[CameraControlSystem] F21 키 입력 감지 (RawInput)"); DecreaseDOF(); break; } } private void HandleFOVControl() { // Unity Input으로도 처리 (RawInput과 병행하여 안정성 확보) if (Input.GetKeyDown(KeyCode.F13)) { IncreaseFOV(); } else if (Input.GetKeyDown(KeyCode.F14)) { DecreaseFOV(); } else if (Input.GetKeyDown(KeyCode.F15)) { CycleFOVSpeed(); } else if (Input.GetKeyDown(KeyCode.F16)) { TakeScreenshot(); } else if (Input.GetKeyDown(KeyCode.F17)) { ToggleCameraUI(); } else if (Input.GetKeyDown(KeyCode.F18)) { ToggleDOF(); } // DOF 제어 (F19-F21) if (Input.GetKeyDown(KeyCode.F19)) { Debug.Log("[CameraControlSystem] F19 키 입력 감지 (Unity Input)"); CycleDOFSpeed(); } else if (Input.GetKeyDown(KeyCode.F20)) { Debug.Log("[CameraControlSystem] F20 키 입력 감지 (Unity Input)"); IncreaseDOF(); } else if (Input.GetKeyDown(KeyCode.F21)) { Debug.Log("[CameraControlSystem] F21 키 입력 감지 (Unity Input)"); DecreaseDOF(); } } 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; } float currentForce = fovForces[currentForceIndex]; ApplyFOVForce(currentForce); // 양의 포스 적용 } private void DecreaseFOV() { if (currentVirtualCamera == null) return; // 현재 FOV를 targetFOV로 초기화 (처음 호출 시) if (!isApplyingForce) { targetFOV = currentVirtualCamera.Lens.FieldOfView; isApplyingForce = true; } float currentForce = fovForces[currentForceIndex]; ApplyFOVForce(-currentForce); // 음의 포스 적용 } private void CycleFOVSpeed() { currentForceIndex = (currentForceIndex + 1) % fovForces.Length; } 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)) { targetDOF = (float)valueProperty.GetValue(fieldValue); Debug.Log($"[CameraControlSystem] 초기 DOF 거리: {targetDOF}"); } 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() { Debug.Log("[CameraControlSystem] IncreaseDOF() 호출됨"); if (beautifyOverride == null) { Debug.LogWarning("[CameraControlSystem] beautifyOverride가 null이어서 IncreaseDOF() 중단됨"); return; } if (!isDOFEnabled) { Debug.Log("[CameraControlSystem] DOF가 비활성화되어 있어서 조작 불가"); return; } // 현재 DOF를 targetDOF로 초기화 (처음 호출 시) if (!isApplyingDofForce) { var currentDOF = GetCurrentDOFValue(); if (currentDOF.HasValue) { targetDOF = currentDOF.Value; } isApplyingDofForce = true; } float currentForce = dofForces[currentDofForceIndex]; ApplyDOFForce(currentForce); // 양의 포스 적용 } private void DecreaseDOF() { Debug.Log("[CameraControlSystem] DecreaseDOF() 호출됨"); if (beautifyOverride == null) { Debug.LogWarning("[CameraControlSystem] beautifyOverride가 null이어서 DecreaseDOF() 중단됨"); return; } if (!isDOFEnabled) { Debug.Log("[CameraControlSystem] DOF가 비활성화되어 있어서 조작 불가"); return; } // 현재 DOF를 targetDOF로 초기화 (처음 호출 시) if (!isApplyingDofForce) { Debug.Log("[CameraControlSystem] 현재 DOF 값 가져오는 중..."); var currentDOF = GetCurrentDOFValue(); if (currentDOF.HasValue) { targetDOF = currentDOF.Value; Debug.Log($"[CameraControlSystem] 현재 DOF 값: {currentDOF.Value}"); } else { Debug.LogWarning("[CameraControlSystem] 현재 DOF 값을 가져올 수 없음"); } isApplyingDofForce = true; } float currentForce = dofForces[currentDofForceIndex]; ApplyDOFForce(-currentForce); // 음의 포스 적용 } private void CycleDOFSpeed() { currentDofForceIndex = (currentDofForceIndex + 1) % dofForces.Length; } private void ApplyDOFForce(float force) { // 포스를 속도에 더함 (가속도 = 포스, 질량 = 1로 가정) dofVelocity += force * Time.deltaTime; // 최대 속도 제한 dofVelocity = Mathf.Clamp(dofVelocity, -dofMaxVelocity, dofMaxVelocity); } private void UpdateDOFPhysics() { if (beautifyOverride == null || !isDOFEnabled) return; var currentDOF = GetCurrentDOFValue(); if (!currentDOF.HasValue) return; // 더 부드러운 감쇠력 적용 (지수적 감쇠) float dampingFactor = Mathf.Exp(-dofDamping * Time.deltaTime); dofVelocity *= dampingFactor; // 속도가 거의 0에 가까우면 완전히 정지 (더 작은 임계값) if (Mathf.Abs(dofVelocity) < 0.01f) { dofVelocity = 0f; isApplyingDofForce = false; } // 속도가 있을 때만 DOF 업데이트 if (Mathf.Abs(dofVelocity) > 0.001f) { float newDOF = currentDOF.Value + (dofVelocity * Time.deltaTime); // DOF 범위 제한 및 경계에서 부드러운 반발 if (newDOF <= minDOF) { newDOF = minDOF; dofVelocity = Mathf.Max(0f, dofVelocity * 0.3f); // 부드러운 반발 } else if (newDOF >= maxDOF) { newDOF = maxDOF; dofVelocity = Mathf.Min(0f, dofVelocity * 0.3f); // 부드러운 반발 } SetDOFValue(newDOF); targetDOF = newDOF; } } // DOF 값을 가져오는 헬퍼 메서드 private float? GetCurrentDOFValue() { if (beautifyOverride == null) return null; string[] possibleFieldNames = { "depthOfFieldDistance", "dofDistance", "focusDistance", "depthOfFieldFocusDistance", "distance" }; 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 값을 설정하는 헬퍼 메서드 private void SetDOFValue(float newValue) { if (beautifyOverride == null) { Debug.LogWarning("[CameraControlSystem] beautifyOverride가 null입니다"); return; } string[] possibleFieldNames = { "depthOfFieldDistance", "dofDistance", "focusDistance", "depthOfFieldFocusDistance", "distance" }; 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 Distance 필드를 찾을 수 없거나 설정할 수 없습니다"); } 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); targetDOF = 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 } 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 ? "활성화" : "비활성화")}"); } } } } // 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 fovForces[currentForceIndex]; } public int GetCurrentForceIndex() { return currentForceIndex; } public float GetCurrentVelocity() { return fovVelocity; } // DOF UI에서 사용할 Public 메서드들 public float GetCurrentDOF() { var dofValue = GetCurrentDOFValue(); return dofValue.HasValue ? dofValue.Value : 0f; } public float GetCurrentDofForce() { return dofForces[currentDofForceIndex]; } public int GetCurrentDofForceIndex() { return currentDofForceIndex; } public float GetCurrentDofVelocity() { return dofVelocity; } }