/* For documentation please refer to this address: http://peyman-mass.blogspot.com/2017/12/using-multiple-bones-to-look-at-target.html */ using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class HeadLookAtData { /// /// 默认旋转 /// private Quaternion m_DefaultRotation; /// /// 骨骼 /// public Transform m_Bone; /// /// 旋转的极限角度 /// public float m_RotationLimit = 90.0f; /// /// 旋转向上向量权重 /// public float m_RotateAroundUpVectorWeight = 0.0f; /// /// 是否重置为默认旋转 /// //public bool m_ResetToDefaultRotation = false; public void SetDefaultRotation(Quaternion rot) { m_DefaultRotation = rot; } /// /// 检测极限旋转数值有效性 /// public void CheckJointRotation() { if (m_RotationLimit < Mathf.Epsilon) { Debug.LogError("极限旋转为零或负。 没有生效"); } } } /// /// 头看向目标 /// public class HeadLookAtTarget : MonoBehaviour { /// /// 游戏对象缩放 /// //private Vector3 m_gameObjectScale; /// /// 目标对象 /// public GameObject m_TargetObject; public Vector3 m_UpVector = Vector3.up; /// /// 权重 /// public float m_Weight = 1.0f; /// /// 看向的混合速度 /// public float m_LookAtBlendSpeed = 5.0f; public bool m_DrawDebugLookAtLines = false; /// /// 骨骼数据列表 /// public HeadLookAtData[] m_LookAtBones; /// /// 骨骼旋转列表 /// private Quaternion[] m_BlendedRotations; /// /// 上一帧骨骼旋转 /// private Quaternion[] m_LastFrameRotations; /*************************************************************************/ public static Quaternion GetWorldLookAtRotation(Vector3 targetVector, Vector3 fwdVectorInWorldSpace) { Quaternion worldLookAtRotation; Vector3 rotationAxis; float angle = Vector3.Angle(fwdVectorInWorldSpace, targetVector) * Mathf.Deg2Rad; if (Mathf.Abs(angle - Mathf.PI) < Mathf.Epsilon || angle <= Mathf.Epsilon) { rotationAxis = Vector3.up; } else { rotationAxis = Vector3.Cross(fwdVectorInWorldSpace, targetVector); } worldLookAtRotation = QuaternionFromAngleAxis(ref rotationAxis, angle); return worldLookAtRotation; } /*************************************************************************/ private Quaternion PerfectLookAtSlerp(Quaternion a, Quaternion b, float weight) { if (Mathf.Abs(weight - 1.0f) < Mathf.Epsilon) { return b; } if (weight < Mathf.Epsilon) { return a; } return Quaternion.Slerp(a, b, weight); } /*************************************************************************/ public static Quaternion QuaternionFromAngleAxis(ref Vector3 rotationAxis, float angleRad) { float halfAngleRad = angleRad * 0.5f; rotationAxis = rotationAxis.normalized * Mathf.Sin(halfAngleRad); float quatW = Mathf.Cos(halfAngleRad); return new Quaternion(rotationAxis.x, rotationAxis.y, rotationAxis.z, quatW); } /*************************************************************************/ private float GetAngleFromQuaternionRad(Quaternion inputQuat) { float ret = Mathf.Acos(inputQuat.w) * 2.0f; return ret; } /*************************************************************************/ private float GetAngleFromQuaternionRad(ref Quaternion inputQuat) { float ret = Mathf.Acos(inputQuat.w) * 2.0f; return ret; } ///*************************************************************************/ //private Vector3 GetForwardVector( ref Transform inputTr, FwdDirection inputAxis ) //{ // switch ( inputAxis ) // { // case FwdDirection.X_AXIS: // return inputTr.right; // case FwdDirection.Y_AXIS: // return inputTr.up; // case FwdDirection.Z_AXIS: // return inputTr.forward; // case FwdDirection.MINUS_X_AXIS: // return -inputTr.right; // case FwdDirection.MINUS_Y_AXIS: // return -inputTr.up; // case FwdDirection.MINUS_Z_AXIS: // return -inputTr.forward; // default: // return inputTr.forward; // } //} /*************************************************************************/ void Start() { //新建骨骼旋转列表 m_BlendedRotations = new Quaternion[m_LookAtBones.Length]; m_LastFrameRotations = new Quaternion[m_LookAtBones.Length]; for (int i = 0; i < m_LookAtBones.Length; i++) { HeadLookAtData lookAtBoneData = m_LookAtBones[i]; if (lookAtBoneData.m_Bone != null) { lookAtBoneData.SetDefaultRotation(lookAtBoneData.m_Bone.localRotation);//设置默认旋转 m_LastFrameRotations[i] = lookAtBoneData.m_Bone.localRotation;//设置上一帧旋转 lookAtBoneData.CheckJointRotation();//检测极限旋转数据有效性 } } } /*************************************************************************/ void Update() { if (m_TargetObject == null) { return; } if (m_LookAtBones.Length > 0)//骨骼列表存在 { if (m_DrawDebugLookAtLines)//如果画线 { Vector3 destination = m_TargetObject.transform.position - m_LookAtBones[0].m_Bone.position;//目标与跟骨骼的向量 Debug.DrawLine(m_LookAtBones[0].m_Bone.position, m_LookAtBones[0].m_Bone.position + destination);//画线 } } } /*************************************************************************/ void LateUpdate() { if (m_TargetObject == null) { return; } if (m_Weight < Mathf.Epsilon) { //即使权重为零,也更新最后一帧旋转 for (int i = 0; i < m_LookAtBones.Length; i++) { if (m_LookAtBones[i].m_Bone == null) { continue; } HeadLookAtData lookAtBoneData = m_LookAtBones[i]; m_LastFrameRotations[i] = lookAtBoneData.m_Bone.localRotation; } return; } //更新骨骼旋转 if (m_LookAtBones.Length > 0) { if (m_LookAtBones[0].m_Bone == null) { return; } Vector3 rotatedInitFwdVec = m_LookAtBones[0].m_Bone.up;//初始向量 Vector3 currentFwdVec; Vector3 parentFwdVec; Vector3 targetVector = m_TargetObject.transform.position - m_LookAtBones[0].m_Bone.position; //current vector is being updated in UpdateCurrentTargetVector Vector3 firstBoneRotatedInitFwdVec = rotatedInitFwdVec; byte numBonesToRotate = 1; for (int i = 0; i < m_LookAtBones.Length; i++) { if (m_LookAtBones[i].m_Bone == null) { continue; } Transform currentBone = m_LookAtBones[i].m_Bone; Transform parentBone = currentBone.parent; ; float rotationLimit = m_LookAtBones[i].m_RotationLimit; bool parentExists = true; currentFwdVec = currentBone.up; parentFwdVec = parentBone.up; if (parentBone != null) { currentFwdVec = currentBone.up; ; parentFwdVec = parentBone.up; float diffAngleFromAnim = Vector3.Angle(currentFwdVec, parentFwdVec); //in case animation already has a bone with relative rotation higher than the limit specified by user if (diffAngleFromAnim > rotationLimit) { rotationLimit = diffAngleFromAnim; } } else { parentExists = false; } Quaternion lookAtRot = GetWorldLookAtRotation(targetVector, rotatedInitFwdVec); if (m_LookAtBones[i].m_RotateAroundUpVectorWeight > 0.0f) { Vector3 currentRotationAxis; currentRotationAxis.x = lookAtRot.x; currentRotationAxis.y = lookAtRot.y; currentRotationAxis.z = lookAtRot.z; //checking the up vector direction for rotation float rotationSign = Mathf.Sign(Vector3.Cross(firstBoneRotatedInitFwdVec, targetVector).y); Vector3 finalUpVector = rotationSign * m_UpVector; finalUpVector = Vector3.Lerp(currentRotationAxis, finalUpVector, m_LookAtBones[i].m_RotateAroundUpVectorWeight); lookAtRot = QuaternionFromAngleAxis(ref finalUpVector, GetAngleFromQuaternionRad(lookAtRot)); } Quaternion childRotation = lookAtRot * currentBone.rotation; if (parentExists) { //checking the angle difference float diffAngle = Mathf.Abs(Vector3.Angle(parentFwdVec, lookAtRot * currentFwdVec)) - rotationLimit; if (diffAngle > 0) { { Vector3 rotationAxis = new Vector3(lookAtRot.x, lookAtRot.y, lookAtRot.z); float limitAngleRad = GetAngleFromQuaternionRad(ref lookAtRot) + Mathf.Deg2Rad * (-diffAngle); lookAtRot = QuaternionFromAngleAxis(ref rotationAxis, limitAngleRad); childRotation = lookAtRot * currentBone.rotation; Quaternion currentBoneRotationLS = currentBone.localRotation; m_LookAtBones[i].m_Bone.rotation = childRotation; m_BlendedRotations[i] = PerfectLookAtSlerp(currentBoneRotationLS, m_LookAtBones[i].m_Bone.localRotation, m_Weight); } // if (i != m_LookAtBones.Length - 1) { Vector3 currentBoneToParentPosDiff = m_LookAtBones[0].m_Bone.position - m_LookAtBones[i + 1].m_Bone.position; Vector3 firstBoneToTargetDiff = m_TargetObject.transform.position - m_LookAtBones[0].m_Bone.position; rotatedInitFwdVec = m_LookAtBones[0].m_Bone.up; rotatedInitFwdVec.Normalize(); rotatedInitFwdVec = firstBoneToTargetDiff.magnitude * rotatedInitFwdVec; rotatedInitFwdVec = currentBoneToParentPosDiff + rotatedInitFwdVec; targetVector = currentBoneToParentPosDiff + firstBoneToTargetDiff; numBonesToRotate++; if (m_DrawDebugLookAtLines) { Debug.DrawLine(m_LookAtBones[i + 1].m_Bone.position, m_LookAtBones[i + 1].m_Bone.position + rotatedInitFwdVec, Color.green); Debug.DrawLine(m_LookAtBones[i + 1].m_Bone.position, m_LookAtBones[i + 1].m_Bone.position + targetVector, Color.red); } } } else { Quaternion currentBoneRotationLS = currentBone.localRotation; m_LookAtBones[i].m_Bone.rotation = childRotation; m_BlendedRotations[i] = PerfectLookAtSlerp(currentBoneRotationLS, m_LookAtBones[i].m_Bone.localRotation, m_Weight); break; } } else { Quaternion currentBoneRotationLS = currentBone.localRotation; m_LookAtBones[i].m_Bone.rotation = childRotation; m_BlendedRotations[i] = PerfectLookAtSlerp(currentBoneRotationLS, m_LookAtBones[i].m_Bone.localRotation, m_Weight); if (i < m_LookAtBones.Length - 1) { Debug.LogWarning("Warning Bone name doesn't have a parent. The rest of the PerfectLookAt bone chain won't work after this bone!", this); break; } } } //Updating last frame rotations. Two loops are separated to have less jumps in memory and be more cache friendly. bool isBlending = Mathf.Abs(m_Weight - 1.0f) > Mathf.Epsilon; // Calculating the blend weight to blend between the current frame and last frame to achieve smooth rotations in dynamic animations with large range of movements. float smoothingWeight = Mathf.Clamp(m_LookAtBlendSpeed * Time.deltaTime, 0.0f, 1.0f); smoothingWeight = (smoothingWeight - 1.0f) * m_Weight + 1.0f; for (int k = 0; k < m_LookAtBones.Length; k++) { HeadLookAtData lookAtBoneData = m_LookAtBones[k]; Quaternion boneLocalRotation; if (isBlending && k < numBonesToRotate) { boneLocalRotation = m_BlendedRotations[k]; } else { boneLocalRotation = lookAtBoneData.m_Bone.localRotation; } Quaternion localRotationFromAnim = lookAtBoneData.m_Bone.localRotation; lookAtBoneData.m_Bone.localRotation = PerfectLookAtSlerp(m_LastFrameRotations[k], boneLocalRotation, smoothingWeight); m_LastFrameRotations[k] = lookAtBoneData.m_Bone.localRotation; } } } }