using UnityEngine; using KindRetargeting; [DefaultExecutionOrder(16001)] public class SimplePoseTransfer : MonoBehaviour { [Header("Pose Transfer Settings")] public Animator sourceBone; public Animator[] targetBones; [Header("Scale Transfer")] [Tooltip("소스의 머리 스케일을 타겟에도 적용")] public bool transferHeadScale = true; // 캐싱된 Transform들 private Transform[] cachedSourceBones; private Transform[,] cachedTargetBones; // 각 targetBone의 초기 회전 차이를 저장 private Quaternion[,] boneRotationDifferences; // 머리 스케일 관련 private CustomRetargetingScript sourceRetargetingScript; private Vector3[] originalTargetHeadScales; private void Start() { Init(); } public void Init() { if (targetBones == null || targetBones.Length == 0) { Debug.LogError("Target bones are null or empty"); return; } if (sourceBone == null) { Debug.LogError("Source bone is null"); return; } InitializeTargetBones(); CacheAllBoneTransforms(); CacheHeadScales(); Debug.Log($"SimplePoseTransfer initialized with {targetBones.Length} targets"); } private void CacheHeadScales() { // 소스에서 CustomRetargetingScript 찾기 sourceRetargetingScript = sourceBone.GetComponent(); // 타겟들의 원본 머리 스케일 저장 originalTargetHeadScales = new Vector3[targetBones.Length]; for (int i = 0; i < targetBones.Length; i++) { if (targetBones[i] != null) { Transform headBone = targetBones[i].GetBoneTransform(HumanBodyBones.Head); if (headBone != null) { originalTargetHeadScales[i] = headBone.localScale; } else { originalTargetHeadScales[i] = Vector3.one; } } } } private void InitializeTargetBones() { boneRotationDifferences = new Quaternion[targetBones.Length, 55]; for (int i = 0; i < targetBones.Length; i++) { if (targetBones[i] == null) { Debug.LogError($"targetBones[{i}] is null"); continue; } // 55개의 휴머노이드 본에 대해 회전 차이 계산 for (int j = 0; j < 55; j++) { Transform sourceBoneTransform = sourceBone.GetBoneTransform((HumanBodyBones)j); Transform targetBoneTransform = targetBones[i].GetBoneTransform((HumanBodyBones)j); if (sourceBoneTransform != null && targetBoneTransform != null) { boneRotationDifferences[i, j] = Quaternion.Inverse(sourceBoneTransform.rotation) * targetBoneTransform.rotation; } } } } private void CacheAllBoneTransforms() { // 소스 본 캐싱 cachedSourceBones = new Transform[55]; for (int i = 0; i < 55; i++) { cachedSourceBones[i] = sourceBone.GetBoneTransform((HumanBodyBones)i); } // 타겟 본 캐싱 cachedTargetBones = new Transform[targetBones.Length, 55]; for (int t = 0; t < targetBones.Length; t++) { for (int i = 0; i < 55; i++) { cachedTargetBones[t, i] = targetBones[t].GetBoneTransform((HumanBodyBones)i); } } } private void LateUpdate() { TransferPoses(); } private void TransferPoses() { if (sourceBone == null) { return; } for (int i = 0; i < targetBones.Length; i++) { if (targetBones[i] != null && targetBones[i].gameObject.activeInHierarchy) { TransferPoseToTarget(i); } } } private void TransferPoseToTarget(int targetIndex) { Animator targetBone = targetBones[targetIndex]; // 루트 회전 동기화 targetBone.transform.rotation = sourceBone.transform.rotation; // 모든 본에 대해 포즈 전송 for (int i = 0; i < 55; i++) { Transform targetBoneTransform = cachedTargetBones[targetIndex, i]; Transform sourceBoneTransform = cachedSourceBones[i]; if (targetBoneTransform != null && sourceBoneTransform != null) { // 회전 적용 targetBoneTransform.rotation = sourceBoneTransform.rotation * boneRotationDifferences[targetIndex, i]; // 펠비스 위치 동기화 (HumanBodyBones.Hips = 0) if (i == 0) { targetBoneTransform.position = sourceBoneTransform.position; } // 머리 스케일 적용 (HumanBodyBones.Head = 10) if (transferHeadScale && i == 10) { ApplyHeadScale(targetIndex, targetBoneTransform); } } } } private void ApplyHeadScale(int targetIndex, Transform targetHeadBone) { if (sourceRetargetingScript != null) { float headScale = sourceRetargetingScript.GetHeadScale(); targetHeadBone.localScale = originalTargetHeadScales[targetIndex] * headScale; } else { // CustomRetargetingScript가 없으면 소스 머리 스케일 직접 복사 Transform sourceHead = cachedSourceBones[10]; // HumanBodyBones.Head if (sourceHead != null) { targetHeadBone.localScale = sourceHead.localScale; } } } }