diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraControlSystem.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraControlSystem.cs new file mode 100644 index 00000000..039db4d8 --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraControlSystem.cs @@ -0,0 +1,1020 @@ +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 Volume currentVolume; + + 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.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(); + } + + // 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; + } + + // 현재 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; + } + + // 현재 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) 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" // 최대 밝기 + }; + + 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, true); + 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; + } + } + } + } + } + } + + 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(); + } + } + + // 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; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraControlSystem.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraControlSystem.cs.meta new file mode 100644 index 00000000..ee3f667c --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraControlSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1b2c3d4e5f6789012345678901234ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraInfoUI.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraInfoUI.cs new file mode 100644 index 00000000..b2f9cbb1 --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraInfoUI.cs @@ -0,0 +1,223 @@ +using UnityEngine; +using Unity.Cinemachine; +using UnityEngine.UI; + +public class CameraInfoUI : MonoBehaviour +{ + [Header("UI References")] + [SerializeField] private GameObject uiPanel; + [SerializeField] private Text cameraNameText; + [SerializeField] private Text fovValueText; + [SerializeField] private Text fovSpeedText; + [SerializeField] private Text velocityText; + [SerializeField] private Text dofValueText; + [SerializeField] private Text dofSpeedText; + [SerializeField] private Text dofVelocityText; + [SerializeField] private GameObject flashPanel; + + [Header("UI Settings")] + [SerializeField] private float updateInterval = 0.1f; // UI 업데이트 간격 + [SerializeField] private float flashDuration = 0.2f; + + private CameraManager cameraManager; + private CameraControlSystem cameraControlSystem; + private float lastUpdateTime; + private bool isUIVisible = true; + private Coroutine flashCoroutine; + + private void Awake() + { + CreateUI(); + } + + private void Start() + { + cameraManager = FindObjectOfType(); + cameraControlSystem = FindObjectOfType(); + + if (cameraManager != null) + { + cameraManager.OnCameraChanged += OnCameraChanged; + } + } + + private void OnDestroy() + { + if (cameraManager != null) + { + cameraManager.OnCameraChanged -= OnCameraChanged; + } + } + + private void Update() + { + if (isUIVisible && Time.time - lastUpdateTime > updateInterval) + { + UpdateUI(); + lastUpdateTime = Time.time; + } + } + + private void CreateUI() + { + // 캔버스 생성 + GameObject canvasGO = new GameObject("CameraInfoCanvas"); + Canvas canvas = canvasGO.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = 1000; // 최상위 렌더링 + + CanvasScaler scaler = canvasGO.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920, 1080); + + canvasGO.AddComponent(); + + // UI 패널 생성 (우측 상단) + uiPanel = new GameObject("CameraInfoPanel"); + uiPanel.transform.SetParent(canvasGO.transform, false); + + Image panelImage = uiPanel.AddComponent(); + panelImage.color = new Color(0, 0, 0, 0.7f); // 반투명 검은색 + + RectTransform panelRect = uiPanel.GetComponent(); + panelRect.anchorMin = new Vector2(1, 1); + panelRect.anchorMax = new Vector2(1, 1); + panelRect.pivot = new Vector2(1, 1); + panelRect.anchoredPosition = new Vector2(-20, -20); + panelRect.sizeDelta = new Vector2(350, 200); + + // 텍스트들 생성 + CreateInfoText("Camera Name: ", out cameraNameText, 0); + CreateInfoText("FOV: ", out fovValueText, 1); + CreateInfoText("FOV Force: ", out fovSpeedText, 2); + CreateInfoText("FOV Velocity: ", out velocityText, 3); + CreateInfoText("DOF Distance: ", out dofValueText, 4); + CreateInfoText("DOF Force: ", out dofSpeedText, 5); + CreateInfoText("DOF Velocity: ", out dofVelocityText, 6); + + // 플래시 패널 생성 + flashPanel = new GameObject("FlashPanel"); + flashPanel.transform.SetParent(canvasGO.transform, false); + + Image flashImage = flashPanel.AddComponent(); + flashImage.color = new Color(1, 1, 1, 0); // 투명한 흰색 + + RectTransform flashRect = flashPanel.GetComponent(); + flashRect.anchorMin = Vector2.zero; + flashRect.anchorMax = Vector2.one; + flashRect.sizeDelta = Vector2.zero; + flashRect.anchoredPosition = Vector2.zero; + + flashPanel.SetActive(false); + } + + private void CreateInfoText(string label, out Text textComponent, int index) + { + GameObject textGO = new GameObject($"InfoText_{index}"); + textGO.transform.SetParent(uiPanel.transform, false); + + textComponent = textGO.AddComponent(); + textComponent.text = label + "Loading..."; + textComponent.font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); + textComponent.fontSize = 14; + textComponent.color = Color.white; + + RectTransform textRect = textGO.GetComponent(); + textRect.anchorMin = new Vector2(0, 1); + textRect.anchorMax = new Vector2(1, 1); + textRect.pivot = new Vector2(0, 1); + textRect.anchoredPosition = new Vector2(10, -10 - (index * 25)); + textRect.sizeDelta = new Vector2(-20, 20); + } + + private void UpdateUI() + { + if (cameraManager?.CurrentPreset?.virtualCamera != null) + { + var currentCamera = cameraManager.CurrentPreset.virtualCamera; + cameraNameText.text = $"Camera: {currentCamera.name}"; + fovValueText.text = $"FOV: {currentCamera.Lens.FieldOfView:F1}°"; + } + else + { + cameraNameText.text = "Camera: None"; + fovValueText.text = "FOV: --°"; + } + + if (cameraControlSystem != null) + { + fovSpeedText.text = $"FOV Force: {cameraControlSystem.GetCurrentForce():F0} ({cameraControlSystem.GetCurrentForceIndex() + 1}/3)"; + velocityText.text = $"FOV Velocity: {cameraControlSystem.GetCurrentVelocity():F1}"; + + dofValueText.text = $"DOF Distance: {cameraControlSystem.GetCurrentDOF():F1}"; + dofSpeedText.text = $"DOF Force: {cameraControlSystem.GetCurrentDofForce():F1} ({cameraControlSystem.GetCurrentDofForceIndex() + 1}/3)"; + dofVelocityText.text = $"DOF Velocity: {cameraControlSystem.GetCurrentDofVelocity():F2}"; + } + else + { + fovSpeedText.text = "FOV Force: --"; + velocityText.text = "FOV Velocity: --"; + dofValueText.text = "DOF Distance: --"; + dofSpeedText.text = "DOF Force: --"; + dofVelocityText.text = "DOF Velocity: --"; + } + } + + private void OnCameraChanged(CameraManager.CameraPreset oldPreset, CameraManager.CameraPreset newPreset) + { + UpdateUI(); + } + + public void ToggleUI() + { + isUIVisible = !isUIVisible; + uiPanel.SetActive(isUIVisible); + Debug.Log($"[CameraInfoUI] UI {(isUIVisible ? "표시" : "숨김")}"); + } + + public void TriggerScreenshotFlash() + { + if (flashCoroutine != null) + { + StopCoroutine(flashCoroutine); + } + + flashCoroutine = StartCoroutine(FlashEffect()); + } + + private System.Collections.IEnumerator FlashEffect() + { + flashPanel.SetActive(true); + Image flashImage = flashPanel.GetComponent(); + + // 페이드 인 + float elapsed = 0f; + float fadeTime = flashDuration * 0.3f; + + while (elapsed < fadeTime) + { + elapsed += Time.deltaTime; + float alpha = Mathf.Lerp(0f, 0.8f, elapsed / fadeTime); + flashImage.color = new Color(1, 1, 1, alpha); + yield return null; + } + + // 페이드 아웃 + elapsed = 0f; + float fadeOutTime = flashDuration * 0.7f; + + while (elapsed < fadeOutTime) + { + elapsed += Time.deltaTime; + float alpha = Mathf.Lerp(0.8f, 0f, elapsed / fadeOutTime); + flashImage.color = new Color(1, 1, 1, alpha); + yield return null; + } + + flashImage.color = new Color(1, 1, 1, 0); + flashPanel.SetActive(false); + flashCoroutine = null; + } + + public bool IsUIVisible => isUIVisible; +} \ No newline at end of file diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraInfoUI.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraInfoUI.cs.meta new file mode 100644 index 00000000..b2ebc278 --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/CameraInfoUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2c3d4e5f6789012345678901234bcde +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file