527 lines
15 KiB
C#
527 lines
15 KiB
C#
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<KeyCode, RawKey> _mapping;
|
|
|
|
static KeyMapping()
|
|
{
|
|
_mapping = new Dictionary<KeyCode, RawKey>(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<RawKey> rawKeys = new List<RawKey>();
|
|
[System.NonSerialized] private List<KeyCode> unityKeys = new List<KeyCode>();
|
|
[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<KeyCode>();
|
|
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<CameraPreset> cameraPresets = new List<CameraPreset>();
|
|
|
|
[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<InputHandler>();
|
|
if (inputHandler == null)
|
|
{
|
|
inputHandler = gameObject.AddComponent<InputHandler>();
|
|
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<CameraPreset>();
|
|
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<CameraPreset, bool> 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
|
|
} |