using UnityEngine; using UnityEditor; using UnityEngine.UIElements; using UnityEditor.UIElements; using KindRetargeting; [CustomEditor(typeof(FullBodyInverseKinematics))] public class FullBodyInverseKinematicsEditor : BaseRetargetingEditor { private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"; private SerializedProperty iterationsProp; private SerializedProperty deltaThresholdProp; private SerializedProperty[] limbProps; private readonly string[] limbNames = { "왼팔", "오른팔", "왼다리", "오른다리" }; private readonly string[] limbPropNames = { "leftArm", "rightArm", "leftLeg", "rightLeg" }; protected override void OnEnable() { base.OnEnable(); iterationsProp = serializedObject.FindProperty("iterations"); deltaThresholdProp = serializedObject.FindProperty("deltaThreshold"); limbProps = new SerializedProperty[4]; for (int i = 0; i < 4; i++) limbProps[i] = serializedObject.FindProperty(limbPropNames[i]); } public override VisualElement CreateInspectorGUI() { var root = new VisualElement(); var commonUss = AssetDatabase.LoadAssetAtPath(CommonUssPath); if (commonUss != null) root.styleSheets.Add(commonUss); // IK 기본 설정 var basicFoldout = new Foldout { text = "IK 기본 설정", value = true }; basicFoldout.Add(new PropertyField(iterationsProp, "반복 횟수")); basicFoldout.Add(new PropertyField(deltaThresholdProp, "델타 임계값")); root.Add(basicFoldout); // 사지 설정 var limbFoldout = new Foldout { text = "사지 설정" }; for (int i = 0; i < 4; i++) limbFoldout.Add(BuildLimbElement(i)); root.Add(limbFoldout); return root; } private VisualElement BuildLimbElement(int index) { var container = new VisualElement(); container.style.backgroundColor = new Color(0, 0, 0, 0.1f); container.style.borderTopLeftRadius = container.style.borderTopRightRadius = container.style.borderBottomLeftRadius = container.style.borderBottomRightRadius = 4; container.style.paddingTop = container.style.paddingBottom = container.style.paddingLeft = container.style.paddingRight = 4; container.style.marginBottom = 4; if (index >= limbProps.Length || limbProps[index] == null) { container.Add(new HelpBox($"'{limbPropNames[index]}' 프로퍼티를 찾을 수 없습니다.", HelpBoxMessageType.Warning)); return container; } var foldout = new Foldout { text = limbNames[index] }; var enableIKProp = limbProps[index].FindPropertyRelative("enableIK"); if (enableIKProp == null) { container.Add(foldout); return container; } foldout.Add(new PropertyField(enableIKProp, "IK 활성화")); var ikSettings = new VisualElement { style = { marginTop = 4 } }; // 엔드 타겟 var endTargetProp = limbProps[index].FindPropertyRelative("endTarget"); if (endTargetProp != null) { ikSettings.Add(new PropertyField(endTargetProp.FindPropertyRelative("target"), "엔드 타겟")); ikSettings.Add(new PropertyField(endTargetProp.FindPropertyRelative("weight"), "엔드 가중치")); } // 미들 타겟 var middleTargetProp = limbProps[index].FindPropertyRelative("middleTarget"); if (middleTargetProp != null) { ikSettings.Add(new PropertyField(middleTargetProp.FindPropertyRelative("target"), "미들 타겟")); ikSettings.Add(new PropertyField(middleTargetProp.FindPropertyRelative("weight"), "미들 가중치")); } // 발목 타겟 var ankleTargetProp = limbProps[index].FindPropertyRelative("ankleTarget"); if (ankleTargetProp != null) { ikSettings.Add(new PropertyField(ankleTargetProp.FindPropertyRelative("target"), "발목 타겟")); ikSettings.Add(new PropertyField(ankleTargetProp.FindPropertyRelative("weight"), "발목 가중치")); } foldout.Add(ikSettings); // enableIK 토글에 따라 설정 표시/숨김 foldout.TrackPropertyValue(enableIKProp, prop => { ikSettings.style.display = prop.boolValue ? DisplayStyle.Flex : DisplayStyle.None; }); ikSettings.style.display = enableIKProp.boolValue ? DisplayStyle.Flex : DisplayStyle.None; container.Add(foldout); return container; } }