using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using RootMotion.FinalIK; namespace KindRetargeting { [DefaultExecutionOrder(4)] public class LimbWeightController : MonoBehaviour { [Header("거리 기반 가중치 설정")] [SerializeField, Range(0.3f, 1f)] public float maxDistance = 0.5f; // 가중치가 0이 되는 최대 거리 [SerializeField, Range(0.05f, 0.3f)] public float minDistance = 0.1f; // 가중치가 1이 되는 최소 거리 [Header("마스터 가중치 변화 속도")] [SerializeField, Range(0.1f, 20f)] public float weightSmoothSpeed = 10f; // 가중치 변화 속도 [Header("허리 가중치 설정")] [SerializeField, Range(0.1f, 1f)] public float hipsMinDistance = 0.2f; // 최소 거리 (가중치 1) [SerializeField, Range(0.1f, 1f)] public float hipsMaxDistance = 0.6f; // 최대 거리 (가중치 0) [Header("바닥 기준 히프 보정")] [SerializeField, Range(0.3f, 1f)] public float groundHipsMinHeight = 0f; // 최소 높이 (가중치 0) [SerializeField, Range(0.5f, 1.5f)] public float groundHipsMaxHeight = 0.5f; // 최대 높이 (가중치 1) [Header("발 높이 기반 가중치 설정")] [SerializeField, Range(0.1f, 0.5f)] public float footHeightMinThreshold = 0.2f; // 최소 높이 (가중치 1) [SerializeField, Range(0.5f, 1.0f)] public float footHeightMaxThreshold = 0.5f; // 최대 높이 (가중치 0) [Header("IK 활성화 설정")] [SerializeField] public bool enableLeftArmIK = true; // 왼팔 IK 활성화 여부 [SerializeField] public bool enableRightArmIK = true; // 오른팔 IK 활성화 여부 private FullBodyInverseKinematics_RND fbik; private CustomRetargetingScript crs; private Dictionary> weightLayers = new Dictionary>(); List leftArmEndWeights = new List(); List rightArmEndWeights = new List(); List leftLegEndWeights = new List(); List rightLegEndWeights = new List(); List leftLegBendWeights = new List(); List rightLegBendWeights = new List(); private float MasterleftArmEndWeights = 0f; private float MasterrightArmEndWeights = 0f; private float MasterleftLegEndWeights = 0f; private float MasterrightLegEndWeights = 0f; private float MasterleftLegBendWeights = 0f; private float MasterrightLegBendWeights = 0f; public List props = new List(); public Transform characterRoot; // 힙스 가중치 리스트 추가 List hipsWeights = new List(); private float MasterHipsWeight = 1f; void Update() { //손의 거리를 기반으로한 가중치 적용 HandDistances(); //프랍과의 거리에 따른 가중치 적용 PropDistances(); // 마스터 변수에 가중치 적용 FinalizeWeights(); // 앉아있을 때 다리와의 거리에 따른 가중치 적용 SitLegDistances(); // 의자 타입 프랍과의 거리에 따른 가중치 적용 SitChairDistances(); // 바닥 기준 히프 보정 적용 UpdateGroundBasedHipsWeight(); // 발 높이에 따른 가중치 적용 UpdateFootHeightBasedWeight(); // FBIK에 최종 가중치 적용 ApplyWeightsToFBIK(); } void Start() { fbik = GetComponent(); crs = GetComponent(); InitWeightLayers(); //프랍 오브젝트 찾기 props = FindObjectsByType(FindObjectsSortMode.None).Select(controller => controller.transform).ToList(); // 프랍 오브젝트 찾기 GetHand(); //HandDistances();에서 사용을 위한 리스트 추가 //손 거리에 따른 웨이트 업데이트 인덱스 0번 leftArmEndWeights.Add(0); rightArmEndWeights.Add(0); // 프랍과의 거리에 따른 웨이트 업데이트 인덱스 1번 leftArmEndWeights.Add(0); rightArmEndWeights.Add(0); // 앉아있을 때 다리와의 거리에 따른 가중치 적용 인덱스 0번 leftLegEndWeights.Add(0); rightLegEndWeights.Add(0); // 다리 골 가중치 초기화 leftLegBendWeights.Add(1f); // 기본 가중치 rightLegBendWeights.Add(1f); // 기본 가중치 // CharacterController가 있는 루트 오브젝트 찾기 if (characterRoot == null) { characterRoot = transform; } // 힙스 가중치 초기화 인덱스 0번 hipsWeights.Add(1f); // 의자 거리 기반 가중치 // 지면 높이 기반 가중치 초기화 인덱스 1번 hipsWeights.Add(1f); // 지면 높이 기반 가중치 // 발 높이 기반 가중치 초기화 인덱스 1번 leftLegEndWeights.Add(1f); rightLegEndWeights.Add(1f); } private void GetHand() { // 모든 LimbWeightController 찾기 LimbWeightController[] allControllers = FindObjectsOfType(); foreach (LimbWeightController controller in allControllers) { // 자기 자신은 제외 if (controller == this) continue; // CustomRetargetingScript 가져오기 CustomRetargetingScript otherCrs = controller.GetComponent(); if (otherCrs == null) continue; // 왼손과 오른손 Transform 가져오기 Transform leftHand = otherCrs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); Transform rightHand = otherCrs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); // 손이 존재하면 props 리스트에 추가 if (leftHand != null) { props.Add(leftHand); } if (rightHand != null) { props.Add(rightHand); } } } private void InitWeightLayers() { fbik.solver.leftArm.positionWeight = 0f; fbik.solver.leftArm.rotationWeight = 0f; fbik.solver.leftArm.bendGoalWeight = 0f; fbik.solver.rightArm.positionWeight = 0f; fbik.solver.rightArm.rotationWeight = 0f; fbik.solver.rightArm.bendGoalWeight = 0f; } /// /// 타겟과의 거리에 따라 가중치를 업데이트합니다. /// private void HandDistances() { if (fbik == null || crs == null) return; // 왼쪽 팔 가중치 업데이트 if (fbik.solver.leftArm.target != null && fbik.solver.rightArm.target != null) { Transform leftHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); Transform rightHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); if (leftHandTransform != null && rightHandTransform != null && props != null && props.Count > 0) { float Distance = Vector3.Distance(leftHandTransform.position, rightHandTransform.position); float Weight = 1f - Mathf.Clamp01((Distance - minDistance) / (maxDistance - minDistance)); leftArmEndWeights[0] = Weight; rightArmEndWeights[0] = Weight; } else { // props가 없을 때 기본 가중치 설정 leftArmEndWeights[0] = 0f; rightArmEndWeights[0] = 0f; } } } void SitLegDistances() { if (fbik == null || crs == null || characterRoot == null) return; Transform hips = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.Hips); if (hips == null) return; // 캐릭터 루트 기준으로 히프의 로컬 높이 계산 float hipHeight = characterRoot.InverseTransformPoint(hips.position).y; // 기본적으로 다리 가중치를 1로 설정 leftLegEndWeights[0] = 1f; rightLegEndWeights[0] = 1f; // 바닥 기준 히프 보정 변수들을 활용한 앉아있는 판단 // groundHipsMaxHeight 이하일 때 앉아있다고 판단 if (hipHeight <= groundHipsMaxHeight) { // 왼쪽 다리 처리 ProcessSitLegWeight(hips, fbik.solver.leftLeg.target, leftLegEndWeights, 0); // 오른쪽 다리 처리 ProcessSitLegWeight(hips, fbik.solver.rightLeg.target, rightLegEndWeights, 0); } } private void ProcessSitLegWeight(Transform hips, Transform footTarget, List weightList, int weightIndex) { if (footTarget == null) return; // Y축을 제외한 실제 수평 거리 계산 Vector3 hipPos = hips.position; Vector3 footPos = footTarget.position; Vector3 hipPosFlat = new Vector3(hipPos.x, 0, hipPos.z); Vector3 footPosFlat = new Vector3(footPos.x, 0, footPos.z); float horizontalDistance = Vector3.Distance(hipPosFlat, footPosFlat); // 다리 길이를 기준으로 최대/최소 허용 거리 설정 const float MAX_LEG_DISTANCE_RATIO = 0.8f; // 다리 길이의 80% const float MIN_LEG_DISTANCE_RATIO = 0.3f; // 다리 길이의 30% // 거리가 멀수록 가중치 감소 float weight = 1f - Mathf.Clamp01((horizontalDistance - MIN_LEG_DISTANCE_RATIO) / (MAX_LEG_DISTANCE_RATIO - MIN_LEG_DISTANCE_RATIO)); weightList[weightIndex] = weight; } void PropDistances() { if (fbik == null || crs == null) return; // 프랍과의 거리에 따른 웨이트 업데이트 Transform leftHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); Transform rightHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); if (leftHandTransform != null && rightHandTransform != null && props != null) { // 왼손과 프랍 사이의 최소 거리 계산 float minLeftDistance = float.MaxValue; foreach (Transform prop in props) { float distance = Vector3.Distance(leftHandTransform.position, prop.position); minLeftDistance = Mathf.Min(minLeftDistance, distance); } // 오른손과 프랍 사이의 최소 거리 계산 float minRightDistance = float.MaxValue; foreach (Transform prop in props) { float distance = Vector3.Distance(rightHandTransform.position, prop.position); minRightDistance = Mathf.Min(minRightDistance, distance); } // 거리에 따른 가중치 계산 float leftWeight = 1f - Mathf.Clamp01((minLeftDistance - minDistance) / (maxDistance - minDistance)); float rightWeight = 1f - Mathf.Clamp01((minRightDistance - minDistance) / (maxDistance - minDistance)); leftArmEndWeights[1] = leftWeight; rightArmEndWeights[1] = rightWeight; } } void SitChairDistances() { if (crs == null) return; Transform hipsTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips); if (hipsTransform != null && props != null) { float minDistance = float.MaxValue; foreach (Transform prop in props) { PropTypeController ptc = prop.GetComponent(); if (ptc != null && ptc.propType == EnumsList.PropType.Chair) { float distance = Vector3.Distance(hipsTransform.position, prop.childCount > 0 ? prop.GetChild(0).position : prop.position); minDistance = Mathf.Min(minDistance, distance); } } float t = Mathf.Clamp01((minDistance - hipsMinDistance) / (hipsMaxDistance - hipsMinDistance)); hipsWeights[0] = t; // 직접 HipsWeightOffset 수정 대신 배열에 저장 } } /// /// 각 리스트의 최대 가중치 값을 마스터 변수에 부드럽게 설정합니다. /// public void FinalizeWeights() { float deltaTime = Time.deltaTime; // 팔 가중치 계산 (최대값 사용) MasterleftArmEndWeights = Mathf.Lerp( MasterleftArmEndWeights, GetMaxValue(leftArmEndWeights), weightSmoothSpeed * deltaTime ); MasterrightArmEndWeights = Mathf.Lerp( MasterrightArmEndWeights, GetMaxValue(rightArmEndWeights), weightSmoothSpeed * deltaTime ); // 다리 가중치 계산 (최소값 사용) MasterleftLegEndWeights = Mathf.Lerp( MasterleftLegEndWeights, GetMinValue(leftLegEndWeights), weightSmoothSpeed * deltaTime ); MasterrightLegEndWeights = Mathf.Lerp( MasterrightLegEndWeights, GetMinValue(rightLegEndWeights), weightSmoothSpeed * deltaTime ); // 다리 골 가중치 계산 MasterleftLegBendWeights = Mathf.Lerp( MasterleftLegBendWeights, GetMinValue(leftLegBendWeights), weightSmoothSpeed * deltaTime ); MasterrightLegBendWeights = Mathf.Lerp( MasterrightLegBendWeights, GetMinValue(rightLegBendWeights), weightSmoothSpeed * deltaTime ); // 힙스 마스터 가중치 업데이트 MasterHipsWeight = Mathf.Lerp( MasterHipsWeight, GetMinValue(hipsWeights), weightSmoothSpeed * deltaTime ); // CustomRetargetingScript에 최종 가중치 전달 if (crs != null) { crs.HipsWeightOffset = MasterHipsWeight; } } /// /// 리스트에서 최대값을 찾습니다. /// private float GetMaxValue(List list) { if (list.Count == 0) return 0f; float max = list[0]; for (int i = 1; i < list.Count; i++) { if (list[i] > max) { max = list[i]; } } return max; } /// /// 리스트에서 최소값을 찾습니다. /// private float GetMinValue(List list) { if (list.Count == 0) return 0f; float min = list[0]; for (int i = 1; i < list.Count; i++) { if (list[i] < min) { min = list[i]; } } return min; } /// /// 마스터 가중치 값을 FBIK에 적용합니다. /// private void ApplyWeightsToFBIK() { if (fbik == null) return; // 왼쪽 팔 가중치 적용 if (fbik.solver.leftArm != null) { if (fbik.solver.leftArm.target != null) { float finalLeftArmWeight = enableLeftArmIK ? MasterleftArmEndWeights : 0f; fbik.solver.leftArm.positionWeight = finalLeftArmWeight; fbik.solver.leftArm.rotationWeight = finalLeftArmWeight; } if (fbik.solver.leftArm.bendGoal != null) fbik.solver.leftArm.bendGoalWeight = enableLeftArmIK ? MasterleftArmEndWeights : 0f; } // 오른쪽 팔 가중치 적용 if (fbik.solver.rightArm != null) { if (fbik.solver.rightArm.target != null) { float finalRightArmWeight = enableRightArmIK ? MasterrightArmEndWeights : 0f; fbik.solver.rightArm.positionWeight = finalRightArmWeight; fbik.solver.rightArm.rotationWeight = finalRightArmWeight; } if (fbik.solver.rightArm.bendGoal != null) fbik.solver.rightArm.bendGoalWeight = enableRightArmIK ? MasterrightArmEndWeights : 0f; } // 왼쪽 다리 가중치 적용 if (fbik.solver.leftLeg != null) { if (fbik.solver.leftLeg.target != null) { fbik.solver.leftLeg.positionWeight = MasterleftLegEndWeights; fbik.solver.leftLeg.rotationWeight = MasterleftLegEndWeights; } if (fbik.solver.leftLeg.bendGoal != null) fbik.solver.leftLeg.bendGoalWeight = MasterleftLegBendWeights; // 골 가중치 적용 } // 오른쪽 다리 가중치 적용 if (fbik.solver.rightLeg != null) { if (fbik.solver.rightLeg.target != null) { fbik.solver.rightLeg.positionWeight = MasterrightLegEndWeights; fbik.solver.rightLeg.rotationWeight = MasterrightLegEndWeights; } if (fbik.solver.rightLeg.bendGoal != null) fbik.solver.rightLeg.bendGoalWeight = MasterrightLegBendWeights; // 골 가중치 적용 } } /// /// 바닥으로부터의 높이에 따라 히프 보정값을 조정합니다. /// private void UpdateGroundBasedHipsWeight() { if (crs == null || fbik == null || fbik == null) return; Transform hipsTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips); if (hipsTransform != null) { float groundHeight = characterRoot.position.y; float hipsHeight = hipsTransform.position.y - groundHeight; float weight = Mathf.Clamp01((hipsHeight - groundHipsMinHeight) / (groundHipsMaxHeight - groundHipsMinHeight)); hipsWeights[1] = weight; // 직접 HipsWeightOffset 수정 대신 배열에 저장 } } /// /// 발의 높이에 따라 IK 가중치를 조절합니다. /// private void UpdateFootHeightBasedWeight() { if (fbik == null || crs == null || characterRoot == null) return; // 왼발 처리 ProcessFootHeightWeight( fbik.solver.leftLeg.target, leftLegEndWeights, 1 // 새로 추가된 레이어의 인덱스 ); // 오른발 처리 ProcessFootHeightWeight( fbik.solver.rightLeg.target, rightLegEndWeights, 1 // 새로 추가된 레이어의 인덱스 ); } private void ProcessFootHeightWeight(Transform footTarget, List weightList, int weightIndex) { if (footTarget == null) return; // 캐릭터 루트 기준으로 발의 로컬 높이 계산 float footHeight = characterRoot.InverseTransformPoint(footTarget.position).y; // 높이에 따른 가중치 계산 (높이가 높을수록 가중치 감소) float weight = 1f - Mathf.Clamp01((footHeight - footHeightMinThreshold) / (footHeightMaxThreshold - footHeightMinThreshold)); // 계산된 가중치 설정 weightList[weightIndex] = weight; } } }