1075 lines
40 KiB
C#
1075 lines
40 KiB
C#
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<CameraManager>();
|
|
if (cameraManager != null)
|
|
{
|
|
cameraManager.OnCameraChanged += OnCameraChanged;
|
|
UpdateCurrentCamera();
|
|
}
|
|
|
|
// CameraInfoUI 생성
|
|
GameObject uiGO = new GameObject("CameraInfoUI");
|
|
cameraInfoUI = uiGO.AddComponent<CameraInfoUI>();
|
|
|
|
// 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<Volume>();
|
|
if (volume == null)
|
|
{
|
|
Debug.Log("[CameraControlSystem] Volume이 없어서 새로 생성합니다");
|
|
volume = gameObject.AddComponent<Volume>();
|
|
volume.priority = 10f; // 우선순위 설정
|
|
volume.isGlobal = true; // 전역 볼륨으로 설정
|
|
|
|
// VolumeProfile 생성
|
|
volume.sharedProfile = ScriptableObject.CreateInstance<VolumeProfile>();
|
|
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<T>(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<float> 타입
|
|
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<float> 타입
|
|
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<T>() 메서드들 확인
|
|
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<T>(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<T>)");
|
|
addSuccess = true;
|
|
break;
|
|
}
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
Debug.Log($"[CameraControlSystem] Add<T> 시도 실패: {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;
|
|
}
|
|
} |