Refactor : PropLocationController를 Serializable 모듈로 전환
- PropLocationController: MonoBehaviour → [Serializable] 클래스 - Start() → Initialize(Animator), GetComponent 제거 - CRS에서 propLocation 필드로 소유 및 초기화 - RetargetingControlWindow: GetComponent → script.propLocation 직접 접근 - PropLocationControllerEditor 삭제 (MonoBehaviour 아니므로 불필요) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
52d6960710
commit
5c65185a61
@ -98,6 +98,9 @@ namespace KindRetargeting
|
|||||||
[Header("어깨 보정")]
|
[Header("어깨 보정")]
|
||||||
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
|
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
|
||||||
|
|
||||||
|
[Header("프랍 부착")]
|
||||||
|
[SerializeField] public PropLocationController propLocation = new PropLocationController();
|
||||||
|
|
||||||
[Header("아바타 크기 조정")]
|
[Header("아바타 크기 조정")]
|
||||||
[SerializeField, Range(0.1f, 3f)] private float avatarScale = 1f;
|
[SerializeField, Range(0.1f, 3f)] private float avatarScale = 1f;
|
||||||
private float previousScale = 1f;
|
private float previousScale = 1f;
|
||||||
@ -325,6 +328,10 @@ namespace KindRetargeting
|
|||||||
// 어깨 보정 모듈 초기화
|
// 어깨 보정 모듈 초기화
|
||||||
if (targetAnimator != null)
|
if (targetAnimator != null)
|
||||||
shoulderCorrection.Initialize(targetAnimator);
|
shoulderCorrection.Initialize(targetAnimator);
|
||||||
|
|
||||||
|
// 프랍 부착 모듈 초기화
|
||||||
|
if (targetAnimator != null)
|
||||||
|
propLocation.Initialize(targetAnimator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -1,167 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine.UIElements;
|
|
||||||
using UnityEditor.UIElements;
|
|
||||||
|
|
||||||
namespace KindRetargeting
|
|
||||||
{
|
|
||||||
[CustomEditor(typeof(PropLocationController))]
|
|
||||||
public class PropLocationControllerEditor : BaseRetargetingEditor
|
|
||||||
{
|
|
||||||
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
|
||||||
|
|
||||||
private VisualElement offsetContainer;
|
|
||||||
private VisualElement propListContainer;
|
|
||||||
private PropLocationController controller;
|
|
||||||
|
|
||||||
public override VisualElement CreateInspectorGUI()
|
|
||||||
{
|
|
||||||
controller = target as PropLocationController;
|
|
||||||
if (controller == null) return new VisualElement();
|
|
||||||
var root = new VisualElement();
|
|
||||||
|
|
||||||
var commonUss = AssetDatabase.LoadAssetAtPath<StyleSheet>(CommonUssPath);
|
|
||||||
if (commonUss != null) root.styleSheets.Add(commonUss);
|
|
||||||
|
|
||||||
// 기본 프로퍼티
|
|
||||||
var iterator = serializedObject.GetIterator();
|
|
||||||
iterator.NextVisible(true); // skip m_Script
|
|
||||||
while (iterator.NextVisible(false))
|
|
||||||
{
|
|
||||||
var field = new PropertyField(iterator.Copy());
|
|
||||||
root.Add(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 오프셋 조정 섹션
|
|
||||||
root.Add(new Label("오프셋 조정") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginTop = 10 } });
|
|
||||||
offsetContainer = new VisualElement();
|
|
||||||
root.Add(offsetContainer);
|
|
||||||
|
|
||||||
// 부착된 프랍 목록 섹션
|
|
||||||
root.Add(new Label("부착된 프랍 목록") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginTop = 10 } });
|
|
||||||
propListContainer = new VisualElement();
|
|
||||||
root.Add(propListContainer);
|
|
||||||
|
|
||||||
// 프랍 위치 이동 버튼
|
|
||||||
root.Add(new Label("프랍 위치 이동") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginTop = 10 } });
|
|
||||||
var btnRow = new VisualElement { style = { flexDirection = FlexDirection.Row, marginTop = 4 } };
|
|
||||||
|
|
||||||
var headBtn = new Button(() => controller.MoveToHead()) { text = "머리로 이동" };
|
|
||||||
headBtn.style.flexGrow = 1; headBtn.style.height = 30; headBtn.style.marginRight = 2;
|
|
||||||
btnRow.Add(headBtn);
|
|
||||||
|
|
||||||
var leftBtn = new Button(() => controller.MoveToLeftHand()) { text = "왼손으로 이동" };
|
|
||||||
leftBtn.style.flexGrow = 1; leftBtn.style.height = 30; leftBtn.style.marginRight = 2;
|
|
||||||
btnRow.Add(leftBtn);
|
|
||||||
|
|
||||||
var rightBtn = new Button(() => controller.MoveToRightHand()) { text = "오른손으로 이동" };
|
|
||||||
rightBtn.style.flexGrow = 1; rightBtn.style.height = 30;
|
|
||||||
btnRow.Add(rightBtn);
|
|
||||||
|
|
||||||
root.Add(btnRow);
|
|
||||||
|
|
||||||
var detachBtn = new Button(() =>
|
|
||||||
{
|
|
||||||
if (Selection.activeGameObject != null)
|
|
||||||
Undo.RecordObject(Selection.activeGameObject.transform, "Detach Prop");
|
|
||||||
controller.DetachProp();
|
|
||||||
}) { text = "프랍 해제" };
|
|
||||||
detachBtn.style.height = 30;
|
|
||||||
detachBtn.style.marginTop = 4;
|
|
||||||
root.Add(detachBtn);
|
|
||||||
|
|
||||||
// 주기적으로 동적 UI 갱신
|
|
||||||
root.schedule.Execute(() => RebuildDynamicUI()).Every(500);
|
|
||||||
RebuildDynamicUI();
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RebuildDynamicUI()
|
|
||||||
{
|
|
||||||
if (controller == null) return;
|
|
||||||
RebuildOffsets();
|
|
||||||
RebuildPropLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RebuildOffsets()
|
|
||||||
{
|
|
||||||
if (offsetContainer == null) return;
|
|
||||||
offsetContainer.Clear();
|
|
||||||
|
|
||||||
BuildOffsetSection(offsetContainer, "왼손 오프셋", controller.GetLeftHandOffset());
|
|
||||||
BuildOffsetSection(offsetContainer, "오른손 오프셋", controller.GetRightHandOffset());
|
|
||||||
BuildOffsetSection(offsetContainer, "머리 오프셋", controller.GetHeadOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildOffsetSection(VisualElement parent, string label, Transform offset)
|
|
||||||
{
|
|
||||||
if (offset == null) return;
|
|
||||||
|
|
||||||
var foldout = new Foldout { text = label, value = true };
|
|
||||||
|
|
||||||
var posField = new Vector3Field("위치") { value = offset.localPosition };
|
|
||||||
posField.RegisterValueChangedCallback(evt =>
|
|
||||||
{
|
|
||||||
Undo.RecordObject(offset, $"Update {label}");
|
|
||||||
offset.localPosition = evt.newValue;
|
|
||||||
EditorUtility.SetDirty(offset);
|
|
||||||
});
|
|
||||||
foldout.Add(posField);
|
|
||||||
|
|
||||||
var rotField = new Vector3Field("회전") { value = offset.localRotation.eulerAngles };
|
|
||||||
rotField.RegisterValueChangedCallback(evt =>
|
|
||||||
{
|
|
||||||
Undo.RecordObject(offset, $"Update {label}");
|
|
||||||
offset.localRotation = Quaternion.Euler(evt.newValue);
|
|
||||||
EditorUtility.SetDirty(offset);
|
|
||||||
});
|
|
||||||
foldout.Add(rotField);
|
|
||||||
|
|
||||||
parent.Add(foldout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RebuildPropLists()
|
|
||||||
{
|
|
||||||
if (propListContainer == null) return;
|
|
||||||
propListContainer.Clear();
|
|
||||||
|
|
||||||
BuildPropListSection(propListContainer, "머리 프랍", controller.GetHeadProps());
|
|
||||||
BuildPropListSection(propListContainer, "왼손 프랍", controller.GetLeftHandProps());
|
|
||||||
BuildPropListSection(propListContainer, "오른손 프랍", controller.GetRightHandProps());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildPropListSection(VisualElement parent, string title, GameObject[] propList)
|
|
||||||
{
|
|
||||||
var box = new VisualElement();
|
|
||||||
box.style.backgroundColor = new Color(0, 0, 0, 0.1f);
|
|
||||||
box.style.borderTopLeftRadius = box.style.borderTopRightRadius =
|
|
||||||
box.style.borderBottomLeftRadius = box.style.borderBottomRightRadius = 4;
|
|
||||||
box.style.paddingTop = box.style.paddingBottom =
|
|
||||||
box.style.paddingLeft = box.style.paddingRight = 4;
|
|
||||||
box.style.marginBottom = 4;
|
|
||||||
|
|
||||||
box.Add(new Label(title) { style = { unityFontStyleAndWeight = FontStyle.Bold } });
|
|
||||||
|
|
||||||
if (propList.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (var prop in propList)
|
|
||||||
{
|
|
||||||
if (prop == null) continue;
|
|
||||||
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginTop = 2 } };
|
|
||||||
row.Add(new Label(prop.name) { style = { flexGrow = 1 } });
|
|
||||||
var selectBtn = new Button(() => Selection.activeGameObject = prop) { text = "선택" };
|
|
||||||
selectBtn.style.width = 60;
|
|
||||||
row.Add(selectBtn);
|
|
||||||
box.Add(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
box.Add(new Label("부착된 프랍 없음") { style = { color = new Color(0.6f, 0.6f, 0.6f) } });
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.Add(box);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a49ee1ae55b970e4c8ca00ccae5d6f97
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -148,8 +148,7 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
s.gameObject.AddComponent<LimbWeightController>();
|
s.gameObject.AddComponent<LimbWeightController>();
|
||||||
if (s.GetComponent<FingerShapedController>() == null)
|
if (s.GetComponent<FingerShapedController>() == null)
|
||||||
s.gameObject.AddComponent<FingerShapedController>();
|
s.gameObject.AddComponent<FingerShapedController>();
|
||||||
if (s.GetComponent<PropLocationController>() == null)
|
// PropLocationController는 CRS 내부 모듈로 이동됨
|
||||||
s.gameObject.AddComponent<PropLocationController>();
|
|
||||||
EditorUtility.SetDirty(s.gameObject);
|
EditorUtility.SetDirty(s.gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,19 +605,7 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
private VisualElement BuildPropSection(CustomRetargetingScript script)
|
private VisualElement BuildPropSection(CustomRetargetingScript script)
|
||||||
{
|
{
|
||||||
var foldout = new Foldout { text = "프랍 설정", value = false };
|
var foldout = new Foldout { text = "프랍 설정", value = false };
|
||||||
var propController = script.GetComponent<PropLocationController>();
|
var propController = script.propLocation;
|
||||||
if (propController == null)
|
|
||||||
{
|
|
||||||
foldout.Add(new HelpBox("PropLocationController가 없습니다.", HelpBoxMessageType.Warning));
|
|
||||||
var addBtn = new Button(() =>
|
|
||||||
{
|
|
||||||
script.gameObject.AddComponent<PropLocationController>();
|
|
||||||
EditorUtility.SetDirty(script.gameObject);
|
|
||||||
RebuildCharacterPanels();
|
|
||||||
}) { text = "PropLocationController 추가" };
|
|
||||||
foldout.Add(addBtn);
|
|
||||||
return foldout;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dynamicContainer = new VisualElement();
|
var dynamicContainer = new VisualElement();
|
||||||
foldout.Add(dynamicContainer);
|
foldout.Add(dynamicContainer);
|
||||||
|
|||||||
@ -4,7 +4,8 @@ using UniHumanoid;
|
|||||||
|
|
||||||
namespace KindRetargeting
|
namespace KindRetargeting
|
||||||
{
|
{
|
||||||
public class PropLocationController : MonoBehaviour
|
[System.Serializable]
|
||||||
|
public class PropLocationController
|
||||||
{
|
{
|
||||||
// 캐시된 타겟과 오프셋 Transform
|
// 캐시된 타겟과 오프셋 Transform
|
||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
@ -18,9 +19,9 @@ namespace KindRetargeting
|
|||||||
[SerializeField] private TargetOffset rightHandTargetOffset;
|
[SerializeField] private TargetOffset rightHandTargetOffset;
|
||||||
[SerializeField] private TargetOffset headTargetOffset;
|
[SerializeField] private TargetOffset headTargetOffset;
|
||||||
|
|
||||||
private void Start()
|
public void Initialize(Animator animator)
|
||||||
{
|
{
|
||||||
CreateTargets();
|
CreateTargets(animator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTPose(Animator animator)
|
public void SetTPose(Animator animator)
|
||||||
@ -31,7 +32,6 @@ namespace KindRetargeting
|
|||||||
Avatar avatar = animator.avatar;
|
Avatar avatar = animator.avatar;
|
||||||
Transform transform = animator.transform;
|
Transform transform = animator.transform;
|
||||||
|
|
||||||
// HumanPoseClip에 저장된 T-포즈 데이터를 로드하여 적용
|
|
||||||
var humanPoseClip = Resources.Load<HumanPoseClip>(HumanPoseClip.TPoseResourcePath);
|
var humanPoseClip = Resources.Load<HumanPoseClip>(HumanPoseClip.TPoseResourcePath);
|
||||||
if (humanPoseClip != null)
|
if (humanPoseClip != null)
|
||||||
{
|
{
|
||||||
@ -43,9 +43,9 @@ namespace KindRetargeting
|
|||||||
Debug.LogWarning("T-Pose 데이터가 존재하지 않습니다.");
|
Debug.LogWarning("T-Pose 데이터가 존재하지 않습니다.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void CreateTargets()
|
|
||||||
|
private void CreateTargets(Animator animator)
|
||||||
{
|
{
|
||||||
Animator animator = GetComponent<Animator>();
|
|
||||||
SetTPose(animator);
|
SetTPose(animator);
|
||||||
|
|
||||||
// 왼손 타겟 및 오프셋 설정
|
// 왼손 타겟 및 오프셋 설정
|
||||||
@ -54,7 +54,7 @@ namespace KindRetargeting
|
|||||||
{
|
{
|
||||||
leftHandTargetOffset = new TargetOffset();
|
leftHandTargetOffset = new TargetOffset();
|
||||||
GameObject leftTarget = new GameObject("Left_Hand_Target");
|
GameObject leftTarget = new GameObject("Left_Hand_Target");
|
||||||
leftTarget.transform.parent = leftHandBone; // 왼손 본에 직접 부모 설정
|
leftTarget.transform.parent = leftHandBone;
|
||||||
leftHandTargetOffset.target = leftTarget.transform;
|
leftHandTargetOffset.target = leftTarget.transform;
|
||||||
leftTarget.transform.position = leftHandBone.position + new Vector3(-0.039f, -0.022f, 0f);
|
leftTarget.transform.position = leftHandBone.position + new Vector3(-0.039f, -0.022f, 0f);
|
||||||
leftTarget.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
|
leftTarget.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
|
||||||
@ -63,7 +63,6 @@ namespace KindRetargeting
|
|||||||
leftOffset.transform.parent = leftTarget.transform;
|
leftOffset.transform.parent = leftTarget.transform;
|
||||||
leftHandTargetOffset.offset = leftOffset.transform;
|
leftHandTargetOffset.offset = leftOffset.transform;
|
||||||
|
|
||||||
// 로컬 포지션과 로테이션 설정
|
|
||||||
leftHandTargetOffset.offset.localPosition = Vector3.zero;
|
leftHandTargetOffset.offset.localPosition = Vector3.zero;
|
||||||
leftHandTargetOffset.offset.localRotation = Quaternion.identity;
|
leftHandTargetOffset.offset.localRotation = Quaternion.identity;
|
||||||
}
|
}
|
||||||
@ -74,7 +73,7 @@ namespace KindRetargeting
|
|||||||
{
|
{
|
||||||
rightHandTargetOffset = new TargetOffset();
|
rightHandTargetOffset = new TargetOffset();
|
||||||
GameObject rightTarget = new GameObject("Right_Hand_Target");
|
GameObject rightTarget = new GameObject("Right_Hand_Target");
|
||||||
rightTarget.transform.parent = rightHandBone; // 오른손 본에 직접 부모 설정
|
rightTarget.transform.parent = rightHandBone;
|
||||||
rightHandTargetOffset.target = rightTarget.transform;
|
rightHandTargetOffset.target = rightTarget.transform;
|
||||||
rightTarget.transform.position = rightHandBone.position + new Vector3(0.039f, -0.022f, 0f);
|
rightTarget.transform.position = rightHandBone.position + new Vector3(0.039f, -0.022f, 0f);
|
||||||
rightTarget.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
|
rightTarget.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
|
||||||
@ -83,7 +82,6 @@ namespace KindRetargeting
|
|||||||
rightOffset.transform.parent = rightTarget.transform;
|
rightOffset.transform.parent = rightTarget.transform;
|
||||||
rightHandTargetOffset.offset = rightOffset.transform;
|
rightHandTargetOffset.offset = rightOffset.transform;
|
||||||
|
|
||||||
// 로컬 포지션과 로테이션 설정
|
|
||||||
rightHandTargetOffset.offset.localPosition = Vector3.zero;
|
rightHandTargetOffset.offset.localPosition = Vector3.zero;
|
||||||
rightHandTargetOffset.offset.localRotation = Quaternion.identity;
|
rightHandTargetOffset.offset.localRotation = Quaternion.identity;
|
||||||
}
|
}
|
||||||
@ -94,7 +92,7 @@ namespace KindRetargeting
|
|||||||
{
|
{
|
||||||
headTargetOffset = new TargetOffset();
|
headTargetOffset = new TargetOffset();
|
||||||
GameObject headTarget = new GameObject("Head_Target");
|
GameObject headTarget = new GameObject("Head_Target");
|
||||||
headTarget.transform.parent = headBone; // 머리 본에 직접 부모 설정
|
headTarget.transform.parent = headBone;
|
||||||
headTargetOffset.target = headTarget.transform;
|
headTargetOffset.target = headTarget.transform;
|
||||||
headTarget.transform.position = headBone.position + new Vector3(0f, 0.16f, 0f);
|
headTarget.transform.position = headBone.position + new Vector3(0f, 0.16f, 0f);
|
||||||
headTarget.transform.rotation = Quaternion.Euler(0f, 0f, 0f);
|
headTarget.transform.rotation = Quaternion.Euler(0f, 0f, 0f);
|
||||||
@ -103,7 +101,6 @@ namespace KindRetargeting
|
|||||||
headOffset.transform.parent = headTarget.transform;
|
headOffset.transform.parent = headTarget.transform;
|
||||||
headTargetOffset.offset = headOffset.transform;
|
headTargetOffset.offset = headOffset.transform;
|
||||||
|
|
||||||
// 기본 오프셋 설정
|
|
||||||
headTargetOffset.offset.localPosition = Vector3.zero;
|
headTargetOffset.offset.localPosition = Vector3.zero;
|
||||||
headTargetOffset.offset.localRotation = Quaternion.identity;
|
headTargetOffset.offset.localRotation = Quaternion.identity;
|
||||||
}
|
}
|
||||||
@ -172,7 +169,6 @@ namespace KindRetargeting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 에디터에서 사용할 메서드들
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
public void MoveToHead()
|
public void MoveToHead()
|
||||||
{
|
{
|
||||||
@ -195,7 +191,6 @@ namespace KindRetargeting
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 오프셋 getter 메서드들 추가
|
|
||||||
public Transform GetLeftHandOffset()
|
public Transform GetLeftHandOffset()
|
||||||
{
|
{
|
||||||
return leftHandTargetOffset?.offset;
|
return leftHandTargetOffset?.offset;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user