using UnityEngine; using System.Collections.Generic; using UnityRawInput; using System.Linq; using Unity.Cinemachine; public class CameraManager : MonoBehaviour, IController { #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() { 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); } } } public bool IsTriggered() { if (isRecording) return false; if (rawKeys == null || !rawKeys.Any()) return false; bool allRawKeysPressed = rawKeys.All(key => RawInput.IsKeyDown(key)); if (allRawKeysPressed) return true; if (unityKeys.Any()) { return unityKeys.All(key => Input.GetKey(key)); } 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")] private float rotationSensitivity = 2f; private float panSpeed = 0.02f; private float zoomSpeed = 0.1f; private float orbitSpeed = 10f; private CinemachineCamera currentCamera; private InputHandler inputHandler; private CameraPreset currentPreset; private Vector3 rotationCenter = Vector3.zero; private Vector3 rotationStartPosition; private bool isRotating = false; // 초기 카메라 상태 저장 private Vector3 initialPosition; private Quaternion initialRotation; private bool isInitialStateSet = false; #endregion #region Properties private bool IsValidSetup => currentCamera != null && inputHandler != null; public CameraPreset CurrentPreset => currentPreset; #endregion #region Unity Messages private void Awake() { InitializeInputHandler(); InitializeRawInput(); InitializeCameraPresets(); } private void OnDestroy() { if (RawInput.IsRunning) { RawInput.OnKeyDown -= HandleRawKeyDown; RawInput.Stop(); } } private void Update() { if (!IsValidSetup) return; UpdateHotkeyRecording(); HandleCameraControls(); HandleHotkeys(); } 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(); } } private void InitializeRawInput() { if (!RawInput.IsRunning) { RawInput.Start(); RawInput.WorkInBackground = true; } RawInput.OnKeyDown += HandleRawKeyDown; } private void InitializeCameraPresets() { if (cameraPresets == null) { cameraPresets = new List(); } if (!cameraPresets.Any()) { return; } foreach (var preset in cameraPresets.Where(p => p?.hotkey != null)) { preset.hotkey.InitializeUnityKeys(); } Set(0); // 초기 카메라 상태 저장 if (currentCamera != null && !isInitialStateSet) { initialPosition = currentCamera.transform.position; initialRotation = currentCamera.transform.rotation; isInitialStateSet = true; } } #endregion #region Input Handling private void HandleRawKeyDown(RawKey key) { if (key == default(RawKey)) return; TryActivatePresetByInput(preset => { if (preset?.hotkey == null) return false; return preset.hotkey.IsTriggered(); }); } private void HandleHotkeys() { if (Input.anyKeyDown) { TryActivatePresetByInput(preset => { if (preset?.hotkey == null) return false; return preset.hotkey.IsTriggered(); }); } } private void TryActivatePresetByInput(System.Func predicate) { var matchingPreset = cameraPresets?.FirstOrDefault(predicate); if (matchingPreset != null) { Set(cameraPresets.IndexOf(matchingPreset)); } } #endregion #region Camera Controls private void HandleCameraControls() { if (!IsValidSetup) return; var virtualCamera = currentCamera; if (virtualCamera == null) return; // Alt+Q로 초기 위치로 복원 if (Input.GetKey(KeyCode.LeftAlt) && Input.GetKeyDown(KeyCode.Q)) { RestoreInitialCameraState(); return; } HandleRotation(virtualCamera); HandlePanning(virtualCamera); HandleZooming(virtualCamera); HandleOrbiting(virtualCamera); } private void HandleRotation(CinemachineCamera virtualCamera) { Transform cameraTransform = virtualCamera.transform; if (inputHandler.IsRightMouseHeld()) { if (!isRotating) { // 회전 시작 시 현재 위치 저장 isRotating = true; rotationStartPosition = cameraTransform.position; // 현재 카메라의 y값을 기준으로 회전 중심점 설정 rotationCenter = new Vector3(0f, rotationStartPosition.y, 0f); } Vector2 lookDelta = inputHandler.GetLookDelta(); if (lookDelta.sqrMagnitude < float.Epsilon) return; // 현재 회전값을 오일러 각도로 가져오기 Vector3 currentEuler = cameraTransform.eulerAngles; // X축 회전값을 -80도에서 80도 사이로 제한하기 위해 360도 형식에서 변환 float currentX = currentEuler.x; if (currentX > 180f) currentX -= 360f; // 새로운 회전값 계산 float newX = currentX - lookDelta.y * rotationSensitivity; float newY = currentEuler.y + lookDelta.x * rotationSensitivity; // X축 회전 제한 (-80도 ~ 80도) newX = Mathf.Clamp(newX, -80f, 80f); // 회전 적용 Quaternion targetRotation = Quaternion.Euler(newX, newY, 0f); cameraTransform.rotation = targetRotation; // 회전 시작 위치를 기준으로 회전 (y값 유지) Vector3 relativePosition = rotationStartPosition - rotationCenter; Vector3 rotatedPosition = targetRotation * new Vector3(relativePosition.x, 0f, relativePosition.z); Vector3 newPosition = rotationCenter + rotatedPosition; cameraTransform.position = newPosition; } else { isRotating = false; } } private void HandlePanning(CinemachineCamera virtualCamera) { if (!inputHandler.IsMiddleMouseHeld()) return; Vector2 panDelta = inputHandler.GetLookDelta(); if (panDelta.sqrMagnitude < float.Epsilon) return; Transform cameraTransform = virtualCamera.transform; // 이동 적용 (카메라의 right와 up 방향으로 이동) Vector3 right = cameraTransform.right * -panDelta.x * panSpeed; Vector3 up = cameraTransform.up * -panDelta.y * panSpeed; cameraTransform.position += right + up; } private void HandleZooming(CinemachineCamera virtualCamera) { if (inputHandler.IsZoomActive()) { // Ctrl + 좌클릭으로 줌 Vector2 lookDelta = inputHandler.GetLookDelta(); if (lookDelta.sqrMagnitude < float.Epsilon) return; Transform cameraTransform = virtualCamera.transform; Vector3 forward = cameraTransform.forward * lookDelta.y * zoomSpeed * 10f; cameraTransform.position += forward; } else { // 마우스 휠로 줌 float zoomDelta = inputHandler.GetZoomDelta(); if (Mathf.Abs(zoomDelta) <= 0.1f) return; Transform cameraTransform = virtualCamera.transform; Vector3 forward = cameraTransform.forward * zoomDelta * zoomSpeed; cameraTransform.position += forward; } } private void HandleOrbiting(CinemachineCamera virtualCamera) { if (!inputHandler.IsOrbitActive()) return; Vector2 orbitDelta = inputHandler.GetLookDelta(); if (orbitDelta.sqrMagnitude < float.Epsilon) return; Transform cameraTransform = virtualCamera.transform; // 현재 회전값을 오일러 각도로 가져오기 Vector3 currentEuler = cameraTransform.eulerAngles; // X축 회전값을 -80도에서 80도 사이로 제한하기 위해 360도 형식에서 변환 float currentX = currentEuler.x; if (currentX > 180f) currentX -= 360f; // 새로운 회전값 계산 float newX = currentX - orbitDelta.y * orbitSpeed; float newY = currentEuler.y + orbitDelta.x * orbitSpeed; // X축 회전 제한 (-80도 ~ 80도) newX = Mathf.Clamp(newX, -80f, 80f); // 회전 적용 Quaternion targetRotation = Quaternion.Euler(newX, newY, 0f); cameraTransform.rotation = targetRotation; // 원점으로부터의 거리 유지 float distance = cameraTransform.position.magnitude; // 새로운 위치 계산 (원점으로부터의 거리 유지) Vector3 newPosition = targetRotation * Vector3.back * distance; cameraTransform.position = newPosition; } private void RestoreInitialCameraState() { if (!isInitialStateSet || currentCamera == null) return; currentCamera.transform.position = initialPosition; currentCamera.transform.rotation = initialRotation; // 회전 중심점 초기화 rotationCenter = new Vector3(0f, initialPosition.y, 0f); } #endregion #region Camera Management public void Set(int index) { if (cameraPresets == null || index < 0 || index >= cameraPresets.Count) return; var newPreset = cameraPresets[index]; if (!newPreset.IsValid()) return; var oldPreset = currentPreset; currentPreset = newPreset; UpdateCameraPriorities(newPreset.virtualCamera); OnCameraChanged?.Invoke(oldPreset, newPreset); } private void UpdateCameraPriorities(CinemachineCamera newCamera) { if (newCamera == null) return; if (currentCamera != null) { currentCamera.Priority = 0; } currentCamera = newCamera; currentCamera.Priority = 10; } #endregion #region Hotkey Management public void StartRecordingHotkey(int presetIndex) { if (cameraPresets == null || presetIndex < 0 || presetIndex >= cameraPresets.Count) return; var preset = cameraPresets[presetIndex]; if (!preset.IsValid()) return; foreach (var otherPreset in cameraPresets) { if (otherPreset?.hotkey?.isRecording == true) { otherPreset.hotkey.StopRecording(); } } preset.hotkey.StartRecording(); } public void StopRecordingHotkey(int presetIndex) { if (cameraPresets == null || presetIndex < 0 || presetIndex >= cameraPresets.Count) return; var preset = cameraPresets[presetIndex]; if (preset?.hotkey?.isRecording == true) { preset.hotkey.StopRecording(); } } #endregion }