using UnityEditor; using UnityEngine; namespace KindRetargeting { /// /// CustomRetargetingScript의 인스펙터를 커스터마이징하여 프로퍼티들을 접었다 펼칠 수 있는 섹션으로 그룹화합니다. /// [CustomEditor(typeof(CustomRetargetingScript))] public class CustomRetargetingScriptEditor : BaseRetargetingEditor { // Foldout 상태를 저장할 변수들 private bool showHipsSettings = false; private bool showFingerCopySettings = false; private bool showMotionFilterSettings = false; private bool showRoughMotionSettings = false; private bool showKneeSettings = true; private bool hasCachedData = false; private bool showFloorSettings = false; private bool showScaleSettings = true; // SerializedProperty 변수들 private SerializedProperty sourceAnimatorProp; private SerializedProperty targetAnimatorProp; private SerializedProperty hipsOffsetXProp; private SerializedProperty hipsOffsetYProp; private SerializedProperty hipsOffsetZProp; private SerializedProperty debugAxisNormalizerProp; private SerializedProperty fingerCopyModeProp; private SerializedProperty useMotionFilterProp; private SerializedProperty filterBufferSizeProp; private SerializedProperty useBodyRoughMotionProp; private SerializedProperty useFingerRoughMotionProp; private SerializedProperty bodyRoughnessProp; private SerializedProperty fingerRoughnessProp; private SerializedProperty kneeInOutWeightProp; private SerializedProperty kneeFrontBackWeightProp; private SerializedProperty floorHeightProp; private SerializedProperty avatarScaleProp; protected override void OnEnable() { base.OnEnable(); // SerializedProperty 초기화 sourceAnimatorProp = serializedObject.FindProperty("sourceAnimator"); targetAnimatorProp = serializedObject.FindProperty("targetAnimator"); hipsOffsetXProp = serializedObject.FindProperty("hipsOffsetX"); hipsOffsetYProp = serializedObject.FindProperty("hipsOffsetY"); hipsOffsetZProp = serializedObject.FindProperty("hipsOffsetZ"); debugAxisNormalizerProp = serializedObject.FindProperty("debugAxisNormalizer"); fingerCopyModeProp = serializedObject.FindProperty("fingerCopyMode"); useMotionFilterProp = serializedObject.FindProperty("useMotionFilter"); filterBufferSizeProp = serializedObject.FindProperty("filterBufferSize"); useBodyRoughMotionProp = serializedObject.FindProperty("useBodyRoughMotion"); useFingerRoughMotionProp = serializedObject.FindProperty("useFingerRoughMotion"); bodyRoughnessProp = serializedObject.FindProperty("bodyRoughness"); fingerRoughnessProp = serializedObject.FindProperty("fingerRoughness"); kneeInOutWeightProp = serializedObject.FindProperty("kneeInOutWeight"); kneeFrontBackWeightProp = serializedObject.FindProperty("kneeFrontBackWeight"); floorHeightProp = serializedObject.FindProperty("floorHeight"); avatarScaleProp = serializedObject.FindProperty("avatarScale"); } private void CheckCacheStatus() { var script = (CustomRetargetingScript)target; hasCachedData = script.HasCachedSettings(); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUI.BeginChangeCheck(); GUILayout.Space(10); // sourceAnimator를 한 번만 표시 EditorGUILayout.PropertyField(sourceAnimatorProp, new GUIContent("원본 Animator")); GUILayout.Space(10); // 아바타 크기 조정 섹션 추가 showScaleSettings = EditorGUILayout.Foldout(showScaleSettings, "아바타 크기 설정", true); if (showScaleSettings) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(avatarScaleProp, new GUIContent("아바타 크기")); EditorGUI.indentLevel--; } GUILayout.Space(5); // 힙 위치 보정 Foldout showHipsSettings = EditorGUILayout.Foldout(showHipsSettings, "힙 위치 보정 (로컬 좌표계)"); if (showHipsSettings) { EditorGUI.indentLevel++; // 축 정규화 정보 표시 if (debugAxisNormalizerProp != null) { Vector3 normalizer = debugAxisNormalizerProp.vector3Value; EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("축 정규화 정보", EditorStyles.boldLabel); EditorGUI.BeginDisabledGroup(true); EditorGUILayout.Vector3Field("정규화 계수", normalizer); EditorGUI.EndDisabledGroup(); EditorGUILayout.HelpBox( $"X축: {(normalizer.x > 0 ? "정방향 ↔" : "역방향 ↔")}\n" + $"Y축: {(normalizer.y > 0 ? "정방향 ↕" : "역방향 ↕")}\n" + $"Z축: {(normalizer.z > 0 ? "정방향 ⇄" : "역방향 ⇄")}\n\n" + "이 계수는 Start()에서 자동 계산되며, 항상 직관적인 방향으로 제어할 수 있게 합니다.", MessageType.Info); EditorGUILayout.EndVertical(); GUILayout.Space(5); } EditorGUILayout.PropertyField(hipsOffsetXProp, new GUIContent("좌우 오프셋 (←-/+→)", "캐릭터 기준 왼쪽(-) / 오른쪽(+)")); EditorGUILayout.PropertyField(hipsOffsetYProp, new GUIContent("상하 오프셋 (↓-/+↑)", "캐릭터 기준 아래(-) / 위(+)")); EditorGUILayout.PropertyField(hipsOffsetZProp, new GUIContent("앞뒤 오프셋 (←-/+→)", "캐릭터 기준 뒤(-) / 앞(+)")); EditorGUILayout.HelpBox( "로컬 좌표계 기반: 캐릭터의 회전 상태와 관계없이 항상 캐릭터 기준으로 이동합니다.", MessageType.Info); EditorGUI.indentLevel--; } GUILayout.Space(5); // 무릎 위치 조정 Foldout showKneeSettings = EditorGUILayout.Foldout(showKneeSettings, "무릎 위치 조정", true); if (showKneeSettings) { EditorGUI.indentLevel++; // 몸릎 안/밖 정만 표시 EditorGUILayout.Slider(kneeFrontBackWeightProp, -1f, 1f, new GUIContent("무릎 앞/뒤 가중치", "음수: 뒤로, 양수: 앞으로")); EditorGUILayout.Slider(kneeInOutWeightProp, -1f, 1f, new GUIContent("무릎 안/밖 가중치", "음수: 안쪽, 양수: 바깥쪽")); EditorGUI.indentLevel--; } GUILayout.Space(5); // 손가락 복제 설정 Foldout showFingerCopySettings = EditorGUILayout.Foldout(showFingerCopySettings, "손가락 복제 설정"); if (showFingerCopySettings) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(fingerCopyModeProp, new GUIContent("복제 방식", "손가락 포즈를 복제하는 방식을 선택합니다.")); EditorGUI.indentLevel--; } GUILayout.Space(5); // 모션 필터링 설정 Foldout showMotionFilterSettings = EditorGUILayout.Foldout(showMotionFilterSettings, "모션 필터링 설정"); if (showMotionFilterSettings) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(useMotionFilterProp, new GUIContent("모션 필터 사용", "모션 필터링을 적용할지 여부를 설정합니다.")); if (useMotionFilterProp.boolValue) { EditorGUILayout.PropertyField(filterBufferSizeProp, new GUIContent("필터 버퍼 크기", "모션 필터링에 사용할 버퍼의 크기를 설정합니다. (2-10)")); } EditorGUI.indentLevel--; } GUILayout.Space(5); // 러프 모션 설정 Foldout showRoughMotionSettings = EditorGUILayout.Foldout(showRoughMotionSettings, "러프 모션 설정"); if (showRoughMotionSettings) { EditorGUI.indentLevel++; // 몸 러프 모션 설정 EditorGUILayout.PropertyField(useBodyRoughMotionProp, new GUIContent("몸 러프 모션 사용", "몸의 러프한 움직임을 적용할지 여부를 설정합니다.")); if (useBodyRoughMotionProp.boolValue) { EditorGUILayout.PropertyField(bodyRoughnessProp, new GUIContent("몸 러프니스", "몸 전체의 러프한 정도를 설정합니다 (0: 없음, 1: 최대)")); } // 손가락 러프 모션 설정 EditorGUILayout.PropertyField(useFingerRoughMotionProp, new GUIContent("손가락 러프 모션 사용", "손가락의 러프한 움직임을 적용할지 여부를 설정합니다.")); if (useFingerRoughMotionProp.boolValue) { EditorGUILayout.PropertyField(fingerRoughnessProp, new GUIContent("손가락 러프니스", "손가락의 러프한 정도를 설정합니다 (0: 없음, 1: 최대)")); } EditorGUI.indentLevel--; } GUILayout.Space(5); // 바닥 높이 조정 Foldout showFloorSettings = EditorGUILayout.Foldout(showFloorSettings, "바닥 높이 조정", true); if (showFloorSettings) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(floorHeightProp, new GUIContent("바닥 높이 (-1 ~ 1)")); EditorGUI.indentLevel--; } // 캐시 상태를 텍스트로 표시 EditorGUILayout.LabelField(hasCachedData ? "캘리브레이션 데이터가 저장되어 있습니다." : "저장된 캘리브레이션 데이터가 없습니다.", EditorStyles.boldLabel); // 버튼들을 수평으로 배치 EditorGUILayout.BeginHorizontal(); // I-포즈 캘리브레이션 버튼 if (GUILayout.Button("I-포즈 캘리브레이션")) { var script = (CustomRetargetingScript)target; script.I_PoseCalibration(); CheckCacheStatus(); Repaint(); } // 캐시 삭제 버튼 (캐시가 있을 때만 표시) if (hasCachedData) { if (GUILayout.Button("캐시 데이터 삭제")) { var script = (CustomRetargetingScript)target; script.ResetPoseAndCache(); CheckCacheStatus(); Repaint(); } } EditorGUILayout.EndHorizontal(); GUILayout.Space(10); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); var script = (CustomRetargetingScript)target; if (script.targetAnimator != null) // targetAnimator가 설정되어 있을 때만 저장 { script.SaveSettings(); } } else { serializedObject.ApplyModifiedProperties(); } } } }