Streamingle_URP/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs

380 lines
17 KiB
C#

using UnityEditor;
using UnityEngine;
namespace KindRetargeting
{
/// <summary>
/// CustomRetargetingScript의 인스펙터를 커스터마이징하여 프로퍼티들을 접었다 펼칠 수 있는 섹션으로 그룹화합니다.
/// </summary>
[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 showFootSettings = 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 footFrontBackOffsetProp;
private SerializedProperty footInOutOffsetProp;
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");
footFrontBackOffsetProp = serializedObject.FindProperty("footFrontBackOffset");
footInOutOffsetProp = serializedObject.FindProperty("footInOutOffset");
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 axisMapping = debugAxisNormalizerProp.vector3Value;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("축 매핑 정보 (T-포즈 기준)", EditorStyles.boldLabel);
// 플레이 모드에서만 정확한 정보 표시
if (Application.isPlaying && axisMapping != Vector3.one)
{
// axisMapping: 1=X, 2=Y, 3=Z, 부호는 방향
string GetAxisName(float value)
{
int axis = Mathf.RoundToInt(Mathf.Abs(value));
string sign = value > 0 ? "+" : "-";
return axis switch
{
1 => $"{sign}X",
2 => $"{sign}Y",
3 => $"{sign}Z",
_ => "?"
};
}
EditorGUILayout.HelpBox(
"T-포즈에서 분석된 축 매핑:\n" +
$" 좌우 오프셋 → 로컬 {GetAxisName(axisMapping.x)} 축\n" +
$" 상하 오프셋 → 로컬 {GetAxisName(axisMapping.y)} 축\n" +
$" 앞뒤 오프셋 → 로컬 {GetAxisName(axisMapping.z)} 축\n\n" +
"이 매핑 덕분에 모든 아바타에서 동일하게 작동합니다.",
MessageType.Info);
}
else
{
EditorGUILayout.HelpBox(
"플레이 모드에서 T-포즈 분석 후 축 매핑 정보가 표시됩니다.\n" +
"이 매핑은 각 아바타의 힙 로컬 축 방향에 맞춰 자동 계산됩니다.",
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);
// 발 IK 위치 조정 Foldout
showFootSettings = EditorGUILayout.Foldout(showFootSettings, "발 IK 위치 조정", true);
if (showFootSettings)
{
EditorGUI.indentLevel++;
EditorGUILayout.Slider(footFrontBackOffsetProp, -1f, 1f,
new GUIContent("발 앞/뒤 오프셋", "+: 앞으로, -: 뒤로"));
EditorGUILayout.Slider(footInOutOffsetProp, -1f, 1f,
new GUIContent("발 벌리기/모으기", "+: 벌리기, -: 모으기"));
EditorGUI.indentLevel--;
}
GUILayout.Space(5);
// 손가락 복제 설정 Foldout
showFingerCopySettings = EditorGUILayout.Foldout(showFingerCopySettings, "손가락 복제 설정");
if (showFingerCopySettings)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(fingerCopyModeProp,
new GUIContent("복제 방식", "손가락 포즈를 복제하는 방식을 선택합니다."));
// Mingle 모드일 때 캘리브레이션 버튼 표시
if (fingerCopyModeProp.enumValueIndex == (int)EnumsList.FingerCopyMode.Mingle)
{
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Mingle 캘리브레이션", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"Mingle 모드는 소스 아바타의 손가락 회전 범위를 캘리브레이션하여 타겟에 적용합니다.\n" +
"1. 손가락을 완전히 펼친 상태에서 '펼침 기록' 클릭\n" +
"2. 손가락을 완전히 모은(주먹) 상태에서 '모음 기록' 클릭",
MessageType.Info);
var script = (CustomRetargetingScript)target;
// 자동 캘리브레이션 진행 중일 때
if (Application.isPlaying && script.IsAutoCalibrating)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("자동 캘리브레이션 진행 중", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"상태: {script.AutoCalibrationStatus}");
EditorGUILayout.LabelField($"남은 시간: {script.AutoCalibrationTimeRemaining:F1}초");
if (GUILayout.Button("취소"))
{
script.StopAutoCalibration();
}
EditorGUILayout.EndVertical();
// 인스펙터 갱신
Repaint();
}
else
{
// 수동 캘리브레이션 버튼
EditorGUILayout.BeginHorizontal();
GUI.enabled = Application.isPlaying;
if (GUILayout.Button("펼침 기록 (Open)"))
{
script.CalibrateMingleOpen();
}
if (GUILayout.Button("모음 기록 (Close)"))
{
script.CalibrateMingleClose();
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
// 자동 캘리브레이션 버튼
GUILayout.Space(5);
GUI.enabled = Application.isPlaying;
if (GUILayout.Button("자동 캘리브레이션 (3초 펼침 → 3초 모음)"))
{
script.StartAutoCalibration();
}
GUI.enabled = true;
}
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("캘리브레이션은 플레이 모드에서만 가능합니다.", MessageType.Warning);
}
EditorGUILayout.EndVertical();
}
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();
}
}
}
}