using UnityEngine; using Unity.Cinemachine; using System.Collections.Generic; using UnityRawInput; using System.Linq; public class CameraManager : MonoBehaviour { #region Classes private static class KeyMapping { private static readonly Dictionary _mapping; static KeyMapping() { _mapping = new Dictionary(RawKeySetup.KeyMapping); } public static bool TryGetRawKey(KeyCode keyCode, out RawKey rawKey) { return _mapping.TryGetValue(keyCode, out rawKey); } public static bool TryGetKeyCode(RawKey rawKey, out KeyCode keyCode) { var pair = _mapping.FirstOrDefault(x => x.Value == rawKey); keyCode = pair.Key; return keyCode != KeyCode.None; } public static bool IsValidRawKey(RawKey key) { return _mapping.ContainsValue(key); } } [System.Serializable] public class HotkeyCommand { public List rawKeys = new List(); [System.NonSerialized] private List unityKeys = new List(); [System.NonSerialized] public bool isRecording = false; [System.NonSerialized] private float recordStartTime; [System.NonSerialized] private const float MAX_RECORD_TIME = 2f; public void StartRecording() { isRecording = true; recordStartTime = Time.time; rawKeys.Clear(); } public void StopRecording() { isRecording = false; InitializeUnityKeys(); } public void UpdateRecording() { if (!isRecording) return; if (Time.time - recordStartTime > MAX_RECORD_TIME) { StopRecording(); return; } // 현재 눌린 키 확인 foreach (KeyCode keyCode in System.Enum.GetValues(typeof(KeyCode))) { if (Input.GetKeyDown(keyCode) && KeyMapping.TryGetRawKey(keyCode, out RawKey rawKey)) { if (!rawKeys.Contains(rawKey)) { rawKeys.Add(rawKey); } } } // 모든 키가 떼어졌는지 확인 bool allKeysReleased = rawKeys.Any() && rawKeys.All(key => !Input.GetKey(KeyMapping.TryGetKeyCode(key, out KeyCode keyCode) ? keyCode : KeyCode.None)); if (allKeysReleased) { StopRecording(); } } public void InitializeUnityKeys() { try { unityKeys ??= new List(); unityKeys.Clear(); if (rawKeys == null || !rawKeys.Any()) return; foreach (var rawKey in rawKeys) { if (KeyMapping.TryGetKeyCode(rawKey, out KeyCode keyCode) && keyCode != KeyCode.None) { unityKeys.Add(keyCode); } } } catch (System.Exception e) { Debug.LogError($"핫키 초기화 중 오류 발생: {e.Message}"); } } public bool IsTriggered() { if (isRecording) return false; try { if (rawKeys == null || !rawKeys.Any()) return false; // RawInput으로 체크 (우선순위) bool allRawKeysPressed = rawKeys.All(key => RawInput.IsKeyDown(key)); if (allRawKeysPressed) { return true; } // Unity Input으로 체크 (백업) if (unityKeys != null && unityKeys.Any()) { bool allKeysPressed = unityKeys.All(key => Input.GetKey(key)); if (allKeysPressed) { return true; } } return false; } catch (System.Exception e) { Debug.LogError($"핫키 트리거 확인 중 오류 발생: {e.Message}"); return false; } } public override string ToString() => rawKeys?.Any() == true ? string.Join(" + ", rawKeys) : "설정되지 않음"; } [System.Serializable] public class CameraPreset { public string presetName = "New Camera Preset"; public CinemachineCamera virtualCamera; public HotkeyCommand hotkey; [System.NonSerialized] public bool isEditingHotkey = false; public CameraPreset(CinemachineCamera camera) { virtualCamera = camera; presetName = camera?.gameObject.name ?? "Unnamed Camera"; hotkey = new HotkeyCommand(); } public bool IsValid() => virtualCamera != null && hotkey != null; } #endregion #region Events public delegate void CameraChangedEventHandler(CameraPreset oldPreset, CameraPreset newPreset); public event CameraChangedEventHandler OnCameraChanged; #endregion #region Fields [SerializeField] public List cameraPresets = new List(); [Header("Camera Control Settings")] [SerializeField, Range(0.1f, 10f)] private float rotationSensitivity = 2f; [SerializeField, Range(0.1f, 20f)] private float panSpeed = 10f; [SerializeField, Range(0.1f, 20f)] private float zoomSpeed = 10f; [SerializeField, Range(0.1f, 10f)] private float orbitSpeed = 5f; private CinemachineCamera currentCamera; private InputHandler inputHandler; private Vector3 targetPosition; private CameraPreset currentPreset; #endregion #region Properties private bool IsValidSetup => currentCamera != null && inputHandler != null; public CameraPreset CurrentPreset => currentPreset; #endregion #region Unity Messages private void Awake() { try { InitializeInputHandler(); InitializeRawInput(); InitializeCameraPresets(); } catch (System.Exception e) { Debug.LogError($"초기화 중 오류 발생: {e.Message}"); } } private void OnDestroy() { try { CleanupRawInput(); } catch (System.Exception e) { Debug.LogError($"정리 중 오류 발생: {e.Message}"); } } private void Update() { if (!IsValidSetup) return; try { UpdateHotkeyRecording(); HandleCameraControls(); HandleUnityHotkeys(); } catch (System.Exception e) { Debug.LogError($"카메라 제어 중 오류 발생: {e.Message}"); } } private void UpdateHotkeyRecording() { foreach (var preset in cameraPresets) { if (preset?.hotkey?.isRecording == true) { preset.hotkey.UpdateRecording(); } } } #endregion #region Initialization private void InitializeInputHandler() { inputHandler = GetComponent(); if (inputHandler == null) { inputHandler = gameObject.AddComponent(); Debug.Log("InputHandler 컴포넌트가 자동으로 추가되었습니다."); } } private void InitializeRawInput() { if (!RawInput.IsRunning) { RawInput.Start(); RawInput.WorkInBackground = true; } RawInput.OnKeyDown += HandleKeyDown; } private void InitializeCameraPresets() { if (cameraPresets == null) { cameraPresets = new List(); Debug.LogWarning("카메라 프리셋 리스트가 null이어서 새로 생성되었습니다."); } if (!cameraPresets.Any()) { Debug.LogWarning("카메라 프리셋이 설정되어 있지 않습니다."); return; } foreach (var preset in cameraPresets.Where(p => p?.hotkey != null)) { preset.hotkey.InitializeUnityKeys(); } SwitchToCamera(0); } private void CleanupRawInput() { if (RawInput.IsRunning) { RawInput.OnKeyDown -= HandleKeyDown; RawInput.Stop(); } } #endregion #region Input Handling private void HandleKeyDown(RawKey key) { if (key == default(RawKey)) return; TryActivatePresetByInput(preset => { if (preset?.hotkey == null) return false; bool contains = preset.hotkey.rawKeys?.Contains(key) == true; bool triggered = preset.hotkey.IsTriggered(); return contains && triggered; }); } private void HandleUnityHotkeys() { if (!Input.anyKeyDown) return; TryActivatePresetByInput(preset => { if (preset?.hotkey == null) return false; return preset.hotkey.IsTriggered(); }); } private void TryActivatePresetByInput(System.Func predicate) { try { var matchingPreset = cameraPresets? .FirstOrDefault(predicate); if (matchingPreset != null) { SwitchToCamera(cameraPresets.IndexOf(matchingPreset)); } } catch (System.Exception e) { Debug.LogError($"프리셋 활성화 중 오류 발생: {e.Message}"); } } #endregion #region Camera Controls private void HandleCameraControls() { if (!IsValidSetup) return; Transform cameraTransform = currentCamera.transform; if (cameraTransform == null) return; HandleRotation(cameraTransform); HandlePanning(cameraTransform); HandleZooming(cameraTransform); HandleOrbiting(cameraTransform); } private void HandleRotation(Transform cameraTransform) { if (!inputHandler.IsRightMouseHeld()) return; Vector2 lookDelta = inputHandler.GetLookDelta(); if (lookDelta.sqrMagnitude < float.Epsilon) return; var rotation = new Vector3(-lookDelta.y, lookDelta.x, 0f) * rotationSensitivity; cameraTransform.rotation *= Quaternion.Euler(rotation); } private void HandlePanning(Transform cameraTransform) { if (!inputHandler.IsMiddleMouseHeld()) return; Vector2 panDelta = inputHandler.GetLookDelta(); if (panDelta.sqrMagnitude < float.Epsilon) return; Vector3 panMovement = cameraTransform.right * -panDelta.x * panSpeed + cameraTransform.up * -panDelta.y * panSpeed; cameraTransform.position += panMovement * Time.deltaTime; } private void HandleZooming(Transform cameraTransform) { float zoomDelta = inputHandler.GetZoomDelta(); if (Mathf.Abs(zoomDelta) <= 0.1f) return; cameraTransform.position += cameraTransform.forward * zoomDelta * zoomSpeed * Time.deltaTime; } private void HandleOrbiting(Transform cameraTransform) { if (!inputHandler.IsOrbitActive()) return; if (inputHandler.IsOrbitStarting()) { UpdateOrbitTarget(cameraTransform); } PerformOrbitRotation(cameraTransform); } private void UpdateOrbitTarget(Transform cameraTransform) { if (Camera.main == null) { targetPosition = cameraTransform.position + cameraTransform.forward * 10f; return; } Ray ray = Camera.main.ScreenPointToRay(inputHandler.GetMousePosition()); targetPosition = Physics.Raycast(ray, out RaycastHit hit) ? hit.point : cameraTransform.position + cameraTransform.forward * 10f; } private void PerformOrbitRotation(Transform cameraTransform) { Vector2 orbitDelta = inputHandler.GetLookDelta(); if (orbitDelta.sqrMagnitude < float.Epsilon) return; Vector3 directionToTarget = targetPosition - cameraTransform.position; float distance = directionToTarget.magnitude; // 수평 회전 cameraTransform.RotateAround(targetPosition, Vector3.up, orbitDelta.x * orbitSpeed); // 수직 회전 제한 추가 Vector3 right = Vector3.Cross(Vector3.up, directionToTarget.normalized); float currentAngle = Vector3.Angle(Vector3.up, directionToTarget); float newAngle = currentAngle - orbitDelta.y * orbitSpeed; if (newAngle > 5f && newAngle < 175f) { cameraTransform.RotateAround(targetPosition, right, -orbitDelta.y * orbitSpeed); } // 거리 유지 cameraTransform.position = targetPosition - cameraTransform.forward * distance; } #endregion #region Camera Management public void SwitchToCamera(int index) { if (!IsValidCameraIndex(index)) return; var newPreset = cameraPresets[index]; if (!newPreset.IsValid()) { Debug.LogWarning($"프리셋 {index}의 카메라가 설정되지 않았습니다."); return; } var oldPreset = currentPreset; currentPreset = newPreset; UpdateCameraPriorities(newPreset.virtualCamera); try { OnCameraChanged?.Invoke(oldPreset, newPreset); } catch (System.Exception e) { Debug.LogError($"카메라 변경 이벤트 처리 중 오류 발생: {e.Message}"); } Debug.Log($"카메라 전환: {newPreset.presetName}"); } private bool IsValidCameraIndex(int index) => cameraPresets != null && index >= 0 && index < cameraPresets.Count; private void UpdateCameraPriorities(CinemachineCamera newCamera) { if (newCamera == null) { Debug.LogError("새로운 카메라가 null입니다."); return; } if (currentCamera != null) { currentCamera.Priority = 0; } currentCamera = newCamera; currentCamera.Priority = 10; } #endregion #region Hotkey Management public void StartRecordingHotkey(int presetIndex) { if (!IsValidCameraIndex(presetIndex)) return; var preset = cameraPresets[presetIndex]; if (!preset.IsValid()) { Debug.LogWarning($"프리셋 {presetIndex}가 유효하지 않습니다."); return; } // 다른 프리셋의 레코딩 중지 foreach (var otherPreset in cameraPresets) { if (otherPreset?.hotkey?.isRecording == true) { otherPreset.hotkey.StopRecording(); } } preset.hotkey.StartRecording(); } public void StopRecordingHotkey(int presetIndex) { if (!IsValidCameraIndex(presetIndex)) return; var preset = cameraPresets[presetIndex]; if (preset?.hotkey?.isRecording == true) { preset.hotkey.StopRecording(); } } #endregion }