Unity 如何实现游戏Avatar角色头部跟随视角转动


功能简介

如图所示,当相机的视角转动时,Avatar角色的头部会同步转动,看向视角的方向。

功能示例

实现步骤

获取看向的位置

Avatar看向的位置即相机前方一定距离的某个坐标,该距离偏大于相机与Avatar角色的距离即可,可以取100来代表:

//获取看向的位置
private Vector3 GetLookAtPosition()
{
    
    
    //主相机前方100个单位的位置
    return mainCamera.transform.position + mainCamera.transform.forward * 100f;
}

获取头部的位置

头部位置可以通过Animator组件中的GetBoneTransform接口来获取

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<Camera>();
            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<Camera>();
            head = animator.GetBoneTransform(HumanBodyBones.Head);
            headHeight = Vector3.Distance(transform.position, head.position);
        }

        /// <summary>
        /// 看向某点
        /// </summary>
        /// <param name="position"></param>
        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;
}

封装看向某点的函数:

/// <summary>
/// 看向某点
/// </summary>
/// <param name="position"></param>
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<Camera>();
            head = animator.GetBoneTransform(HumanBodyBones.Head);
            headHeight = Vector3.Distance(transform.position, head.position);
        }

        /// <summary>
        /// 看向某点
        /// </summary>
        /// <param name="position"></param>
        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标签:

Tag
在代码中加入判断:

//获取看向的位置
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<Camera>();
            head = animator.GetBoneTransform(HumanBodyBones.Head);
            headHeight = Vector3.Distance(transform.position, head.position);
        }

        /// <summary>
        /// 看向某点
        /// </summary>
        /// <param name="position"></param>
        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); 
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_42139931/article/details/128957137