From 98676d49dba2b0d8283635b2494d9db774047d70 Mon Sep 17 00:00:00 2001 From: KINDNICK <68893236+KINDNICK@users.noreply.github.com> Date: Sat, 20 Dec 2025 23:14:07 +0900 Subject: [PATCH] =?UTF-8?q?Fix=20:=20=EB=A8=B8=EB=A6=AC=20=ED=9A=8C?= =?UTF-8?q?=EC=A0=84=20=EA=B8=B0=EB=8A=A5=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomRetargetingScript.cs | 47 +++++++++++++++++++ .../Editor/RetargetingControlWindow.cs | 44 +++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs index 68ab3fc9..1870f4fb 100644 --- a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs +++ b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs @@ -108,6 +108,11 @@ namespace KindRetargeting [Header("발목 높이 설정")] [SerializeField] public float minimumAnkleHeight = 0.2f; // 수동 설정용 최소 발목 높이 + [Header("머리 회전 오프셋")] + [SerializeField, Range(-180f, 180f)] public float headRotationOffsetX = 0f; // 머리 좌우 기울기 (Roll) + [SerializeField, Range(-180f, 180f)] public float headRotationOffsetY = 0f; // 머리 좌우 회전 (Yaw) + [SerializeField, Range(-180f, 180f)] public float headRotationOffsetZ = 0f; // 머리 상하 회전 (Pitch) + [Header("설정 저장/로드")] [SerializeField] private string settingsFolderName = "RetargetingSettings"; @@ -171,6 +176,10 @@ namespace KindRetargeting public List sourceMuscleCalibrationCache; // 의자 앉기 높이 오프셋 (LimbWeightController) public float chairSeatHeightOffset; + // 머리 회전 오프셋 + public float headRotationOffsetX; + public float headRotationOffsetY; + public float headRotationOffsetZ; } [System.Serializable] @@ -685,6 +694,9 @@ namespace KindRetargeting fingerCloseRotationsCache = fingerCloseCache, sourceMuscleCalibrationCache = muscleCalibrationCache, chairSeatHeightOffset = chairOffset, + headRotationOffsetX = headRotationOffsetX, + headRotationOffsetY = headRotationOffsetY, + headRotationOffsetZ = headRotationOffsetZ, }; string json = JsonUtility.ToJson(settings, true); @@ -785,6 +797,11 @@ namespace KindRetargeting limbController.chairSeatHeightOffset = settings.chairSeatHeightOffset; } + // 머리 회전 오프셋 로드 + headRotationOffsetX = settings.headRotationOffsetX; + headRotationOffsetY = settings.headRotationOffsetY; + headRotationOffsetZ = settings.headRotationOffsetZ; + //너무 자주 출력되어서 주석처리 //Debug.Log($"설정을 로드했습니다: {filePath}"); } @@ -1356,6 +1373,36 @@ namespace KindRetargeting previousScale = avatarScale; } } + + /// + /// LateUpdate에서 머리 회전 오프셋을 적용합니다 (IK 이후 실행). + /// + void LateUpdate() + { + ApplyHeadRotationOffset(); + } + + /// + /// 머리 본에 회전 오프셋을 적용합니다. + /// + private void ApplyHeadRotationOffset() + { + if (targetAnimator == null) return; + + // 머리 본 가져오기 + Transform headBone = targetAnimator.GetBoneTransform(HumanBodyBones.Head); + if (headBone == null) return; + + // 오프셋이 모두 0이면 스킵 + if (Mathf.Approximately(headRotationOffsetX, 0f) && + Mathf.Approximately(headRotationOffsetY, 0f) && + Mathf.Approximately(headRotationOffsetZ, 0f)) + return; + + // 오프셋 적용 (로컬 회전에 오일러 각도 추가) + Quaternion offsetRotation = Quaternion.Euler(headRotationOffsetX, headRotationOffsetY, headRotationOffsetZ); + headBone.localRotation *= offsetRotation; + } /// /// 머슬 데이터를 사용하여 손가락 포즈를 복제합니다. /// SetHumanPose가 모든 본에 영향을 미치므로, 손가락을 제외한 모든 본의 Transform을 저장하고 복원합니다. diff --git a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs index 8dde2a25..25fa38d6 100644 --- a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs +++ b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs @@ -27,6 +27,7 @@ public class RetargetingControlWindow : EditorWindow private Dictionary rightHandFoldouts = new Dictionary(); private Dictionary headFoldouts = new Dictionary(); private Dictionary propControlFoldouts = new Dictionary(); + private Dictionary headRotationFoldouts = new Dictionary(); private bool isDirty = false; private double lastUpdateTime; @@ -233,6 +234,7 @@ public class RetargetingControlWindow : EditorWindow if (!rightHandFoldouts.ContainsKey(instanceID)) rightHandFoldouts[instanceID] = false; if (!headFoldouts.ContainsKey(instanceID)) headFoldouts[instanceID] = false; if (!propControlFoldouts.ContainsKey(instanceID)) propControlFoldouts[instanceID] = false; + if (!headRotationFoldouts.ContainsKey(instanceID)) headRotationFoldouts[instanceID] = false; SerializedObject serializedObject = new SerializedObject(script); @@ -387,6 +389,15 @@ public class RetargetingControlWindow : EditorWindow EditorGUI.indentLevel--; } + // 머리 회전 오프셋 설정 섹션 + headRotationFoldouts[instanceID] = EditorGUILayout.Foldout(headRotationFoldouts[instanceID], "머리 회전 오프셋", true); + if (headRotationFoldouts[instanceID]) + { + EditorGUI.indentLevel++; + DrawHeadRotationSettings(serializedObject); + EditorGUI.indentLevel--; + } + // 프랍 컨트롤 설정 propControlFoldouts[instanceID] = EditorGUILayout.Foldout(propControlFoldouts[instanceID], "프랍 설정", true); if (propControlFoldouts[instanceID]) @@ -1114,6 +1125,39 @@ public class RetargetingControlWindow : EditorWindow } } + // 머리 회전 오프셋 UI 그리기 함수 + private void DrawHeadRotationSettings(SerializedObject serializedObject) + { + EditorGUI.BeginChangeCheck(); + + var headRotationXProp = serializedObject.FindProperty("headRotationOffsetX"); + var headRotationYProp = serializedObject.FindProperty("headRotationOffsetY"); + var headRotationZProp = serializedObject.FindProperty("headRotationOffsetZ"); + + EditorGUILayout.Slider(headRotationXProp, -180f, 180f, + new GUIContent("X (Roll) - 좌우 기울기", "머리를 좌우로 기울입니다")); + EditorGUILayout.Slider(headRotationYProp, -180f, 180f, + new GUIContent("Y (Yaw) - 좌우 회전", "머리를 좌우로 회전합니다")); + EditorGUILayout.Slider(headRotationZProp, -180f, 180f, + new GUIContent("Z (Pitch) - 상하 회전", "머리를 상하로 회전합니다")); + + // 초기화 버튼 + EditorGUILayout.Space(5); + if (GUILayout.Button("회전 초기화", GUILayout.Height(25))) + { + headRotationXProp.floatValue = 0f; + headRotationYProp.floatValue = 0f; + headRotationZProp.floatValue = 0f; + serializedObject.ApplyModifiedProperties(); + EditorUtility.SetDirty(serializedObject.targetObject); + } + + if (EditorGUI.EndChangeCheck()) + { + serializedObject.ApplyModifiedProperties(); + } + } + // 창이 포커스를 얻거나 잃을 때 리페인트 private void OnFocus() {