如图所示,当相机的视角转动时,Avatar角色的头部会同步转动,看向视角的方向。
Avatar看向的位置即相机前方一定距离的某个坐标,该距离偏大于相机与Avatar角色的距离即可,可以取100来代表:
//获取看向的位置
private Vector3 GetLookAtPosition()
{//主相机前方100个单位的位置return mainCamera.transform.position + mainCamera.transform.forward * 100f;
}
头部位置可以通过Animator
组件中的GetBoneTransform
接口来获取
示例如下:
using UnityEngine;namespace SK.Framework.Avatar
{public class HeadTrack : MonoBehaviour{//动画组件[SerializeField] private Animator animator; private Camera mainCamera; //主相机private Transform head; //头部private void Start(){mainCamera = Camera.main ?? FindObjectOfType();head = animator.GetBoneTransform(HumanBodyBones.Head);}//获取看向的位置private Vector3 GetLookAtPosition(){//主相机前方100个单位的位置return mainCamera.transform.position + mainCamera.transform.forward * 100f;}}
}
有了头部的位置后,就可以计算头部的高度,声明一个变量headHeight
来记录头部高度:
headHeight = Vector3.Distance(transform.position, head.position);
有了看向的坐标和头部的坐标,就取得了看向的朝向,在LateUpdate
中赋值该头部朝向,注意一定要使用LateUpdate
,因为Animator动画组件在控制Avatar各骨骼的朝向,使用LateUpdate
可以确保我们的旋转值修改起作用。
using UnityEngine;namespace SK.Framework.Avatar
{public class HeadTrack : MonoBehaviour{//动画组件[SerializeField] private Animator animator; private Camera mainCamera; //主相机private Transform head; //头部private float headHeight; //头部的高度private void Start(){mainCamera = Camera.main ?? FindObjectOfType();head = animator.GetBoneTransform(HumanBodyBones.Head);headHeight = Vector3.Distance(transform.position, head.position);}/// /// 看向某点/// /// public void LookAtPosition(Vector3 position){//头部位置Vector3 headPosition = transform.position + transform.up * headHeight;//朝向Quaternion lookRotation = Quaternion.LookRotation(position - headPosition);head.rotation = lookRotation;}private void LateUpdate(){Debug.DrawLine(transform.position + transform.up * headHeight, GetLookAtPosition());LookAtPosition(GetLookAtPosition());}//获取看向的位置private Vector3 GetLookAtPosition(){//主相机前方100个单位的位置return mainCamera.transform.position + mainCamera.transform.forward * 100f;}}
}
如图所示,我们已经实现了头部的转向,但是旋转值过大会导致反人类现象,因此需要将旋转值进行限制。
//水平方向上的角度限制
[SerializeField] private Vector2 horizontalAngleLimit = new Vector2(-100f, 100f); //垂直方向上的角度限制
[SerializeField] private Vector2 verticalAngleLimit = new Vector2(-60f, 60f);
封装一个角度标准化的函数,当角度大于180度时减360度,当角度小于180度时加360度:
//角度标准化
private float NormalizeAngle(float angle)
{if (angle > 180) angle -= 360f;else if (angle < -180) angle += 360f;return angle;
}
封装看向某点的函数:
///
/// 看向某点
///
///
public void LookAtPosition(Vector3 position)
{//头部位置Vector3 headPosition = transform.position + transform.up * headHeight;//朝向Quaternion lookRotation = Quaternion.LookRotation(position - headPosition);Vector3 eulerAngles = lookRotation.eulerAngles - transform.rotation.eulerAngles;float x = NormalizeAngle(eulerAngles.x);float y = NormalizeAngle(eulerAngles.y);x = Mathf.Clamp(x, verticalAngleLimit.x, verticalAngleLimit.y);y = Mathf.Clamp(y, horizontalAngleLimit.x, horizontalAngleLimit.y);Quaternion rotY = Quaternion.AngleAxis(y, head.InverseTransformDirection(transform.up));head.rotation *= rotY;Quaternion rotX = Quaternion.AngleAxis(x, head.InverseTransformDirection(transform.TransformDirection(Vector3.right)));head.rotation *= rotX;
}
当角度超出限制的范围时,将头部自动回正,可以在GetLookAtPosition
函数中加入判断,声明autoTurnback
变量标识是否自动回正:
//获取看向的位置
private Vector3 GetLookAtPosition()
{Vector3 position = mainCamera.transform.position + mainCamera.transform.forward * 100f;if (!autoTurnback) return position;Vector3 direction = position - (transform.position + transform.up * headHeight);Quaternion lookRotation = Quaternion.LookRotation(direction, transform.up);Vector3 angle = lookRotation.eulerAngles - transform.eulerAngles;float x = NormalizeAngle(angle.x);float y = NormalizeAngle(angle.y);bool isInRange = x >= verticalAngleLimit.x && x <= verticalAngleLimit.y&& y >= horizontalAngleLimit.x && y <= horizontalAngleLimit.y;return isInRange ? position : (transform.position + transform.up * headHeight + transform.forward);
}
加入插值运算,使自动回正时有过渡过程,代码如下:
using UnityEngine;namespace SK.Framework.Avatar
{public class HeadTrack : MonoBehaviour{[Tooltip("动画组件"), SerializeField] private Animator animator; [Tooltip("水平方向上的角度限制"), SerializeField] private Vector2 horizontalAngleLimit = new Vector2(-70f, 70f); [Tooltip("垂直方向上的角度限制"), SerializeField] private Vector2 verticalAngleLimit = new Vector2(-60f, 60f);[Tooltip("超出限制范围时自动回正"), SerializeField] private bool autoTurnback = true;[Tooltip("插值速度"), SerializeField] private float lerpSpeed = 5f;private Camera mainCamera; //主相机private Transform head; //头部private float headHeight; //头部的高度private float angleX;private float angleY;private void Start(){mainCamera = Camera.main ?? FindObjectOfType();head = animator.GetBoneTransform(HumanBodyBones.Head);headHeight = Vector3.Distance(transform.position, head.position);}/// /// 看向某点/// /// public void LookAtPosition(Vector3 position){//头部位置Vector3 headPosition = transform.position + transform.up * headHeight;//朝向Quaternion lookRotation = Quaternion.LookRotation(position - headPosition);Vector3 eulerAngles = lookRotation.eulerAngles - transform.rotation.eulerAngles;float x = NormalizeAngle(eulerAngles.x);float y = NormalizeAngle(eulerAngles.y);angleX = Mathf.Clamp(Mathf.Lerp(angleX, x, Time.deltaTime * lerpSpeed), verticalAngleLimit.x, verticalAngleLimit.y);angleY = Mathf.Clamp(Mathf.Lerp(angleY, y, Time.deltaTime * lerpSpeed), horizontalAngleLimit.x, horizontalAngleLimit.y);Quaternion rotY = Quaternion.AngleAxis(angleY, head.InverseTransformDirection(transform.up));head.rotation *= rotY;Quaternion rotX = Quaternion.AngleAxis(angleX, head.InverseTransformDirection(transform.TransformDirection(Vector3.right)));head.rotation *= rotX;}//角度标准化private float NormalizeAngle(float angle){if (angle > 180) angle -= 360f;else if (angle < -180) angle += 360f;return angle;}private void LateUpdate(){LookAtPosition(GetLookAtPosition());}//获取看向的位置private Vector3 GetLookAtPosition(){Vector3 position = mainCamera.transform.position + mainCamera.transform.forward * 100f;if (!autoTurnback) return position;Vector3 direction = position - (transform.position + transform.up * headHeight);Quaternion lookRotation = Quaternion.LookRotation(direction, transform.up);Vector3 angle = lookRotation.eulerAngles - transform.eulerAngles;float x = NormalizeAngle(angle.x);float y = NormalizeAngle(angle.y);bool isInRange = x >= verticalAngleLimit.x && x <= verticalAngleLimit.y && y >= horizontalAngleLimit.x && y <= horizontalAngleLimit.y;return isInRange ? position : (transform.position + transform.up * headHeight + transform.forward); }}
}
如果我们想要在播放某个动画时不让头部转动,可以通过Tag标签来解决,如下图所示,为Hi动画增加IgnoreHeadTrack
标签:
在代码中加入判断:
//获取看向的位置
private Vector3 GetLookAtPosition()
{AnimatorStateInfo animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);if (animatorStateInfo.IsTag("IgnoreHeadTrack"))return transform.position + transform.up * headHeight + transform.forward;Vector3 position = mainCamera.transform.position + mainCamera.transform.forward * 100f;if (!autoTurnback) return position;Vector3 direction = position - (transform.position + transform.up * headHeight);Quaternion lookRotation = Quaternion.LookRotation(direction, transform.up);Vector3 angle = lookRotation.eulerAngles - transform.eulerAngles;float x = NormalizeAngle(angle.x);float y = NormalizeAngle(angle.y);bool isInRange = x >= verticalAngleLimit.x && x <= verticalAngleLimit.y&& y >= horizontalAngleLimit.x && y <= horizontalAngleLimit.y;return isInRange ? position : (transform.position + transform.up * headHeight + transform.forward);
}
完整代码:
using UnityEngine;namespace SK.Framework.Avatar
{public class HeadTrack : MonoBehaviour{[Tooltip("动画组件"), SerializeField] private Animator animator; [Tooltip("水平方向上的角度限制"), SerializeField] private Vector2 horizontalAngleLimit = new Vector2(-70f, 70f); [Tooltip("垂直方向上的角度限制"), SerializeField] private Vector2 verticalAngleLimit = new Vector2(-60f, 60f);[Tooltip("超出限制范围时自动回正"), SerializeField] private bool autoTurnback = true;[Tooltip("插值速度"), SerializeField] private float lerpSpeed = 5f;private Camera mainCamera; //主相机private Transform head; //头部private float headHeight; //头部的高度private float angleX;private float angleY;private void Start(){mainCamera = Camera.main ?? FindObjectOfType();head = animator.GetBoneTransform(HumanBodyBones.Head);headHeight = Vector3.Distance(transform.position, head.position);}/// /// 看向某点/// /// public void LookAtPosition(Vector3 position){//头部位置Vector3 headPosition = transform.position + transform.up * headHeight;//朝向Quaternion lookRotation = Quaternion.LookRotation(position - headPosition);Vector3 eulerAngles = lookRotation.eulerAngles - transform.rotation.eulerAngles;float x = NormalizeAngle(eulerAngles.x);float y = NormalizeAngle(eulerAngles.y);angleX = Mathf.Clamp(Mathf.Lerp(angleX, x, Time.deltaTime * lerpSpeed), verticalAngleLimit.x, verticalAngleLimit.y);angleY = Mathf.Clamp(Mathf.Lerp(angleY, y, Time.deltaTime * lerpSpeed), horizontalAngleLimit.x, horizontalAngleLimit.y);Quaternion rotY = Quaternion.AngleAxis(angleY, head.InverseTransformDirection(transform.up));head.rotation *= rotY;Quaternion rotX = Quaternion.AngleAxis(angleX, head.InverseTransformDirection(transform.TransformDirection(Vector3.right)));head.rotation *= rotX;}//角度标准化private float NormalizeAngle(float angle){if (angle > 180) angle -= 360f;else if (angle < -180) angle += 360f;return angle;}private void LateUpdate(){LookAtPosition(GetLookAtPosition());}//获取看向的位置private Vector3 GetLookAtPosition(){AnimatorStateInfo animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);if (animatorStateInfo.IsTag("IgnoreHeadTrack"))return transform.position + transform.up * headHeight + transform.forward;Vector3 position = mainCamera.transform.position + mainCamera.transform.forward * 100f;if (!autoTurnback) return position;Vector3 direction = position - (transform.position + transform.up * headHeight);Quaternion lookRotation = Quaternion.LookRotation(direction, transform.up);Vector3 angle = lookRotation.eulerAngles - transform.eulerAngles;float x = NormalizeAngle(angle.x);float y = NormalizeAngle(angle.y);bool isInRange = x >= verticalAngleLimit.x && x <= verticalAngleLimit.y && y >= horizontalAngleLimit.x && y <= horizontalAngleLimit.y;return isInRange ? position : (transform.position + transform.up * headHeight + transform.forward); }}
}