512 lines
17 KiB
C#

using UnityEngine;
using UnityEditor;
using UnityRawInput;
using System.Collections.Generic;
using System.Linq;
using Unity.Cinemachine;
[CustomEditor(typeof(CameraManager))]
public class CameraManagerEditor : Editor
{
private HashSet<KeyCode> currentKeys = new HashSet<KeyCode>();
private bool isApplicationPlaying;
private bool isListening = false;
// Foldout 상태
private bool showCameraPresets = true;
private bool showControlSettings = true;
private bool showSmoothingSettings = true;
private bool showZoomSettings = true;
private bool showRotationTarget = true;
private bool showBlendSettings = true;
// SerializedProperties
private SerializedProperty rotationSensitivityProp;
private SerializedProperty panSpeedProp;
private SerializedProperty zoomSpeedProp;
private SerializedProperty orbitSpeedProp;
private SerializedProperty movementSmoothingProp;
private SerializedProperty rotationSmoothingProp;
private SerializedProperty minZoomDistanceProp;
private SerializedProperty maxZoomDistanceProp;
private SerializedProperty useAvatarHeadAsTargetProp;
private SerializedProperty manualRotationTargetProp;
private SerializedProperty useBlendTransitionProp;
private SerializedProperty blendTimeProp;
// 스타일
private GUIStyle headerStyle;
private GUIStyle sectionBoxStyle;
private GUIStyle presetBoxStyle;
private void OnEnable()
{
isApplicationPlaying = Application.isPlaying;
// SerializedProperties 가져오기
rotationSensitivityProp = serializedObject.FindProperty("rotationSensitivity");
panSpeedProp = serializedObject.FindProperty("panSpeed");
zoomSpeedProp = serializedObject.FindProperty("zoomSpeed");
orbitSpeedProp = serializedObject.FindProperty("orbitSpeed");
movementSmoothingProp = serializedObject.FindProperty("movementSmoothing");
rotationSmoothingProp = serializedObject.FindProperty("rotationSmoothing");
minZoomDistanceProp = serializedObject.FindProperty("minZoomDistance");
maxZoomDistanceProp = serializedObject.FindProperty("maxZoomDistance");
useAvatarHeadAsTargetProp = serializedObject.FindProperty("useAvatarHeadAsTarget");
manualRotationTargetProp = serializedObject.FindProperty("manualRotationTarget");
useBlendTransitionProp = serializedObject.FindProperty("useBlendTransition");
blendTimeProp = serializedObject.FindProperty("blendTime");
}
private void OnDisable()
{
StopListening();
}
private void InitializeStyles()
{
if (headerStyle == null)
{
headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 12,
margin = new RectOffset(0, 0, 5, 5)
};
}
if (sectionBoxStyle == null)
{
sectionBoxStyle = new GUIStyle(EditorStyles.helpBox)
{
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(0, 0, 5, 5)
};
}
if (presetBoxStyle == null)
{
presetBoxStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(8, 8, 8, 8),
margin = new RectOffset(0, 0, 3, 3)
};
}
}
private void StartListening()
{
if (!isApplicationPlaying)
{
currentKeys.Clear();
isListening = true;
Debug.Log("키보드 입력 감지 시작");
}
}
private void StopListening()
{
isListening = false;
}
public override void OnInspectorGUI()
{
serializedObject.Update();
InitializeStyles();
CameraManager manager = (CameraManager)target;
EditorGUILayout.Space(5);
// 카메라 컨트롤 설정 섹션
DrawControlSettingsSection();
// 스무딩 설정 섹션
DrawSmoothingSection();
// 줌 제한 섹션
DrawZoomLimitsSection();
// 회전 타겟 섹션
DrawRotationTargetSection();
// 블렌드 전환 섹션
DrawBlendTransitionSection();
EditorGUILayout.Space(10);
// 카메라 프리셋 섹션
DrawCameraPresetsSection(manager);
// 키 입력 감지 로직
HandleKeyListening(manager);
serializedObject.ApplyModifiedProperties();
}
private void DrawControlSettingsSection()
{
EditorGUILayout.BeginVertical(sectionBoxStyle);
showControlSettings = EditorGUILayout.Foldout(showControlSettings, "Camera Control Settings", true, EditorStyles.foldoutHeader);
if (showControlSettings)
{
EditorGUILayout.Space(5);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(rotationSensitivityProp, new GUIContent("회전 감도", "마우스 회전 감도 (0.5 ~ 10)"));
EditorGUILayout.PropertyField(panSpeedProp, new GUIContent("패닝 속도", "휠클릭 패닝 속도 (0.005 ~ 0.1)"));
EditorGUILayout.PropertyField(zoomSpeedProp, new GUIContent("줌 속도", "마우스 휠 줌 속도 (0.05 ~ 0.5)"));
EditorGUILayout.PropertyField(orbitSpeedProp, new GUIContent("오빗 속도", "궤도 회전 속도 (1 ~ 20)"));
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void DrawSmoothingSection()
{
EditorGUILayout.BeginVertical(sectionBoxStyle);
showSmoothingSettings = EditorGUILayout.Foldout(showSmoothingSettings, "Smoothing", true, EditorStyles.foldoutHeader);
if (showSmoothingSettings)
{
EditorGUILayout.Space(5);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(movementSmoothingProp, new GUIContent("이동 스무딩", "카메라 이동 부드러움 (0 ~ 0.95)"));
EditorGUILayout.PropertyField(rotationSmoothingProp, new GUIContent("회전 스무딩", "카메라 회전 부드러움 (0 ~ 0.95)"));
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void DrawZoomLimitsSection()
{
EditorGUILayout.BeginVertical(sectionBoxStyle);
showZoomSettings = EditorGUILayout.Foldout(showZoomSettings, "Zoom Limits", true, EditorStyles.foldoutHeader);
if (showZoomSettings)
{
EditorGUILayout.Space(5);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(minZoomDistanceProp, new GUIContent("최소 줌 거리", "카메라 최소 거리"));
EditorGUILayout.PropertyField(maxZoomDistanceProp, new GUIContent("최대 줌 거리", "카메라 최대 거리"));
// 경고 표시
if (minZoomDistanceProp.floatValue >= maxZoomDistanceProp.floatValue)
{
EditorGUILayout.HelpBox("최소 줌 거리는 최대 줌 거리보다 작아야 합니다.", MessageType.Warning);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void DrawRotationTargetSection()
{
EditorGUILayout.BeginVertical(sectionBoxStyle);
showRotationTarget = EditorGUILayout.Foldout(showRotationTarget, "Rotation Target", true, EditorStyles.foldoutHeader);
if (showRotationTarget)
{
EditorGUILayout.Space(5);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(useAvatarHeadAsTargetProp, new GUIContent("아바타 머리 사용", "활성화하면 아바타 머리를 회전 중심으로 사용"));
EditorGUI.BeginDisabledGroup(useAvatarHeadAsTargetProp.boolValue);
EditorGUILayout.PropertyField(manualRotationTargetProp, new GUIContent("수동 회전 타겟", "수동으로 지정하는 회전 중심점"));
EditorGUI.EndDisabledGroup();
if (useAvatarHeadAsTargetProp.boolValue)
{
EditorGUILayout.HelpBox("런타임에 CustomRetargetingScript를 가진 아바타의 Head 본을 자동으로 찾습니다.", MessageType.Info);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void DrawBlendTransitionSection()
{
EditorGUILayout.BeginVertical(sectionBoxStyle);
showBlendSettings = EditorGUILayout.Foldout(showBlendSettings, "Camera Blend Transition", true, EditorStyles.foldoutHeader);
if (showBlendSettings)
{
EditorGUILayout.Space(5);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(useBlendTransitionProp, new GUIContent("블렌드 전환 사용", "카메라 전환 시 크로스 디졸브 효과 사용"));
EditorGUI.BeginDisabledGroup(!useBlendTransitionProp.boolValue);
EditorGUILayout.PropertyField(blendTimeProp, new GUIContent("블렌드 시간", "크로스 디졸브 소요 시간 (초)"));
EditorGUI.EndDisabledGroup();
if (useBlendTransitionProp.boolValue)
{
EditorGUILayout.HelpBox("URP Renderer에 CameraBlendRendererFeature가 추가되어 있어야 합니다.", MessageType.Info);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
}
private void DrawCameraPresetsSection(CameraManager manager)
{
EditorGUILayout.BeginVertical(sectionBoxStyle);
// 헤더
EditorGUILayout.BeginHorizontal();
showCameraPresets = EditorGUILayout.Foldout(showCameraPresets, $"Camera Presets ({manager.cameraPresets.Count})", true, EditorStyles.foldoutHeader);
GUILayout.FlexibleSpace();
// 프리셋 추가 버튼
if (GUILayout.Button("+ 프리셋 추가", GUILayout.Width(100), GUILayout.Height(20)))
{
var newCamera = FindFirstObjectByType<CinemachineCamera>();
if (newCamera != null)
{
manager.cameraPresets.Add(new CameraManager.CameraPreset(newCamera));
EditorUtility.SetDirty(target);
}
else
{
EditorUtility.DisplayDialog("알림", "Scene에 CinemachineCamera가 없습니다.", "확인");
}
}
EditorGUILayout.EndHorizontal();
if (showCameraPresets)
{
EditorGUILayout.Space(5);
if (manager.cameraPresets.Count == 0)
{
EditorGUILayout.HelpBox("카메라 프리셋이 없습니다. '+ 프리셋 추가' 버튼을 눌러 추가하세요.", MessageType.Info);
}
else
{
// 프리셋 리스트
for (int i = 0; i < manager.cameraPresets.Count; i++)
{
DrawPresetItem(manager, i);
}
}
}
EditorGUILayout.EndVertical();
}
private void DrawPresetItem(CameraManager manager, int index)
{
var preset = manager.cameraPresets[index];
// 활성 프리셋 표시를 위한 배경색
bool isActive = Application.isPlaying && manager.CurrentPreset == preset;
if (isActive)
{
GUI.backgroundColor = new Color(0.5f, 0.8f, 0.5f);
}
EditorGUILayout.BeginVertical(presetBoxStyle);
GUI.backgroundColor = Color.white;
// 프리셋 헤더
EditorGUILayout.BeginHorizontal();
// 인덱스 번호
GUILayout.Label($"{index + 1}", EditorStyles.boldLabel, GUILayout.Width(20));
// 프리셋 이름 필드
EditorGUI.BeginChangeCheck();
preset.presetName = EditorGUILayout.TextField(preset.presetName, GUILayout.MinWidth(100));
if (EditorGUI.EndChangeCheck())
{
EditorUtility.SetDirty(target);
}
// 활성 표시
if (isActive)
{
GUILayout.Label("[Active]", EditorStyles.miniLabel, GUILayout.Width(50));
}
GUILayout.FlexibleSpace();
// 위/아래 버튼
EditorGUI.BeginDisabledGroup(index == 0);
if (GUILayout.Button("▲", GUILayout.Width(25)))
{
SwapPresets(manager, index, index - 1);
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(index == manager.cameraPresets.Count - 1);
if (GUILayout.Button("▼", GUILayout.Width(25)))
{
SwapPresets(manager, index, index + 1);
}
EditorGUI.EndDisabledGroup();
// 삭제 버튼
GUI.backgroundColor = new Color(1f, 0.5f, 0.5f);
if (GUILayout.Button("X", GUILayout.Width(25)))
{
if (EditorUtility.DisplayDialog("프리셋 삭제",
$"프리셋 '{preset.presetName}'을(를) 삭제하시겠습니까?",
"삭제", "취소"))
{
manager.cameraPresets.RemoveAt(index);
EditorUtility.SetDirty(target);
return;
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(3);
// 가상 카메라 필드
EditorGUI.BeginChangeCheck();
preset.virtualCamera = (CinemachineCamera)EditorGUILayout.ObjectField(
"Virtual Camera", preset.virtualCamera, typeof(CinemachineCamera), true);
if (EditorGUI.EndChangeCheck())
{
EditorUtility.SetDirty(target);
}
// 마우스 조작 허용 설정
EditorGUI.BeginChangeCheck();
preset.allowMouseControl = EditorGUILayout.Toggle(
new GUIContent("Allow Mouse Control", "이 카메라에서 마우스 조작(회전, 팬, 줌)을 허용할지 여부"),
preset.allowMouseControl);
if (EditorGUI.EndChangeCheck())
{
EditorUtility.SetDirty(target);
}
// 핫키 설정 UI
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Hotkey", GUILayout.Width(70));
if (preset.hotkey.isRecording)
{
GUI.backgroundColor = new Color(1f, 0.9f, 0.5f);
EditorGUILayout.LabelField("키를 눌렀다 떼면 저장됩니다...", EditorStyles.helpBox);
GUI.backgroundColor = Color.white;
}
else
{
// 핫키 표시
string hotkeyDisplay = preset.hotkey?.ToString() ?? "설정되지 않음";
EditorGUILayout.LabelField(hotkeyDisplay, EditorStyles.textField, GUILayout.MinWidth(80));
if (GUILayout.Button("Record", GUILayout.Width(60)))
{
foreach (var otherPreset in manager.cameraPresets)
{
otherPreset.hotkey.isRecording = false;
}
preset.hotkey.isRecording = true;
preset.hotkey.rawKeys.Clear();
StartListening();
}
if (GUILayout.Button("Clear", GUILayout.Width(50)))
{
preset.hotkey.rawKeys.Clear();
EditorUtility.SetDirty(target);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(2);
}
private void SwapPresets(CameraManager manager, int indexA, int indexB)
{
var temp = manager.cameraPresets[indexA];
manager.cameraPresets[indexA] = manager.cameraPresets[indexB];
manager.cameraPresets[indexB] = temp;
EditorUtility.SetDirty(target);
}
private void HandleKeyListening(CameraManager manager)
{
if (isListening)
{
var e = Event.current;
if (e != null)
{
if (e.type == EventType.KeyDown && e.keyCode != KeyCode.None)
{
// 마우스 버튼 제외
if (e.keyCode != KeyCode.Mouse0 && e.keyCode != KeyCode.Mouse1 && e.keyCode != KeyCode.Mouse2)
{
AddKey(e.keyCode);
e.Use();
}
}
else if (e.type == EventType.KeyUp && currentKeys.Contains(e.keyCode))
{
var recordingPreset = manager.cameraPresets.FirstOrDefault(p => p.hotkey.isRecording);
if (recordingPreset != null)
{
recordingPreset.hotkey.isRecording = false;
EditorUtility.SetDirty(target);
StopListening();
Repaint();
}
e.Use();
}
}
}
}
private void AddKey(KeyCode keyCode)
{
if (!currentKeys.Contains(keyCode))
{
currentKeys.Add(keyCode);
var recordingPreset = ((CameraManager)target).cameraPresets.FirstOrDefault(p => p.hotkey.isRecording);
if (recordingPreset != null)
{
// KeyCode를 RawKey로 변환
var rawKeys = new List<RawKey>();
foreach (var key in currentKeys)
{
if (RawKeySetup.KeyMapping.TryGetValue(key, out RawKey rawKey))
{
rawKeys.Add(rawKey);
}
else
{
Debug.LogWarning($"맵핑되지 않은 키: {key}");
}
}
recordingPreset.hotkey.rawKeys = rawKeys;
EditorUtility.SetDirty(target);
Repaint();
}
}
}
}