Unity 如何实现游戏Avatar角色头部跟随视角转动
创始人
2024-05-24 01:18:43
0

文章目录

  • 功能简介
  • 实现步骤
    • 获取看向的位置
    • 获取头部的位置
    • 修改头部的朝向
    • 限制旋转角度
    • 超出限制范围时自动回正
  • 如何让指定动画不受影响


功能简介

如图所示,当相机的视角转动时,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();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标签:

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();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); }}
}

相关内容

热门资讯

创维电视安卓系统2.3,回顾经... 你有没有发现家里的创维电视有点儿“老态龙钟”了?别急,别急,今天就来给你揭秘一下这款电视的“内心世界...
如何破解车载安卓系统,轻松解锁... 如何破解车载安卓系统:揭秘背后的技术与风险在当今这个数字化飞速发展的时代,汽车已经不仅仅是一种交通工...
安卓机上的windows系统,... 你有没有想过,把Windows系统的强大功能搬到安卓机上?想象那可是个让人眼前一亮的操作体验呢!今天...
非安卓10系统手机,探索非安卓... 你有没有想过,为什么有些人会选择非安卓10系统的手机呢?是不是觉得这有点奇怪?别急,今天就来带你一探...
手机图标制作安卓系统,手机图标... 你有没有想过,那些在手机屏幕上跳动的图标,其实都是精心设计出来的艺术品呢?没错,今天就要带你一探究竟...
安卓系统和鸿蒙系统谁大,谁才是... 你有没有想过,手机里的操作系统就像是一场无声的较量?今天,咱们就来聊聊这个话题:安卓系统和鸿蒙系统,...
bj40安卓系统,功能解析与使... 你有没有发现,最近你的BJ40越野车变得聪明多了?没错,它升级了安卓系统,简直就像给它装上了个智能大...
安卓系统硬件核心板,揭秘智能设... 你有没有想过,手机里的安卓系统其实就像是一个庞大的城市,而硬件核心板就是这座城市的核心区域?今天,就...
王者荣耀安卓系统转区ios系统... 你有没有想过,把你的王者荣耀账号从安卓系统转到iOS系统呢?这可不是一件小事,里面可是有大学问的哦!...
安卓系统的音游,节奏与音乐的完... 你有没有发现,手机里的游戏越来越好玩了?尤其是那些音游,简直让人停不下来!今天,就让我带你深入了解一...
安卓系统取消下方按键,探索全新... 你知道吗?最近安卓系统来了一次大变动,那就是取消了下方按键!这可真是让人眼前一亮,让我们一起来看看这...
安卓系统显示原理设置,从像素到... 你有没有发现,你的安卓手机屏幕上那些花花绿绿的图标和文字,其实背后有着一套神奇而又复杂的显示原理呢?...
平板安卓4.0系统下载,平板下... 你有没有想过,拥有一台运行着最新安卓4.0系统的平板电脑,那感觉简直就像拥有了未来的钥匙?想象流畅的...
安卓原生12系统下载,原生系统... 你有没有听说?安卓原生12系统终于来了!这款全新的操作系统,不仅带来了全新的视觉体验,还有一堆实用的...
安卓怎么下泼辣系统,安卓设备轻... 你有没有想过给你的安卓手机来个“大变身”?想象你的手机瞬间变成了一个泼辣的“女侠”,不仅个性十足,而...
安卓版小米系统下载,畅享智能生... 你有没有发现,最近手机圈里又掀起了一股热潮?没错,就是安卓版小米系统的下载。这款系统自从推出以来,就...
提取安卓系统自带软件,探索安卓... 你有没有想过,你的安卓手机里那些看似不起眼的自带软件,其实都是隐藏的宝藏呢?今天,就让我带你一探究竟...
安卓系统投屏到鸿蒙系统,鸿蒙系... 亲爱的读者们,你是否有过这样的体验:手里拿着安卓手机,却想在大屏幕的鸿蒙系统设备上展示内容?别急,今...
sony 电视安卓8.0系统,... 亲爱的读者们,你是否也和我一样,对科技产品有着浓厚的兴趣呢?今天,我要和你聊聊一个让我眼前一亮的话题...
安卓 替换系统下载,探索安卓系... 你有没有想过,你的安卓手机其实可以换换口味呢?没错,就是那个一直默默陪伴你的系统,今天就来带你一起探...