820 lines
24 KiB
C#
820 lines
24 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using UnityRawInput;
|
|
using System.Linq;
|
|
using Unity.Cinemachine;
|
|
using Streamingle;
|
|
using KindRetargeting;
|
|
|
|
public class CameraManager : MonoBehaviour, IController
|
|
{
|
|
#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()
|
|
{
|
|
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;
|
|
|
|
// 프리셋별 초기 상태 저장
|
|
[System.NonSerialized] public Vector3 savedPosition;
|
|
[System.NonSerialized] public Quaternion savedRotation;
|
|
[System.NonSerialized] public bool hasSavedState = false;
|
|
|
|
public CameraPreset(CinemachineCamera camera)
|
|
{
|
|
virtualCamera = camera;
|
|
presetName = camera?.gameObject.name ?? "Unnamed Camera";
|
|
hotkey = new HotkeyCommand();
|
|
}
|
|
|
|
public bool IsValid() => virtualCamera != null && hotkey != null;
|
|
|
|
public void SaveCurrentState()
|
|
{
|
|
if (virtualCamera == null) return;
|
|
savedPosition = virtualCamera.transform.position;
|
|
savedRotation = virtualCamera.transform.rotation;
|
|
hasSavedState = true;
|
|
}
|
|
|
|
public void RestoreSavedState()
|
|
{
|
|
if (!hasSavedState || virtualCamera == null) return;
|
|
virtualCamera.transform.position = savedPosition;
|
|
virtualCamera.transform.rotation = savedRotation;
|
|
}
|
|
}
|
|
#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.5f, 10f)] private float rotationSensitivity = 2f;
|
|
[SerializeField, Range(0.005f, 0.1f)] private float panSpeed = 0.02f;
|
|
[SerializeField, Range(0.05f, 0.5f)] private float zoomSpeed = 0.1f;
|
|
[SerializeField, Range(1f, 20f)] private float orbitSpeed = 10f;
|
|
|
|
[Header("Smoothing")]
|
|
[SerializeField, Range(0f, 0.95f)] private float movementSmoothing = 0.1f;
|
|
[SerializeField, Range(0f, 0.95f)] private float rotationSmoothing = 0.1f;
|
|
|
|
[Header("Zoom Limits")]
|
|
[SerializeField] private float minZoomDistance = 0.5f;
|
|
[SerializeField] private float maxZoomDistance = 50f;
|
|
|
|
[Header("Rotation Target")]
|
|
[Tooltip("체크하면 아바타 머리를 자동으로 찾아 회전 중심점으로 사용합니다.")]
|
|
[SerializeField] private bool useAvatarHeadAsTarget = true;
|
|
[Tooltip("수동으로 회전 중심점을 지정합니다. (useAvatarHeadAsTarget이 false일 때 사용)")]
|
|
[SerializeField] private Transform manualRotationTarget;
|
|
|
|
private CinemachineCamera currentCamera;
|
|
private InputHandler inputHandler;
|
|
private CameraPreset currentPreset;
|
|
|
|
// 오빗 카메라 상태 (각도 기반)
|
|
private float horizontalAngle;
|
|
private float verticalAngle;
|
|
private float currentDistance;
|
|
private Vector3 focusPoint;
|
|
|
|
// 타겟 값 (스무딩용)
|
|
private float targetHorizontalAngle;
|
|
private float targetVerticalAngle;
|
|
private float targetDistance;
|
|
private Vector3 targetFocusPoint;
|
|
|
|
// 아바타 머리 추적
|
|
private Transform avatarHeadTransform;
|
|
|
|
// 스트림덱 연동
|
|
private StreamDeckServerManager streamDeckManager;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
private bool IsValidSetup => currentCamera != null && inputHandler != null;
|
|
public CameraPreset CurrentPreset => currentPreset;
|
|
#endregion
|
|
|
|
#region Unity Messages
|
|
private void Awake()
|
|
{
|
|
InitializeInputHandler();
|
|
InitializeRawInput();
|
|
InitializeCameraPresets();
|
|
|
|
// StreamDeckServerManager 찾기
|
|
streamDeckManager = FindAnyObjectByType<StreamDeckServerManager>();
|
|
if (streamDeckManager == null)
|
|
{
|
|
Debug.LogWarning("[CameraManager] StreamDeckServerManager를 찾을 수 없습니다. 스트림덱 연동이 비활성화됩니다.");
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
// Start에서 아바타 머리 다시 찾기 (다른 스크립트들이 초기화된 후)
|
|
if (useAvatarHeadAsTarget && avatarHeadTransform == null)
|
|
{
|
|
FindAvatarHead();
|
|
}
|
|
}
|
|
|
|
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<InputHandler>();
|
|
if (inputHandler == null)
|
|
{
|
|
inputHandler = gameObject.AddComponent<InputHandler>();
|
|
}
|
|
}
|
|
|
|
private void InitializeRawInput()
|
|
{
|
|
if (!RawInput.IsRunning)
|
|
{
|
|
RawInput.Start();
|
|
RawInput.WorkInBackground = true;
|
|
}
|
|
// 중복 구독 방지를 위해 먼저 해제 후 구독
|
|
RawInput.OnKeyDown -= HandleRawKeyDown;
|
|
RawInput.OnKeyDown += HandleRawKeyDown;
|
|
}
|
|
|
|
private void InitializeCameraPresets()
|
|
{
|
|
if (cameraPresets == null)
|
|
{
|
|
cameraPresets = new List<CameraPreset>();
|
|
}
|
|
|
|
if (!cameraPresets.Any())
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var preset in cameraPresets.Where(p => p?.hotkey != null))
|
|
{
|
|
preset.hotkey.InitializeUnityKeys();
|
|
}
|
|
|
|
// 모든 프리셋의 초기 상태 저장
|
|
foreach (var preset in cameraPresets.Where(p => p?.IsValid() == true))
|
|
{
|
|
if (!preset.hasSavedState)
|
|
{
|
|
preset.SaveCurrentState();
|
|
}
|
|
}
|
|
|
|
// 아바타 머리 찾기
|
|
FindAvatarHead();
|
|
|
|
Set(0);
|
|
}
|
|
|
|
private void FindAvatarHead()
|
|
{
|
|
if (!useAvatarHeadAsTarget) return;
|
|
|
|
// CustomRetargetingScript를 가진 아바타 찾기
|
|
var retargetingScripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
|
|
foreach (var script in retargetingScripts)
|
|
{
|
|
if (script == null || !script.gameObject.activeInHierarchy) continue;
|
|
|
|
// targetAnimator가 설정되어 있으면 사용
|
|
Animator animator = script.targetAnimator;
|
|
|
|
// targetAnimator가 null이면 같은 GameObject의 Animator 시도
|
|
if (animator == null)
|
|
{
|
|
animator = script.GetComponent<Animator>();
|
|
}
|
|
|
|
if (animator != null)
|
|
{
|
|
avatarHeadTransform = animator.GetBoneTransform(HumanBodyBones.Head);
|
|
if (avatarHeadTransform != null)
|
|
{
|
|
Debug.Log($"[CameraManager] 아바타 머리를 회전 중심점으로 설정: {avatarHeadTransform.name}");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Debug.LogWarning("[CameraManager] 활성화된 아바타의 Head 본을 찾을 수 없습니다. 수동 타겟을 사용하거나 원점을 사용합니다.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 회전 중심점을 반환합니다.
|
|
/// 우선순위: 아바타 머리 > 수동 타겟 > 원점
|
|
/// </summary>
|
|
private Vector3 GetRotationCenter()
|
|
{
|
|
if (useAvatarHeadAsTarget && avatarHeadTransform != null)
|
|
{
|
|
return avatarHeadTransform.position;
|
|
}
|
|
else if (manualRotationTarget != null)
|
|
{
|
|
return manualRotationTarget.position;
|
|
}
|
|
return Vector3.zero;
|
|
}
|
|
|
|
|
|
#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<CameraPreset, bool> predicate)
|
|
{
|
|
var matchingPreset = cameraPresets?.FirstOrDefault(predicate);
|
|
if (matchingPreset != null)
|
|
{
|
|
Set(cameraPresets.IndexOf(matchingPreset));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Camera Controls
|
|
|
|
/// <summary>
|
|
/// 현재 카메라 위치에서 각도와 거리를 초기화합니다.
|
|
/// </summary>
|
|
private void InitializeOrbitState()
|
|
{
|
|
if (currentCamera == null) return;
|
|
|
|
focusPoint = GetRotationCenter();
|
|
targetFocusPoint = focusPoint;
|
|
|
|
Vector3 direction = currentCamera.transform.position - focusPoint;
|
|
currentDistance = direction.magnitude;
|
|
targetDistance = currentDistance;
|
|
|
|
if (currentDistance > 0.01f)
|
|
{
|
|
direction.Normalize();
|
|
horizontalAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
|
|
verticalAngle = Mathf.Asin(Mathf.Clamp(direction.y, -1f, 1f)) * Mathf.Rad2Deg;
|
|
}
|
|
|
|
targetHorizontalAngle = horizontalAngle;
|
|
targetVerticalAngle = verticalAngle;
|
|
}
|
|
|
|
private void HandleCameraControls()
|
|
{
|
|
if (!IsValidSetup) return;
|
|
if (currentCamera == null) return;
|
|
|
|
// Alt+Q로 초기 위치로 복원
|
|
if (Input.GetKey(KeyCode.LeftAlt) && Input.GetKeyDown(KeyCode.Q))
|
|
{
|
|
RestoreInitialCameraState();
|
|
return;
|
|
}
|
|
|
|
HandleInput();
|
|
UpdateCameraPosition();
|
|
}
|
|
|
|
private void HandleInput()
|
|
{
|
|
// 입력 우선순위 처리: Orbit > AltRightZoom > Zoom > Rotation > Panning
|
|
if (inputHandler.IsOrbitActive())
|
|
{
|
|
HandleOrbiting();
|
|
}
|
|
else if (inputHandler.IsCtrlRightZoomActive())
|
|
{
|
|
HandleCtrlRightZoom();
|
|
}
|
|
else if (inputHandler.IsZoomActive())
|
|
{
|
|
HandleDragZoom();
|
|
}
|
|
else if (inputHandler.IsRightMouseHeld())
|
|
{
|
|
HandleRotation();
|
|
}
|
|
else if (inputHandler.IsMiddleMouseHeld())
|
|
{
|
|
HandlePanning();
|
|
}
|
|
|
|
// 휠 줌은 항상 처리
|
|
HandleWheelZoom();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 우클릭 드래그: 수평 + 수직 회전 (현재 비활성화 - Alt+우클릭 사용)
|
|
/// </summary>
|
|
private void HandleRotation()
|
|
{
|
|
// 우클릭만으로는 회전하지 않음 - Alt+우클릭(Orbit)으로 대체
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alt + 우클릭: 자유 궤도 회전 (X, Y축)
|
|
/// </summary>
|
|
private void HandleOrbiting()
|
|
{
|
|
Vector2 delta = inputHandler.GetLookDelta();
|
|
if (delta.sqrMagnitude < float.Epsilon) return;
|
|
|
|
// 회전 속도
|
|
targetHorizontalAngle += delta.x * rotationSensitivity;
|
|
targetVerticalAngle -= delta.y * rotationSensitivity;
|
|
targetVerticalAngle = Mathf.Clamp(targetVerticalAngle, -80f, 80f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 휠클릭 드래그: 패닝 (초점 이동)
|
|
/// </summary>
|
|
private void HandlePanning()
|
|
{
|
|
Vector2 delta = inputHandler.GetLookDelta();
|
|
if (delta.sqrMagnitude < float.Epsilon) return;
|
|
|
|
// 거리에 비례하여 패닝 속도 조절 - 0.2배
|
|
float speedMultiplier = targetDistance * panSpeed * 0.175f;
|
|
|
|
// 카메라의 로컬 축 기준으로 초점 이동
|
|
Vector3 right = currentCamera.transform.right;
|
|
Vector3 up = currentCamera.transform.up;
|
|
|
|
Vector3 panOffset = (-right * delta.x - up * delta.y) * speedMultiplier;
|
|
targetFocusPoint += panOffset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 마우스 휠: 줌
|
|
/// </summary>
|
|
private void HandleWheelZoom()
|
|
{
|
|
float scroll = inputHandler.GetZoomDelta();
|
|
if (Mathf.Abs(scroll) < 0.01f) return;
|
|
|
|
// 거리에 비례하여 줌 속도 조절 (더 자연스러운 느낌) - 0.4배
|
|
float zoomDelta = scroll * zoomSpeed * targetDistance * 0.4f;
|
|
targetDistance -= zoomDelta;
|
|
targetDistance = Mathf.Clamp(targetDistance, minZoomDistance, maxZoomDistance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ctrl + 좌클릭 드래그: 줌
|
|
/// </summary>
|
|
private void HandleDragZoom()
|
|
{
|
|
Vector2 delta = inputHandler.GetLookDelta();
|
|
if (delta.sqrMagnitude < float.Epsilon) return;
|
|
|
|
float zoomDelta = delta.y * zoomSpeed * targetDistance * 0.5f;
|
|
targetDistance -= zoomDelta;
|
|
targetDistance = Mathf.Clamp(targetDistance, minZoomDistance, maxZoomDistance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ctrl + 우클릭 드래그: 줌 (밀기 = 확대, 당기기 = 축소)
|
|
/// </summary>
|
|
private void HandleCtrlRightZoom()
|
|
{
|
|
Vector2 delta = inputHandler.GetLookDelta();
|
|
if (delta.sqrMagnitude < float.Epsilon) return;
|
|
|
|
// 위로 밀면 확대 (거리 감소), 아래로 밀면 축소 (거리 증가)
|
|
float zoomDelta = delta.y * zoomSpeed * targetDistance * 0.5f;
|
|
targetDistance -= zoomDelta;
|
|
targetDistance = Mathf.Clamp(targetDistance, minZoomDistance, maxZoomDistance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스무딩을 적용하여 카메라 위치 업데이트
|
|
/// </summary>
|
|
private void UpdateCameraPosition()
|
|
{
|
|
float dt = Time.deltaTime;
|
|
|
|
// 스무딩 적용
|
|
if (movementSmoothing > 0.01f)
|
|
{
|
|
float smoothSpeed = (1f - movementSmoothing) * 15f;
|
|
horizontalAngle = Mathf.Lerp(horizontalAngle, targetHorizontalAngle, dt * smoothSpeed);
|
|
verticalAngle = Mathf.Lerp(verticalAngle, targetVerticalAngle, dt * smoothSpeed);
|
|
currentDistance = Mathf.Lerp(currentDistance, targetDistance, dt * smoothSpeed);
|
|
focusPoint = Vector3.Lerp(focusPoint, targetFocusPoint, dt * smoothSpeed);
|
|
}
|
|
else
|
|
{
|
|
horizontalAngle = targetHorizontalAngle;
|
|
verticalAngle = targetVerticalAngle;
|
|
currentDistance = targetDistance;
|
|
focusPoint = targetFocusPoint;
|
|
}
|
|
|
|
// 구면 좌표계에서 카메라 위치 계산
|
|
float horizontalRad = horizontalAngle * Mathf.Deg2Rad;
|
|
float verticalRad = verticalAngle * Mathf.Deg2Rad;
|
|
|
|
Vector3 offset = new Vector3(
|
|
Mathf.Sin(horizontalRad) * Mathf.Cos(verticalRad),
|
|
Mathf.Sin(verticalRad),
|
|
Mathf.Cos(horizontalRad) * Mathf.Cos(verticalRad)
|
|
) * currentDistance;
|
|
|
|
currentCamera.transform.position = focusPoint + offset;
|
|
currentCamera.transform.LookAt(focusPoint);
|
|
}
|
|
|
|
private void RestoreInitialCameraState()
|
|
{
|
|
if (currentPreset == null || !currentPreset.hasSavedState) return;
|
|
|
|
currentPreset.RestoreSavedState();
|
|
|
|
// 각도 상태 다시 초기화
|
|
InitializeOrbitState();
|
|
}
|
|
#endregion
|
|
|
|
#region Camera Management
|
|
public void Set(int index)
|
|
{
|
|
Debug.Log($"[CameraManager] 카메라 {index}번으로 전환 시작 (총 {cameraPresets?.Count ?? 0}개)");
|
|
|
|
if (cameraPresets == null)
|
|
{
|
|
Debug.LogError("[CameraManager] cameraPresets가 null입니다!");
|
|
return;
|
|
}
|
|
|
|
if (index < 0 || index >= cameraPresets.Count)
|
|
{
|
|
Debug.LogError($"[CameraManager] 잘못된 인덱스: {index}, 유효 범위: 0-{cameraPresets.Count - 1}");
|
|
return;
|
|
}
|
|
|
|
var newPreset = cameraPresets[index];
|
|
|
|
if (!newPreset.IsValid())
|
|
{
|
|
Debug.LogError($"[CameraManager] 프리셋이 유효하지 않습니다 - 인덱스: {index}");
|
|
return;
|
|
}
|
|
|
|
var oldPreset = currentPreset;
|
|
var newCameraName = newPreset.virtualCamera?.gameObject.name ?? "Unknown";
|
|
|
|
currentPreset = newPreset;
|
|
UpdateCameraPriorities(newPreset.virtualCamera);
|
|
|
|
// 오빗 상태 초기화 (각도, 거리 계산)
|
|
InitializeOrbitState();
|
|
|
|
OnCameraChanged?.Invoke(oldPreset, newPreset);
|
|
|
|
// 스트림덱에 카메라 변경 알림 전송
|
|
if (streamDeckManager != null)
|
|
{
|
|
streamDeckManager.NotifyCameraChanged();
|
|
}
|
|
|
|
Debug.Log($"[CameraManager] 카메라 전환 완료: {newCameraName}");
|
|
}
|
|
|
|
private void UpdateCameraPriorities(CinemachineCamera newCamera)
|
|
{
|
|
if (newCamera == null)
|
|
{
|
|
Debug.LogError("[CameraManager] 새 카메라가 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
|
|
|
|
#region Camera Data
|
|
// 카메라 목록 데이터 반환 (HTTP 요청 시 직접 호출됨)
|
|
public CameraListData GetCameraListData()
|
|
{
|
|
var presetList = cameraPresets.Select((preset, index) => new CameraPresetData
|
|
{
|
|
index = index,
|
|
name = preset?.virtualCamera?.gameObject.name ?? $"Camera {index}",
|
|
isActive = currentPreset == preset,
|
|
hotkey = preset?.hotkey?.ToString() ?? "설정되지 않음"
|
|
}).ToArray();
|
|
|
|
return new CameraListData
|
|
{
|
|
camera_count = cameraPresets.Count,
|
|
presets = presetList,
|
|
current_index = currentPreset != null ? cameraPresets.IndexOf(currentPreset) : -1
|
|
};
|
|
}
|
|
|
|
// 현재 카메라 상태 데이터 반환
|
|
public CameraStateData GetCurrentCameraState()
|
|
{
|
|
if (currentPreset == null) return null;
|
|
|
|
var currentIndex = cameraPresets.IndexOf(currentPreset);
|
|
return new CameraStateData
|
|
{
|
|
current_index = currentIndex,
|
|
camera_name = currentPreset.virtualCamera?.gameObject.name ?? "Unknown",
|
|
preset_name = currentPreset.presetName,
|
|
total_cameras = cameraPresets.Count
|
|
};
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class CameraPresetData
|
|
{
|
|
public int index;
|
|
public string name;
|
|
public bool isActive;
|
|
public string hotkey;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class CameraListData
|
|
{
|
|
public int camera_count;
|
|
public CameraPresetData[] presets;
|
|
public int current_index;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class CameraStateData
|
|
{
|
|
public int current_index;
|
|
public string camera_name;
|
|
public string preset_name;
|
|
public int total_cameras;
|
|
}
|
|
#endregion
|
|
|
|
#region IController Implementation
|
|
public string GetControllerId()
|
|
{
|
|
return "camera_controller";
|
|
}
|
|
|
|
public string GetControllerName()
|
|
{
|
|
return "카메라 컨트롤러";
|
|
}
|
|
|
|
public object GetControllerData()
|
|
{
|
|
return GetCameraListData();
|
|
}
|
|
|
|
public void ExecuteAction(string actionId, object parameters)
|
|
{
|
|
switch (actionId)
|
|
{
|
|
case "switch_camera":
|
|
if (parameters is int cameraIndex)
|
|
{
|
|
Set(cameraIndex);
|
|
}
|
|
else if (parameters is System.Dynamic.ExpandoObject expando)
|
|
{
|
|
var dict = (IDictionary<string, object>)expando;
|
|
if (dict.ContainsKey("camera_index") && dict["camera_index"] is int index)
|
|
{
|
|
Set(index);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "get_camera_list":
|
|
// 카메라 목록은 GetControllerData()에서 반환됨
|
|
break;
|
|
|
|
default:
|
|
Debug.LogWarning($"[CameraManager] 알 수 없는 액션: {actionId}");
|
|
break;
|
|
}
|
|
}
|
|
#endregion
|
|
} |