using UnityEngine; namespace KindRetargeting { [System.Serializable] public class FingerShapedController { private Animator animator; private HumanPoseHandler humanPoseHandler; // 매 프레임 재사용되는 캐시 (GC 압박 제거) private Transform[] cachedNonFingerBones; private Quaternion[] savedBoneRotations; private HumanPose cachedHumanPose; // 손가락을 제외한 모든 휴먼본 목록 private static readonly HumanBodyBones[] nonFingerBones = new HumanBodyBones[] { HumanBodyBones.Hips, HumanBodyBones.Spine, HumanBodyBones.Chest, HumanBodyBones.UpperChest, HumanBodyBones.Neck, HumanBodyBones.Head, HumanBodyBones.LeftShoulder, HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand, HumanBodyBones.RightShoulder, HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm, HumanBodyBones.RightHand, HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg, HumanBodyBones.LeftFoot, HumanBodyBones.LeftToes, HumanBodyBones.RightUpperLeg, HumanBodyBones.RightLowerLeg, HumanBodyBones.RightFoot, HumanBodyBones.RightToes, HumanBodyBones.LeftEye, HumanBodyBones.RightEye, HumanBodyBones.Jaw }; public bool enabled = false; [Header("왼손 제어 값")] [Range(-1, 1)] public float leftPinkyCurl; // 새끼손가락 구부리기 [Range(-1, 1)] public float leftRingCurl; // 약지 구부리기 [Range(-1, 1)] public float leftMiddleCurl; // 중지 구부리기 [Range(-1, 1)] public float leftIndexCurl; // 검지 구부리기 [Range(-1, 1)] public float leftThumbCurl; // 엄지 구부리기 [Range(-1, 1)] public float leftSpreadFingers; // 손가락 벌리기 [Header("오른손 제어 값")] [Range(-1, 1)] public float rightPinkyCurl; // 새끼손가락 구부리기 [Range(-1, 1)] public float rightRingCurl; // 약지 구부리기 [Range(-1, 1)] public float rightMiddleCurl; // 중지 구부리기 [Range(-1, 1)] public float rightIndexCurl; // 검지 구부리기 [Range(-1, 1)] public float rightThumbCurl; // 엄지 구부리기 [Range(-1, 1)] public float rightSpreadFingers; // 손가락 벌리기 public bool leftHandEnabled = false; // 왼손 제어 활성화 상태 public bool rightHandEnabled = false; // 오른손 제어 활성화 상태 private bool isInitialized; public void Initialize(Animator targetAnimator) { animator = targetAnimator; if (animator == null || !animator.isHuman) return; humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform); // Transform 배열 + 회전 배열 사전 캐싱 (매 프레임 GetBoneTransform 50회 → 0회) cachedNonFingerBones = new Transform[nonFingerBones.Length]; savedBoneRotations = new Quaternion[nonFingerBones.Length]; for (int i = 0; i < nonFingerBones.Length; i++) { cachedNonFingerBones[i] = animator.GetBoneTransform(nonFingerBones[i]); } // HumanPose 사전 할당 (muscles 배열 95개 float, GC 회피) humanPoseHandler.GetHumanPose(ref cachedHumanPose); isInitialized = true; } public void Cleanup() { if (humanPoseHandler != null) { humanPoseHandler.Dispose(); humanPoseHandler = null; } isInitialized = false; } public void OnUpdate() { if (!isInitialized || !enabled) return; UpdateMuscleValues(); } private void UpdateMuscleValues() { // 1. 비손가락 본의 로컬 회전을 배열에 저장 (캐시된 Transform 재사용) for (int i = 0; i < cachedNonFingerBones.Length; i++) { Transform bone = cachedNonFingerBones[i]; if (bone != null) savedBoneRotations[i] = bone.localRotation; } // 2. HumanPose 가져오기 (재사용 필드, GC 없음) + 손가락 머슬 설정 humanPoseHandler.GetHumanPose(ref cachedHumanPose); SetHandMuscles(true, leftThumbCurl, leftIndexCurl, leftMiddleCurl, leftRingCurl, leftPinkyCurl, leftSpreadFingers, ref cachedHumanPose); SetHandMuscles(false, rightThumbCurl, rightIndexCurl, rightMiddleCurl, rightRingCurl, rightPinkyCurl, rightSpreadFingers, ref cachedHumanPose); // 3. 머슬 포즈 적용 (손가락 포함 전체 본에 영향) humanPoseHandler.SetHumanPose(ref cachedHumanPose); // 4. 비손가락 본 로컬 회전 복원 (본 길이 변형 방지) for (int i = 0; i < cachedNonFingerBones.Length; i++) { Transform bone = cachedNonFingerBones[i]; if (bone != null) bone.localRotation = savedBoneRotations[i]; } } private void SetHandMuscles(bool isLeft, float thumb, float index, float middle, float ring, float pinky, float spread, ref HumanPose humanPose) { // 해당 손이 비활성화 상태면 건너뛰기 if (isLeft && !leftHandEnabled) return; if (!isLeft && !rightHandEnabled) return; int baseOffset = isLeft ? 55 : 75; // 왼손은 55부터, 오른손은 75부터 시작 int muscleCount = humanPose.muscles.Length; // 엄지손가락 if (baseOffset < muscleCount) humanPose.muscles[baseOffset] = thumb; // Thumb 1 if (baseOffset + 1 < muscleCount) humanPose.muscles[baseOffset + 1] = thumb; // Thumb Spread if (baseOffset + 2 < muscleCount) humanPose.muscles[baseOffset + 2] = thumb; // Thumb 2 if (baseOffset + 3 < muscleCount) humanPose.muscles[baseOffset + 3] = thumb; // Thumb 3 // 검지 if (baseOffset + 4 < muscleCount) humanPose.muscles[baseOffset + 4] = index; // Index 1 if (baseOffset + 5 < muscleCount) humanPose.muscles[baseOffset + 5] = spread; // Index Spread if (baseOffset + 6 < muscleCount) humanPose.muscles[baseOffset + 6] = index; // Index 2 if (baseOffset + 7 < muscleCount) humanPose.muscles[baseOffset + 7] = index; // Index 3 // 중지 if (baseOffset + 8 < muscleCount) humanPose.muscles[baseOffset + 8] = middle; // Middle 1 if (baseOffset + 9 < muscleCount) humanPose.muscles[baseOffset + 9] = spread; // Middle Spread if (baseOffset + 10 < muscleCount) humanPose.muscles[baseOffset + 10] = middle; // Middle 2 if (baseOffset + 11 < muscleCount) humanPose.muscles[baseOffset + 11] = middle; // Middle 3 // 약지 if (baseOffset + 12 < muscleCount) humanPose.muscles[baseOffset + 12] = ring; // Ring 1 if (baseOffset + 13 < muscleCount) humanPose.muscles[baseOffset + 13] = spread; // Ring Spread if (baseOffset + 14 < muscleCount) humanPose.muscles[baseOffset + 14] = ring; // Ring 2 if (baseOffset + 15 < muscleCount) humanPose.muscles[baseOffset + 15] = ring; // Ring 3 // 새끼손가락 if (baseOffset + 16 < muscleCount) humanPose.muscles[baseOffset + 16] = pinky; // Little 1 if (baseOffset + 17 < muscleCount) humanPose.muscles[baseOffset + 17] = spread; // Little Spread if (baseOffset + 18 < muscleCount) humanPose.muscles[baseOffset + 18] = pinky; // Little 2 if (baseOffset + 19 < muscleCount) humanPose.muscles[baseOffset + 19] = pinky; // Little 3 } } }