using UnityEngine; using UnityEditor; using UnityEngine.UIElements; using UnityEditor.UIElements; namespace KindRetargeting { [CustomEditor(typeof(LimbWeightController))] public class LimbWeightControllerEditor : BaseRetargetingEditor { private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"; SerializedProperty maxDistance; SerializedProperty minDistance; SerializedProperty weightSmoothSpeed; SerializedProperty middleWeightMultiplier; SerializedProperty hipsMinDistance; SerializedProperty hipsMaxDistance; SerializedProperty props; SerializedProperty characterRoot; SerializedProperty groundHipsMinHeight; SerializedProperty groundHipsMaxHeight; SerializedProperty footHeightMinThreshold; SerializedProperty footHeightMaxThreshold; SerializedProperty enableLeftArmIK; SerializedProperty enableRightArmIK; SerializedProperty chairSeatHeightOffset; protected override void OnEnable() { base.OnEnable(); if (serializedObject == null) return; maxDistance = serializedObject.FindProperty("maxDistance"); minDistance = serializedObject.FindProperty("minDistance"); weightSmoothSpeed = serializedObject.FindProperty("weightSmoothSpeed"); middleWeightMultiplier = serializedObject.FindProperty("middleWeightMultiplier"); hipsMinDistance = serializedObject.FindProperty("hipsMinDistance"); hipsMaxDistance = serializedObject.FindProperty("hipsMaxDistance"); props = serializedObject.FindProperty("props"); characterRoot = serializedObject.FindProperty("characterRoot"); groundHipsMinHeight = serializedObject.FindProperty("groundHipsMinHeight"); groundHipsMaxHeight = serializedObject.FindProperty("groundHipsMaxHeight"); footHeightMinThreshold = serializedObject.FindProperty("footHeightMinThreshold"); footHeightMaxThreshold = serializedObject.FindProperty("footHeightMaxThreshold"); enableLeftArmIK = serializedObject.FindProperty("enableLeftArmIK"); enableRightArmIK = serializedObject.FindProperty("enableRightArmIK"); chairSeatHeightOffset = serializedObject.FindProperty("chairSeatHeightOffset"); } public override VisualElement CreateInspectorGUI() { if (serializedObject == null || target == null) return new VisualElement(); var root = new VisualElement(); var commonUss = AssetDatabase.LoadAssetAtPath(CommonUssPath); if (commonUss != null) root.styleSheets.Add(commonUss); // IK 활성화 설정 var ikFoldout = new Foldout { text = "IK 활성화 설정", value = true }; ikFoldout.Add(new PropertyField(enableLeftArmIK, "왼팔 IK 활성화")); ikFoldout.Add(new PropertyField(enableRightArmIK, "오른팔 IK 활성화")); root.Add(ikFoldout); // 거리 기반 가중치 설정 var distFoldout = new Foldout { text = "거리 기반 가중치 설정", value = true }; distFoldout.Add(BuildMinMaxRange("거리 범위 (가중치 1 → 0)", minDistance, maxDistance, 0f, 1f)); root.Add(distFoldout); // 가중치 변화 설정 var weightFoldout = new Foldout { text = "가중치 변화 설정", value = true }; weightFoldout.Add(new PropertyField(weightSmoothSpeed, "가중치 변화 속도")); root.Add(weightFoldout); // 허리 가중치 설정 var hipsFoldout = new Foldout { text = "허리 가중치 설정", value = true }; hipsFoldout.Add(BuildMinMaxRange("허리 거리 범위 (가중치 1 → 0)", hipsMinDistance, hipsMaxDistance, 0f, 1f)); root.Add(hipsFoldout); // 바닥 기준 히프 보정 설정 var groundFoldout = new Foldout { text = "바닥 기준 히프 보정 설정", value = true }; groundFoldout.Add(BuildMinMaxRange("바닥 기준 히프 높이 범위 (가중치 0 → 1)", groundHipsMinHeight, groundHipsMaxHeight, 0f, 2f)); root.Add(groundFoldout); // 발 높이 기반 가중치 설정 var footFoldout = new Foldout { text = "발 높이 기반 가중치 설정", value = true }; footFoldout.Add(BuildMinMaxRange("발 높이 범위 (가중치 1 → 0)", footHeightMinThreshold, footHeightMaxThreshold, 0.1f, 1f)); root.Add(footFoldout); // 의자 앉기 높이 설정 var chairFoldout = new Foldout { text = "의자 앉기 높이 설정", value = true }; chairFoldout.Add(new PropertyField(chairSeatHeightOffset, "좌석 높이 오프셋")); root.Add(chairFoldout); // 참조 설정 var refFoldout = new Foldout { text = "참조 설정", value = true }; refFoldout.Add(new PropertyField(props, "프랍 오브젝트")); refFoldout.Add(new PropertyField(characterRoot, "캐릭터 루트")); root.Add(refFoldout); // 변경 감지 root.TrackSerializedObjectValue(serializedObject, so => { if (target == null) return; MarkDirty(); var script = (LimbWeightController)target; var retargeting = script.GetComponent(); if (retargeting != null) EditorUtility.SetDirty(retargeting); }); return root; } private VisualElement BuildMinMaxRange(string label, SerializedProperty minProp, SerializedProperty maxProp, float limitMin, float limitMax) { var container = new VisualElement { style = { marginBottom = 4 } }; if (minProp == null || maxProp == null) { container.Add(new HelpBox($"'{label}' 프로퍼티를 찾을 수 없습니다.", HelpBoxMessageType.Warning)); return container; } container.Add(new Label(label) { style = { marginBottom = 2 } }); var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } }; var minField = new FloatField { value = minProp.floatValue, style = { width = 50 } }; var slider = new MinMaxSlider(minProp.floatValue, maxProp.floatValue, limitMin, limitMax); slider.style.flexGrow = 1; slider.style.marginLeft = slider.style.marginRight = 4; var maxField = new FloatField { value = maxProp.floatValue, style = { width = 50 } }; // MinMaxSlider → sync fields + properties slider.RegisterValueChangedCallback(evt => { minProp.floatValue = evt.newValue.x; maxProp.floatValue = evt.newValue.y; serializedObject.ApplyModifiedProperties(); minField.SetValueWithoutNotify(evt.newValue.x); maxField.SetValueWithoutNotify(evt.newValue.y); }); // FloatField min → sync slider + property minField.RegisterValueChangedCallback(evt => { float clamped = Mathf.Clamp(evt.newValue, limitMin, maxProp.floatValue); minProp.floatValue = clamped; serializedObject.ApplyModifiedProperties(); slider.SetValueWithoutNotify(new Vector2(clamped, maxProp.floatValue)); minField.SetValueWithoutNotify(clamped); }); // FloatField max → sync slider + property maxField.RegisterValueChangedCallback(evt => { float clamped = Mathf.Clamp(evt.newValue, minProp.floatValue, limitMax); maxProp.floatValue = clamped; serializedObject.ApplyModifiedProperties(); slider.SetValueWithoutNotify(new Vector2(minProp.floatValue, clamped)); maxField.SetValueWithoutNotify(clamped); }); // Track external changes (undo, etc.) container.TrackPropertyValue(minProp, p => { minField.SetValueWithoutNotify(p.floatValue); slider.SetValueWithoutNotify(new Vector2(p.floatValue, maxProp.floatValue)); }); container.TrackPropertyValue(maxProp, p => { maxField.SetValueWithoutNotify(p.floatValue); slider.SetValueWithoutNotify(new Vector2(minProp.floatValue, p.floatValue)); }); row.Add(minField); row.Add(slider); row.Add(maxField); container.Add(row); return container; } } }