1298 lines
49 KiB
C#
1298 lines
49 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections.Generic;
|
|
using KindRetargeting;
|
|
using KindRetargeting.EnumsList;
|
|
|
|
public class RetargetingControlWindow : EditorWindow
|
|
{
|
|
private Vector2 scrollPosition;
|
|
private bool showGlobalSettings = true;
|
|
private CustomRetargetingScript[] retargetingScripts;
|
|
|
|
private Dictionary<string, RetargetingPreset> presets = new Dictionary<string, RetargetingPreset>();
|
|
private string currentPresetName = "";
|
|
|
|
// 각 객체별 foldout 상태를 저장할 Dictionary들
|
|
private Dictionary<int, bool> weightSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> hipsSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> kneeSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> fingerControlFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> fingerCopyFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> motionSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> floorSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> scaleSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> footSettingsFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> leftHandFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> rightHandFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> headFoldouts = new Dictionary<int, bool>();
|
|
private Dictionary<int, bool> propControlFoldouts = new Dictionary<int, bool>();
|
|
|
|
private bool isDirty = false;
|
|
private double lastUpdateTime;
|
|
private const double UPDATE_INTERVAL = 0.25; // 250ms
|
|
private const float SLIDER_HEIGHT = 100f;
|
|
private const float SLIDER_WIDTH = 25f;
|
|
private const float SPACING = 5f;
|
|
|
|
[MenuItem("Tools/리타게팅 컨트롤 패널")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<RetargetingControlWindow>("리타게팅 컨트롤");
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
// 씬에서 모든 리타게팅 스크립트 찾기
|
|
retargetingScripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
EditorApplication.update += OnEditorUpdate;
|
|
Undo.undoRedoPerformed += OnUndoRedo;
|
|
lastUpdateTime = EditorApplication.timeSinceStartup;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
EditorApplication.update -= OnEditorUpdate;
|
|
Undo.undoRedoPerformed -= OnUndoRedo;
|
|
}
|
|
|
|
private void OnEditorUpdate()
|
|
{
|
|
// 플레이 모드일 때는 업데이트 처리를 하지 않음
|
|
if (Application.isPlaying)
|
|
return;
|
|
|
|
// 업데이트 간격을 더 길게 조정 (100ms -> 250ms)
|
|
if (EditorApplication.timeSinceStartup - lastUpdateTime < UPDATE_INTERVAL)
|
|
return;
|
|
|
|
// 실제로 변경된 경우에만 리프레시 수행
|
|
if (isDirty)
|
|
{
|
|
RefreshScripts();
|
|
isDirty = false;
|
|
lastUpdateTime = EditorApplication.timeSinceStartup;
|
|
}
|
|
}
|
|
|
|
private void OnUndoRedo()
|
|
{
|
|
isDirty = true;
|
|
Repaint();
|
|
}
|
|
|
|
private void RefreshScripts()
|
|
{
|
|
// 플레이 모드일 때는 씬 수정을 하지 않음
|
|
if (Application.isPlaying)
|
|
return;
|
|
|
|
// 캐싱된 스크립트 사용
|
|
if (retargetingScripts == null || retargetingScripts.Length == 0)
|
|
retargetingScripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
|
|
bool anyDirty = false;
|
|
|
|
foreach (var script in retargetingScripts)
|
|
{
|
|
if (script != null && script.isActiveAndEnabled) // 활성화된 컴포넌트만 처리
|
|
{
|
|
EditorUtility.SetDirty(script);
|
|
anyDirty = true;
|
|
}
|
|
}
|
|
|
|
if (anyDirty && !Application.isPlaying) // 플레이 모드가 아닐 때만 씬을 수정
|
|
{
|
|
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
|
|
retargetingScripts[0].gameObject.scene);
|
|
}
|
|
|
|
// 불필요한 리페인트 제거
|
|
if (focusedWindow == this)
|
|
{
|
|
Repaint();
|
|
}
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
if (retargetingScripts == null || retargetingScripts.Length == 0)
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
EditorGUILayout.HelpBox("씬에서 리타게팅 스크립트를 찾을 수 없습니다.", MessageType.Warning);
|
|
|
|
if (GUILayout.Button("리타게팅 스크립트 리프레시", GUILayout.Height(30)))
|
|
{
|
|
retargetingScripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
|
|
// 각 리타게팅 스크립트에 대해 필요한 컴포넌트들 체크
|
|
foreach (var script in retargetingScripts)
|
|
{
|
|
if (script != null)
|
|
{
|
|
// LimbWeightController가 없다면 추가
|
|
if (script.GetComponent<LimbWeightController>() == null)
|
|
{
|
|
script.gameObject.AddComponent<LimbWeightController>();
|
|
}
|
|
|
|
// FingerShapedController가 없다면 추가
|
|
if (script.GetComponent<FingerShapedController>() == null)
|
|
{
|
|
script.gameObject.AddComponent<FingerShapedController>();
|
|
}
|
|
|
|
// PropLocationController가 없다면 추가
|
|
if (script.GetComponent<PropLocationController>() == null)
|
|
{
|
|
script.gameObject.AddComponent<PropLocationController>();
|
|
}
|
|
|
|
EditorUtility.SetDirty(script.gameObject);
|
|
}
|
|
}
|
|
|
|
isDirty = true;
|
|
Repaint();
|
|
}
|
|
|
|
EditorGUILayout.Space(5);
|
|
EditorGUILayout.LabelField("마지막 리프레시: " + System.DateTime.Now.ToString("HH:mm:ss"), EditorStyles.miniLabel);
|
|
|
|
EditorGUILayout.EndVertical();
|
|
return;
|
|
}
|
|
|
|
// 전역 설정 옵션
|
|
DrawGlobalSettings();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// 개별 캐릭터 설정
|
|
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
|
var scripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
foreach (var script in scripts)
|
|
{
|
|
DrawRetargetingControls(script);
|
|
}
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
private void DrawGlobalSettings()
|
|
{
|
|
showGlobalSettings = EditorGUILayout.Foldout(showGlobalSettings, "전역 설정", true);
|
|
if (showGlobalSettings)
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
// 전역 캘리브레이션 버튼
|
|
EditorGUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("모든 캐릭터 I-포즈 캘리브레이션"))
|
|
{
|
|
var scripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
foreach (var script in scripts)
|
|
{
|
|
// I-포즈 캘리브레이션 로직
|
|
}
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
if (GUILayout.Button("모든 캐릭터 캘리브레이션 초기화"))
|
|
{
|
|
var scripts = FindObjectsByType<CustomRetargetingScript>(FindObjectsSortMode.None);
|
|
foreach (var script in scripts)
|
|
{
|
|
script.ResetPoseAndCache();
|
|
EditorUtility.SetDirty(script);
|
|
}
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
}
|
|
|
|
private void DrawRetargetingControls(CustomRetargetingScript script)
|
|
{
|
|
if (script == null) return;
|
|
|
|
int instanceID = script.GetInstanceID();
|
|
|
|
// Dictionary 초기화
|
|
if (!weightSettingsFoldouts.ContainsKey(instanceID)) weightSettingsFoldouts[instanceID] = false;
|
|
if (!hipsSettingsFoldouts.ContainsKey(instanceID)) hipsSettingsFoldouts[instanceID] = false;
|
|
if (!kneeSettingsFoldouts.ContainsKey(instanceID)) kneeSettingsFoldouts[instanceID] = false;
|
|
if (!fingerControlFoldouts.ContainsKey(instanceID)) fingerControlFoldouts[instanceID] = false;
|
|
if (!fingerCopyFoldouts.ContainsKey(instanceID)) fingerCopyFoldouts[instanceID] = false;
|
|
if (!motionSettingsFoldouts.ContainsKey(instanceID)) motionSettingsFoldouts[instanceID] = false;
|
|
if (!floorSettingsFoldouts.ContainsKey(instanceID)) floorSettingsFoldouts[instanceID] = false;
|
|
if (!scaleSettingsFoldouts.ContainsKey(instanceID)) scaleSettingsFoldouts[instanceID] = false;
|
|
if (!footSettingsFoldouts.ContainsKey(instanceID)) footSettingsFoldouts[instanceID] = true;
|
|
if (!leftHandFoldouts.ContainsKey(instanceID)) leftHandFoldouts[instanceID] = false;
|
|
if (!rightHandFoldouts.ContainsKey(instanceID)) rightHandFoldouts[instanceID] = false;
|
|
if (!headFoldouts.ContainsKey(instanceID)) headFoldouts[instanceID] = false;
|
|
if (!propControlFoldouts.ContainsKey(instanceID)) propControlFoldouts[instanceID] = false;
|
|
|
|
SerializedObject serializedObject = new SerializedObject(script);
|
|
|
|
// 시작 시 Update 호출
|
|
serializedObject.Update();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
// 헤더 부분
|
|
DrawHeader(script);
|
|
|
|
GUI.backgroundColor = new Color(0.8f, 0.9f, 1f);
|
|
|
|
// 가중치 설정
|
|
weightSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(weightSettingsFoldouts[instanceID], "가중치 설정", true);
|
|
if (weightSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawWeightSettings(script);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 힙 설정
|
|
hipsSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(hipsSettingsFoldouts[instanceID], "힙 위치 보정 (로컬)", true);
|
|
if (hipsSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
|
|
var hipsOffsetXProp = serializedObject.FindProperty("hipsOffsetX");
|
|
var hipsOffsetYProp = serializedObject.FindProperty("hipsOffsetY");
|
|
var hipsOffsetZProp = serializedObject.FindProperty("hipsOffsetZ");
|
|
|
|
// 축 매핑 정보 표시
|
|
var normalizerProp = serializedObject.FindProperty("debugAxisNormalizer");
|
|
if (normalizerProp != null && Application.isPlaying)
|
|
{
|
|
Vector3 mapping = normalizerProp.vector3Value;
|
|
if (mapping != Vector3.one) // 초기화되었을 때만 표시
|
|
{
|
|
// mapping: 1=X, 2=Y, 3=Z, 부호는 방향
|
|
string GetAxis(float v) => Mathf.RoundToInt(Mathf.Abs(v)) switch
|
|
{
|
|
1 => v > 0 ? "+X" : "-X",
|
|
2 => v > 0 ? "+Y" : "-Y",
|
|
3 => v > 0 ? "+Z" : "-Z",
|
|
_ => "?"
|
|
};
|
|
|
|
EditorGUILayout.LabelField($"축 매핑: 좌우→{GetAxis(mapping.x)} 상하→{GetAxis(mapping.y)} 앞뒤→{GetAxis(mapping.z)}",
|
|
EditorStyles.miniLabel);
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.Slider(hipsOffsetXProp, -1f, 1f,
|
|
new GUIContent("← 좌우 →", "캐릭터 기준 왼쪽(-) / 오른쪽(+)"));
|
|
EditorGUILayout.Slider(hipsOffsetYProp, -1f, 1f,
|
|
new GUIContent("↓ 상하 ↑", "캐릭터 기준 아래(-) / 위(+)"));
|
|
EditorGUILayout.Slider(hipsOffsetZProp, -1f, 1f,
|
|
new GUIContent("← 앞뒤 →", "캐릭터 기준 뒤(-) / 앞(+)"));
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 무릎 설정
|
|
kneeSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(kneeSettingsFoldouts[instanceID], "무릎 위치 조정", true);
|
|
if (kneeSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
var kneeFrontBackProp = serializedObject.FindProperty("kneeFrontBackWeight");
|
|
var kneeInOutProp = serializedObject.FindProperty("kneeInOutWeight");
|
|
|
|
EditorGUILayout.Slider(kneeFrontBackProp, -1f, 1f, new GUIContent("무릎 앞/뒤 가중치"));
|
|
EditorGUILayout.Slider(kneeInOutProp, -1f, 1f, new GUIContent("무릎 안/밖 가중치"));
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 발 IK 위치 조정
|
|
footSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(footSettingsFoldouts[instanceID], "발 IK 위치 조정", true);
|
|
if (footSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
var footFrontBackProp = serializedObject.FindProperty("footFrontBackOffset");
|
|
var footInOutProp = serializedObject.FindProperty("footInOutOffset");
|
|
|
|
EditorGUILayout.Slider(footFrontBackProp, -1f, 1f, new GUIContent("발 앞/뒤 오프셋", "+: 앞으로, -: 뒤로"));
|
|
EditorGUILayout.Slider(footInOutProp, -1f, 1f, new GUIContent("발 벌리기/모으기", "+: 벌리기, -: 모으기"));
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 손가락 제어 설정
|
|
fingerControlFoldouts[instanceID] = EditorGUILayout.Foldout(fingerControlFoldouts[instanceID], "손가락 제어 설정", true);
|
|
if (fingerControlFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawFingerControls(script);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 손가락 복제 설정
|
|
fingerCopyFoldouts[instanceID] = EditorGUILayout.Foldout(fingerCopyFoldouts[instanceID], "손가락 복제 설정", true);
|
|
if (fingerCopyFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawFingerCopySettings(script);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 모션 설정
|
|
motionSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(motionSettingsFoldouts[instanceID], "모션 설정", true);
|
|
if (motionSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawMotionSettings(serializedObject);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 바닥 높이 설정 섹션
|
|
floorSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(floorSettingsFoldouts[instanceID], "바닥 높이 설정", true);
|
|
if (floorSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawFloorSettings(serializedObject);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 아바타 크기 설정 섹션
|
|
scaleSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(scaleSettingsFoldouts[instanceID], "아바타 크기 설정", true);
|
|
if (scaleSettingsFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawScaleSettings(serializedObject);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// 프랍 컨트롤 설정
|
|
propControlFoldouts[instanceID] = EditorGUILayout.Foldout(propControlFoldouts[instanceID], "프랍 설정", true);
|
|
if (propControlFoldouts[instanceID])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
DrawPropControls(script);
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
// 캘리브레이션 설정
|
|
EditorGUILayout.Space(10);
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
DrawCalibrationControls(script);
|
|
EditorGUILayout.EndVertical();
|
|
|
|
// 변경사항이 있을 경우 적용
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
// 수정된 속성 적용
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
// 스크립트를 더티로 표시
|
|
EditorUtility.SetDirty(script);
|
|
|
|
// isDirty 플래그 설정
|
|
isDirty = true;
|
|
|
|
// 씬 뷰 갱신
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
|
|
// 헤더 그리기 함수 (기존의 버튼 코드)
|
|
private void DrawHeader(CustomRetargetingScript script)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField($"캐릭터: {script.gameObject.name}", EditorStyles.boldLabel);
|
|
GUILayout.FlexibleSpace();
|
|
|
|
var animator = script.GetComponent<Animator>();
|
|
if (animator != null)
|
|
{
|
|
if (GUILayout.Button("전체", GUILayout.Width(50)))
|
|
{
|
|
Selection.activeGameObject = script.gameObject;
|
|
if (SceneView.lastActiveSceneView != null)
|
|
{
|
|
SceneView.lastActiveSceneView.FrameSelected();
|
|
}
|
|
}
|
|
|
|
if (GUILayout.Button("머리", GUILayout.Width(50)))
|
|
{
|
|
var head = animator.GetBoneTransform(HumanBodyBones.Head);
|
|
if (head != null)
|
|
{
|
|
Selection.activeGameObject = head.gameObject;
|
|
if (SceneView.lastActiveSceneView != null)
|
|
{
|
|
SceneView.lastActiveSceneView.FrameSelected();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GUILayout.Button("왼손", GUILayout.Width(50)))
|
|
{
|
|
var leftHand = animator.GetBoneTransform(HumanBodyBones.LeftHand);
|
|
if (leftHand != null)
|
|
{
|
|
Selection.activeGameObject = leftHand.gameObject;
|
|
if (SceneView.lastActiveSceneView != null)
|
|
{
|
|
SceneView.lastActiveSceneView.FrameSelected();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GUILayout.Button("오른손", GUILayout.Width(50)))
|
|
{
|
|
var rightHand = animator.GetBoneTransform(HumanBodyBones.RightHand);
|
|
if (rightHand != null)
|
|
{
|
|
Selection.activeGameObject = rightHand.gameObject;
|
|
if (SceneView.lastActiveSceneView != null)
|
|
{
|
|
SceneView.lastActiveSceneView.FrameSelected();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawCalibrationControls(CustomRetargetingScript script)
|
|
{
|
|
// 캘리브레이션 상태 표시
|
|
EditorGUILayout.LabelField(script.HasCachedSettings() ?
|
|
"캘리브레이션 데이터가 저장되어 있습니다." :
|
|
"저장된 캘리브레이션 데이터가 없습니다.",
|
|
EditorStyles.boldLabel);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("I-포즈 캘리브레이션"))
|
|
{
|
|
script.I_PoseCalibration();
|
|
}
|
|
if (GUILayout.Button("캘리브레이션 초기화"))
|
|
{
|
|
script.ResetPoseAndCache();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void ResetWeights(CustomRetargetingScript script)
|
|
{
|
|
var limb = script.GetComponent<LimbWeightController>();
|
|
if (limb != null)
|
|
{
|
|
// 기본 설정값으로 초기화
|
|
limb.maxDistance = 0.3f;
|
|
limb.minDistance = 0.1f;
|
|
limb.weightSmoothSpeed = 10f;
|
|
limb.groundHipsMinHeight = 0.3f;
|
|
limb.groundHipsMaxHeight = 0.7f;
|
|
|
|
EditorUtility.SetDirty(limb);
|
|
}
|
|
}
|
|
|
|
private void ResetAllSettings(CustomRetargetingScript script)
|
|
{
|
|
// 모든 컴포넌트의 설정을 기본값으로 초기화
|
|
ResetWeights(script);
|
|
script.ResetPoseAndCache();
|
|
|
|
var shoulder = script.GetComponent<ShoulderCorrectionFunction>();
|
|
if (shoulder != null)
|
|
{
|
|
shoulder.blendStrength = 2f;
|
|
shoulder.maxHeightDifference = 0.3f;
|
|
EditorUtility.SetDirty(shoulder);
|
|
}
|
|
}
|
|
|
|
private void SavePreset()
|
|
{
|
|
if (string.IsNullOrEmpty(currentPresetName)) return;
|
|
|
|
var preset = new RetargetingPreset();
|
|
if (retargetingScripts.Length > 0)
|
|
{
|
|
var script = retargetingScripts[0];
|
|
var shoulder = script.GetComponent<ShoulderCorrectionFunction>();
|
|
var limb = script.GetComponent<LimbWeightController>();
|
|
|
|
if (shoulder != null)
|
|
{
|
|
preset.shoulderBlendStrength = shoulder.blendStrength;
|
|
preset.shoulderMaxHeightDiff = shoulder.maxHeightDifference;
|
|
}
|
|
|
|
if (limb != null)
|
|
{
|
|
preset.limbMaxDistance = limb.maxDistance;
|
|
preset.limbMinDistance = limb.minDistance;
|
|
preset.footHeightMinThreshold = limb.footHeightMinThreshold;
|
|
preset.footHeightMaxThreshold = limb.footHeightMaxThreshold;
|
|
}
|
|
|
|
presets[currentPresetName] = preset;
|
|
EditorUtility.SetDirty(this);
|
|
}
|
|
}
|
|
|
|
private void LoadPreset()
|
|
{
|
|
if (!presets.TryGetValue(currentPresetName, out RetargetingPreset preset)) return;
|
|
|
|
foreach (var script in retargetingScripts)
|
|
{
|
|
var shoulder = script.GetComponent<ShoulderCorrectionFunction>();
|
|
var limb = script.GetComponent<LimbWeightController>();
|
|
|
|
if (shoulder != null)
|
|
{
|
|
shoulder.blendStrength = preset.shoulderBlendStrength;
|
|
shoulder.maxHeightDifference = preset.shoulderMaxHeightDiff;
|
|
}
|
|
|
|
if (limb != null)
|
|
{
|
|
limb.maxDistance = preset.limbMaxDistance;
|
|
limb.minDistance = preset.limbMinDistance;
|
|
limb.footHeightMinThreshold = preset.footHeightMinThreshold;
|
|
limb.footHeightMaxThreshold = preset.footHeightMaxThreshold;
|
|
}
|
|
|
|
EditorUtility.SetDirty(script);
|
|
}
|
|
}
|
|
|
|
private bool DrawHandControls(string label, string prefix, bool foldout, SerializedObject serializedController)
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.BeginHorizontal(GUILayout.Width(200));
|
|
bool newFoldout = EditorGUILayout.Foldout(foldout, label, true);
|
|
|
|
SerializedProperty handEnabledProp = serializedController.FindProperty($"{prefix}HandEnabled");
|
|
bool handEnabled = EditorGUILayout.Toggle("제어 활성화", handEnabledProp.boolValue);
|
|
if (handEnabled != handEnabledProp.boolValue)
|
|
{
|
|
handEnabledProp.boolValue = handEnabled;
|
|
if (handEnabled && !((FingerShapedController)serializedController.targetObject).enabled)
|
|
{
|
|
((FingerShapedController)serializedController.targetObject).enabled = true;
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (GUILayout.Button("초기화", GUILayout.Width(60)))
|
|
{
|
|
ResetHandValues(prefix, serializedController);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (newFoldout && handEnabledProp.boolValue)
|
|
{
|
|
EditorGUILayout.Space(SPACING);
|
|
DrawFingerSliders(prefix, serializedController);
|
|
EditorGUILayout.Space(SPACING);
|
|
DrawSpreadSlider(prefix, serializedController);
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
return newFoldout;
|
|
}
|
|
|
|
private void DrawFingerControls(CustomRetargetingScript script)
|
|
{
|
|
var fingerController = script.GetComponent<FingerShapedController>();
|
|
if (fingerController == null)
|
|
{
|
|
EditorGUILayout.HelpBox("FingerShapedController가 없습니다.", MessageType.Warning);
|
|
if (GUILayout.Button("FingerShapedController 추가"))
|
|
{
|
|
fingerController = script.gameObject.AddComponent<FingerShapedController>();
|
|
EditorUtility.SetDirty(script.gameObject);
|
|
}
|
|
return;
|
|
}
|
|
|
|
SerializedObject serializedController = new SerializedObject(fingerController);
|
|
serializedController.Update();
|
|
|
|
// 활성화/비활성화 토글
|
|
EditorGUI.BeginChangeCheck();
|
|
bool isEnabled = EditorGUILayout.Toggle("손가락 제어 활성화", fingerController.enabled);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
fingerController.enabled = isEnabled;
|
|
EditorUtility.SetDirty(fingerController);
|
|
}
|
|
|
|
if (!fingerController.enabled) return;
|
|
|
|
int instanceID = fingerController.GetInstanceID();
|
|
|
|
// Dictionary 초기화 확인
|
|
if (!leftHandFoldouts.ContainsKey(instanceID))
|
|
leftHandFoldouts[instanceID] = true;
|
|
if (!rightHandFoldouts.ContainsKey(instanceID))
|
|
rightHandFoldouts[instanceID] = true;
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
// 왼손 컨트롤
|
|
leftHandFoldouts[instanceID] = DrawHandControls("왼손", "left", leftHandFoldouts[instanceID], serializedController);
|
|
EditorGUILayout.Space(SPACING);
|
|
|
|
// 오른손 컨트롤
|
|
rightHandFoldouts[instanceID] = DrawHandControls("오른손", "right", rightHandFoldouts[instanceID], serializedController);
|
|
EditorGUILayout.Space(SPACING);
|
|
|
|
// 프리셋 버튼들
|
|
DrawFingerPresetButtons(fingerController);
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
serializedController.ApplyModifiedProperties();
|
|
}
|
|
|
|
private void DrawFingerCopySettings(CustomRetargetingScript script)
|
|
{
|
|
if (script == null) return;
|
|
|
|
SerializedObject serializedObject = new SerializedObject(script);
|
|
serializedObject.Update();
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
// CustomRetargetingScript의 fingerCopyMode 설정을 표시
|
|
var fingerCopyModeProp = serializedObject.FindProperty("fingerCopyMode");
|
|
if (fingerCopyModeProp != null)
|
|
{
|
|
EditorGUILayout.PropertyField(fingerCopyModeProp, new GUIContent("복제 방식", "손가락 포즈를 복제하는 방식을 선택합니다."));
|
|
if (GUI.changed)
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
EditorUtility.SetDirty(script);
|
|
}
|
|
|
|
// Mingle 모드일 때 캘리브레이션 버튼 표시
|
|
if (fingerCopyModeProp.enumValueIndex == (int)FingerCopyMode.Mingle)
|
|
{
|
|
GUILayout.Space(5);
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
EditorGUILayout.LabelField("Mingle 캘리브레이션", EditorStyles.boldLabel);
|
|
EditorGUILayout.HelpBox(
|
|
"Mingle 모드는 소스 아바타의 손가락 회전 범위를 캘리브레이션하여 타겟에 적용합니다.\n" +
|
|
"1. 손가락을 완전히 펼친 상태에서 '펼침 기록' 클릭\n" +
|
|
"2. 손가락을 완전히 모은(주먹) 상태에서 '모음 기록' 클릭",
|
|
MessageType.Info);
|
|
|
|
// 자동 캘리브레이션 진행 중일 때
|
|
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();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox("fingerCopyMode 프로퍼티를 찾을 수 없습니다.", MessageType.Warning);
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawFingerSliders(string prefix, SerializedObject serializedController)
|
|
{
|
|
string[] fingerNames = { "Thumb", "Index", "Middle", "Ring", "Pinky" };
|
|
float totalWidth = (SLIDER_WIDTH + SPACING) * fingerNames.Length;
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.BeginHorizontal(GUILayout.Width(totalWidth));
|
|
|
|
for (int i = 0; i < fingerNames.Length; i++)
|
|
{
|
|
EditorGUILayout.BeginVertical(GUILayout.Width(SLIDER_WIDTH));
|
|
|
|
// 손가락 이름
|
|
GUILayout.Label(GetKoreanFingerName(fingerNames[i]),
|
|
EditorStyles.centeredGreyMiniLabel,
|
|
GUILayout.Width(SLIDER_WIDTH));
|
|
|
|
// 값 표시
|
|
SerializedProperty prop = serializedController.FindProperty($"{prefix}{fingerNames[i]}Curl");
|
|
GUILayout.Label(prop.floatValue.ToString("F1"),
|
|
EditorStyles.centeredGreyMiniLabel,
|
|
GUILayout.Width(SLIDER_WIDTH));
|
|
|
|
// 세로 슬라이더를 중앙에 배치
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
prop.floatValue = GUILayout.VerticalSlider(
|
|
prop.floatValue,
|
|
1f,
|
|
-1f,
|
|
GUILayout.Width(SLIDER_WIDTH),
|
|
GUILayout.Height(SLIDER_HEIGHT)
|
|
);
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
if (i < fingerNames.Length - 1)
|
|
GUILayout.Space(SPACING);
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private string GetKoreanFingerName(string englishName)
|
|
{
|
|
switch (englishName)
|
|
{
|
|
case "Thumb": return "엄지";
|
|
case "Index": return "검지";
|
|
case "Middle": return "중지";
|
|
case "Ring": return "약지";
|
|
case "Pinky": return "새끼";
|
|
default: return englishName;
|
|
}
|
|
}
|
|
|
|
private void DrawSpreadSlider(string prefix, SerializedObject serializedController)
|
|
{
|
|
SerializedProperty spreadProp = serializedController.FindProperty($"{prefix}SpreadFingers");
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.Space(15);
|
|
EditorGUILayout.LabelField("벌리기", GUILayout.Width(50));
|
|
spreadProp.floatValue = EditorGUILayout.Slider(spreadProp.floatValue, -1f, 1f);
|
|
GUILayout.Space(15);
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawFingerPresetButtons(FingerShapedController controller)
|
|
{
|
|
EditorGUILayout.LabelField("손 모양 프리셋", EditorStyles.boldLabel);
|
|
|
|
string[,] presets = {
|
|
{"가위", "바위", "보"},
|
|
{"브이", "검지", "초기화"}
|
|
};
|
|
|
|
for (int row = 0; row < 2; row++)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
|
|
for (int col = 0; col < 3; col++)
|
|
{
|
|
if (GUILayout.Button(presets[row, col], GUILayout.Height(30), GUILayout.Width(100)))
|
|
{
|
|
ApplyPreset(controller, presets[row, col]);
|
|
}
|
|
|
|
if (col < 2)
|
|
{
|
|
GUILayout.Space(10);
|
|
}
|
|
}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (row < 1)
|
|
{
|
|
GUILayout.Space(5);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetHandValues(string prefix, SerializedObject serializedController)
|
|
{
|
|
string[] props = { "ThumbCurl", "IndexCurl", "MiddleCurl", "RingCurl", "PinkyCurl", "SpreadFingers" };
|
|
foreach (var prop in props)
|
|
{
|
|
serializedController.FindProperty($"{prefix}{prop}").floatValue = 0f;
|
|
}
|
|
serializedController.ApplyModifiedProperties();
|
|
}
|
|
|
|
private void ApplyPreset(FingerShapedController controller, string presetName)
|
|
{
|
|
// 프리셋 적용 시 양손 모두 활성화
|
|
if (!controller.enabled)
|
|
{
|
|
controller.enabled = true;
|
|
}
|
|
|
|
switch (presetName)
|
|
{
|
|
case "가위":
|
|
SetPreset(controller, 1f, 1f, -1f, -1f, -1f, 0.3f);
|
|
break;
|
|
case "바위":
|
|
SetPreset(controller, -1f, -1f, -1f, -1f, -1f, 0f);
|
|
break;
|
|
case "보":
|
|
SetPreset(controller, 1f, 1f, 1f, 1f, 1f, 1f);
|
|
break;
|
|
case "브이":
|
|
SetPreset(controller, -1f, 1f, 1f, -1f, -1f, 1f);
|
|
break;
|
|
case "검지":
|
|
SetPreset(controller, -1f, 1f, -1f, -1f, -1f, 0f);
|
|
break;
|
|
case "초기화":
|
|
SetPreset(controller, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void SetPreset(FingerShapedController controller, float thumb, float index,
|
|
float middle, float ring, float pinky, float spread)
|
|
{
|
|
if (controller.leftHandEnabled)
|
|
{
|
|
controller.leftThumbCurl = thumb;
|
|
controller.leftIndexCurl = index;
|
|
controller.leftMiddleCurl = middle;
|
|
controller.leftRingCurl = ring;
|
|
controller.leftPinkyCurl = pinky;
|
|
controller.leftSpreadFingers = spread;
|
|
}
|
|
|
|
if (controller.rightHandEnabled)
|
|
{
|
|
controller.rightThumbCurl = thumb;
|
|
controller.rightIndexCurl = index;
|
|
controller.rightMiddleCurl = middle;
|
|
controller.rightRingCurl = ring;
|
|
controller.rightPinkyCurl = pinky;
|
|
controller.rightSpreadFingers = spread;
|
|
}
|
|
|
|
EditorUtility.SetDirty(controller);
|
|
}
|
|
|
|
private void DrawFingerSettings(SerializedObject serializedObject)
|
|
{
|
|
var fingerCopyModeProp = serializedObject.FindProperty("fingerCopyMode");
|
|
var useFingerRoughProp = serializedObject.FindProperty("useFingerRoughMotion");
|
|
var fingerRoughnessProp = serializedObject.FindProperty("fingerRoughness");
|
|
|
|
EditorGUILayout.PropertyField(fingerCopyModeProp, new GUIContent("복제 방식"));
|
|
EditorGUILayout.PropertyField(useFingerRoughProp, new GUIContent("러프 모션 사용"));
|
|
if (useFingerRoughProp.boolValue)
|
|
{
|
|
EditorGUILayout.PropertyField(fingerRoughnessProp, new GUIContent("러프니스"));
|
|
}
|
|
}
|
|
|
|
private void DrawMotionSettings(SerializedObject serializedObject)
|
|
{
|
|
serializedObject.Update();
|
|
|
|
var useMotionFilterProp = serializedObject.FindProperty("useMotionFilter");
|
|
var filterBufferSizeProp = serializedObject.FindProperty("filterBufferSize");
|
|
var useBodyRoughProp = serializedObject.FindProperty("useBodyRoughMotion");
|
|
var bodyRoughnessProp = serializedObject.FindProperty("bodyRoughness");
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
EditorGUILayout.PropertyField(useMotionFilterProp, new GUIContent("모션 필터 사용"));
|
|
if (useMotionFilterProp.boolValue)
|
|
{
|
|
EditorGUILayout.PropertyField(filterBufferSizeProp, new GUIContent("필터 버퍼 크기"));
|
|
}
|
|
EditorGUILayout.PropertyField(useBodyRoughProp, new GUIContent("몸 러프 모션 사용"));
|
|
if (useBodyRoughProp.boolValue)
|
|
{
|
|
EditorGUILayout.PropertyField(bodyRoughnessProp, new GUIContent("몸 러프니스"));
|
|
}
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
EditorUtility.SetDirty(serializedObject.targetObject);
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
|
|
private void DrawWeightSettings(CustomRetargetingScript script)
|
|
{
|
|
var limb = script.GetComponent<LimbWeightController>();
|
|
if (limb != null)
|
|
{
|
|
var serializedLimb = new SerializedObject(limb);
|
|
serializedLimb.Update();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
// 거리 기반 가중치 설정
|
|
EditorGUILayout.LabelField("손과 프랍과의 범위 (가중치 1 -> 0)");
|
|
EditorGUILayout.BeginHorizontal();
|
|
var minDistance = serializedLimb.FindProperty("minDistance");
|
|
var maxDistance = serializedLimb.FindProperty("maxDistance");
|
|
float minVal = minDistance.floatValue;
|
|
float maxVal = maxDistance.floatValue;
|
|
|
|
minVal = EditorGUILayout.FloatField(minVal, GUILayout.Width(50));
|
|
EditorGUILayout.MinMaxSlider(ref minVal, ref maxVal, 0f, 1f);
|
|
maxVal = EditorGUILayout.FloatField(maxVal, GUILayout.Width(50));
|
|
|
|
minDistance.floatValue = minVal;
|
|
maxDistance.floatValue = maxVal;
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
// 허리 거리 범위 설정
|
|
EditorGUILayout.LabelField("의자와 허리 거리 범위(가중치 1 -> 0)");
|
|
EditorGUILayout.BeginHorizontal();
|
|
var hipsMinDistance = serializedLimb.FindProperty("hipsMinDistance");
|
|
var hipsMaxDistance = serializedLimb.FindProperty("hipsMaxDistance");
|
|
float hipsMin = hipsMinDistance.floatValue;
|
|
float hipsMax = hipsMaxDistance.floatValue;
|
|
|
|
hipsMin = EditorGUILayout.FloatField(hipsMin, GUILayout.Width(50));
|
|
EditorGUILayout.MinMaxSlider(ref hipsMin, ref hipsMax, 0f, 1f);
|
|
hipsMax = EditorGUILayout.FloatField(hipsMax, GUILayout.Width(50));
|
|
|
|
hipsMinDistance.floatValue = hipsMin;
|
|
hipsMaxDistance.floatValue = hipsMax;
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
// 바닥 기준 히프 높이 범위
|
|
EditorGUILayout.LabelField("바닥과 허리 높이에 의한 블렌딩 (가중치 0 -> 1)");
|
|
EditorGUILayout.BeginHorizontal();
|
|
var groundHipsMin = serializedLimb.FindProperty("groundHipsMinHeight");
|
|
var groundHipsMax = serializedLimb.FindProperty("groundHipsMaxHeight");
|
|
float groundMin = groundHipsMin.floatValue;
|
|
float groundMax = groundHipsMax.floatValue;
|
|
|
|
groundMin = EditorGUILayout.FloatField(groundMin, GUILayout.Width(50));
|
|
EditorGUILayout.MinMaxSlider(ref groundMin, ref groundMax, 0f, 2f);
|
|
groundMax = EditorGUILayout.FloatField(groundMax, GUILayout.Width(50));
|
|
|
|
groundHipsMin.floatValue = groundMin;
|
|
groundHipsMax.floatValue = groundMax;
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
// 발 높이 기반 가중치 설정 추가
|
|
EditorGUILayout.LabelField("지면으로부터 발의 범위에 의한 IK 블렌딩 (가중치 1 -> 0)");
|
|
EditorGUILayout.BeginHorizontal();
|
|
var footHeightMin = serializedLimb.FindProperty("footHeightMinThreshold");
|
|
var footHeightMax = serializedLimb.FindProperty("footHeightMaxThreshold");
|
|
float footMin = footHeightMin.floatValue;
|
|
float footMax = footHeightMax.floatValue;
|
|
|
|
footMin = EditorGUILayout.FloatField(footMin, GUILayout.Width(50));
|
|
EditorGUILayout.MinMaxSlider(ref footMin, ref footMax, 0.1f, 1f);
|
|
footMax = EditorGUILayout.FloatField(footMax, GUILayout.Width(50));
|
|
|
|
footHeightMin.floatValue = footMin;
|
|
footHeightMax.floatValue = footMax;
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
// 가중치 보간 설정
|
|
var weightSmoothSpeed = serializedLimb.FindProperty("weightSmoothSpeed");
|
|
EditorGUILayout.PropertyField(weightSmoothSpeed, new GUIContent("가중치 변화 속도"));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedLimb.ApplyModifiedProperties();
|
|
EditorUtility.SetDirty(limb);
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 바닥 높이 설정 그리기 함수
|
|
private void DrawFloorSettings(SerializedObject serializedObject)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
var floorHeightProp = serializedObject.FindProperty("floorHeight");
|
|
EditorGUILayout.PropertyField(floorHeightProp, new GUIContent("바닥 높이 (-1 ~ 1)"));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
// 크기 설정 UI 그리기 함수
|
|
private void DrawScaleSettings(SerializedObject serializedObject)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
var avatarScaleProp = serializedObject.FindProperty("avatarScale");
|
|
EditorGUILayout.PropertyField(avatarScaleProp, new GUIContent("아바타 크기"));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
// 창이 포커스를 얻거나 잃을 때 리페인트
|
|
private void OnFocus()
|
|
{
|
|
Repaint();
|
|
}
|
|
|
|
private void OnLostFocus()
|
|
{
|
|
Repaint();
|
|
}
|
|
|
|
// 에디터 업데이트 시 리페인트
|
|
private void OnInspectorUpdate()
|
|
{
|
|
Repaint();
|
|
}
|
|
|
|
private void DrawPropControls(CustomRetargetingScript script)
|
|
{
|
|
var propController = script.GetComponent<PropLocationController>();
|
|
if (propController == null)
|
|
{
|
|
EditorGUILayout.HelpBox("PropLocationController가 없습니다.", MessageType.Warning);
|
|
if (GUILayout.Button("PropLocationController 추가"))
|
|
{
|
|
propController = script.gameObject.AddComponent<PropLocationController>();
|
|
EditorUtility.SetDirty(script.gameObject);
|
|
}
|
|
return;
|
|
}
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
EditorGUILayout.LabelField("오프셋 조정", EditorStyles.boldLabel);
|
|
|
|
// 왼손 오프셋
|
|
Transform leftOffset = propController.GetLeftHandOffset();
|
|
if (leftOffset != null)
|
|
{
|
|
bool showLeftHand = EditorGUILayout.Foldout(leftHandFoldouts.ContainsKey(script.GetInstanceID()) ?
|
|
leftHandFoldouts[script.GetInstanceID()] : true, "왼손 오프셋");
|
|
leftHandFoldouts[script.GetInstanceID()] = showLeftHand;
|
|
|
|
if (showLeftHand)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 leftPos = EditorGUILayout.Vector3Field("위치", leftOffset.localPosition);
|
|
Vector3 leftRot = EditorGUILayout.Vector3Field("회전", leftOffset.localRotation.eulerAngles);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(leftOffset, "Update Left Hand Offset");
|
|
leftOffset.localPosition = leftPos;
|
|
leftOffset.localRotation = Quaternion.Euler(leftRot);
|
|
EditorUtility.SetDirty(leftOffset);
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
|
|
// 오른손 오프셋
|
|
Transform rightOffset = propController.GetRightHandOffset();
|
|
if (rightOffset != null)
|
|
{
|
|
bool showRightHand = EditorGUILayout.Foldout(rightHandFoldouts.ContainsKey(script.GetInstanceID()) ?
|
|
rightHandFoldouts[script.GetInstanceID()] : true, "오른손 오프셋");
|
|
rightHandFoldouts[script.GetInstanceID()] = showRightHand;
|
|
|
|
if (showRightHand)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 rightPos = EditorGUILayout.Vector3Field("위치", rightOffset.localPosition);
|
|
Vector3 rightRot = EditorGUILayout.Vector3Field("회전", rightOffset.localRotation.eulerAngles);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(rightOffset, "Update Right Hand Offset");
|
|
rightOffset.localPosition = rightPos;
|
|
rightOffset.localRotation = Quaternion.Euler(rightRot);
|
|
EditorUtility.SetDirty(rightOffset);
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
|
|
// 머리 오프셋
|
|
Transform headOffset = propController.GetHeadOffset();
|
|
if (headOffset != null)
|
|
{
|
|
bool showHead = EditorGUILayout.Foldout(headFoldouts.ContainsKey(script.GetInstanceID()) ?
|
|
headFoldouts[script.GetInstanceID()] : true, "머리 오프셋");
|
|
headFoldouts[script.GetInstanceID()] = showHead;
|
|
|
|
if (showHead)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 headPos = EditorGUILayout.Vector3Field("위치", headOffset.localPosition);
|
|
Vector3 headRot = EditorGUILayout.Vector3Field("회전", headOffset.localRotation.eulerAngles);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(headOffset, "Update Head Offset");
|
|
headOffset.localPosition = headPos;
|
|
headOffset.localRotation = Quaternion.Euler(headRot);
|
|
EditorUtility.SetDirty(headOffset);
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
|
|
// 프랍 목록
|
|
EditorGUILayout.Space(10);
|
|
EditorGUILayout.LabelField("부착된 프랍 목록", EditorStyles.boldLabel);
|
|
|
|
// 머리 프랍
|
|
EditorGUILayout.BeginVertical("box");
|
|
EditorGUILayout.LabelField("머리 프랍", EditorStyles.boldLabel);
|
|
GameObject[] headProps = propController.GetHeadProps();
|
|
DrawPropList(headProps);
|
|
EditorGUILayout.EndVertical();
|
|
|
|
// 왼손 프랍
|
|
EditorGUILayout.BeginVertical("box");
|
|
EditorGUILayout.LabelField("왼손 프랍", EditorStyles.boldLabel);
|
|
GameObject[] leftHandProps = propController.GetLeftHandProps();
|
|
DrawPropList(leftHandProps);
|
|
EditorGUILayout.EndVertical();
|
|
|
|
// 오른손 프랍
|
|
EditorGUILayout.BeginVertical("box");
|
|
EditorGUILayout.LabelField("오른손 프랍", EditorStyles.boldLabel);
|
|
GameObject[] rightHandProps = propController.GetRightHandProps();
|
|
DrawPropList(rightHandProps);
|
|
EditorGUILayout.EndVertical();
|
|
|
|
// 프랍 이동 버튼
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField("프랍 위치 이동", EditorStyles.boldLabel);
|
|
EditorGUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("머리로 이동", GUILayout.Height(30)))
|
|
{
|
|
propController.MoveToHead();
|
|
}
|
|
if (GUILayout.Button("왼손으로 이동", GUILayout.Height(30)))
|
|
{
|
|
propController.MoveToLeftHand();
|
|
}
|
|
if (GUILayout.Button("오른손으로 이동", GUILayout.Height(30)))
|
|
{
|
|
propController.MoveToRightHand();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space();
|
|
if (GUILayout.Button("프랍 해제", GUILayout.Height(30)))
|
|
{
|
|
Undo.RecordObject(Selection.activeGameObject?.transform, "Detach Prop");
|
|
propController.DetachProp();
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawPropList(GameObject[] props)
|
|
{
|
|
if (props.Length > 0)
|
|
{
|
|
foreach (GameObject prop in props)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField(prop.name);
|
|
if (GUILayout.Button("선택", GUILayout.Width(60)))
|
|
{
|
|
Selection.activeGameObject = prop;
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.LabelField("부착된 프랍 없음");
|
|
}
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class RetargetingPreset
|
|
{
|
|
public int ikIterations;
|
|
public float ikDeltaThreshold;
|
|
public float shoulderBlendStrength;
|
|
public float shoulderMaxHeightDiff;
|
|
public float limbMaxDistance;
|
|
public float limbMinDistance;
|
|
public float footHeightMinThreshold;
|
|
public float footHeightMaxThreshold;
|
|
// ... 기타 필요한 설정들
|
|
} |