Streamingle_URP/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs
user 4a49ecd772 Refactor: 배경/프랍 브라우저 IMGUI→UI Toolkit 전환 + USS 리디자인
- BackgroundSceneLoaderWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField)
- PropBrowserWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField)
- StreamingleCommon.uss: 브라우저 공통 스타일 추가 (그리드/리스트/뷰토글/액션바/상태바)
- excludeFromWeb 상태 새로고침 시 보존 수정
- 삭제된 배경 리소스 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 01:55:48 +09:00

186 lines
8.8 KiB
C#

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<StyleSheet>(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<CustomRetargetingScript>();
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;
}
}
}