当前位置: 首页 > news >正文

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

相关文章:

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

文章目录功能简介实现步骤获取看向的位置获取头部的位置修改头部的朝向限制旋转角度超出限制范围时自动回正如何让指定动画不受影响功能简介 如图所示&#xff0c;当相机的视角转动时&#xff0c;Avatar角色的头部会同步转动&#xff0c;看向视角的方向。 实现步骤 获取看向的…...

深度学习优化算法总结

深度学习的优化算法 优化的目标 优化提供了一种最大程度减少深度学习损失函数的方法&#xff0c;但本质上&#xff0c;优化和深度学习的目标不同。 优化关注的是最小化目标&#xff1b;深度学习是在给定有限数据量的情况下寻找合适的模型。 优化算法 gradient descent&#xf…...

CMake详细使用

1、CMake简介CMake是一个用于管理源代码的跨平台构建工具可以方便地根据目标平台和编译工具产生对应的编译文件主要用于C/C语言的构建&#xff0c;但是也可以用于其它编程语言的源代码。如同使用make命令工具解析Makefile文件一样cmake命令工具依赖于一个CMakeLists.txt的文件该…...

【数据结构与算法】前缀树的实现

&#x1f320;作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《数据结构与算法要啸着学》 &#x1f387;座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;…...

canvas 制作2048

效果展示 对UI不满意可以自行调整&#xff0c;这里只是说一下游戏的逻辑&#xff0c;具体的API调用不做过多展示。 玩法分析 2048 的玩法非常简单&#xff0c;通过键盘的按下&#xff0c;所有的数字都向着同一个方向移动&#xff0c;如果出现两个相同的数字&#xff0c;就将…...

playwright: 全局修改页面等待超时时间

等待超时时间默认是30s, 可以通过以下几个方法设置&#xff1a; browser_context.set_default_navigation_timeout()browser_context.set_default_timeout()page.set_default_navigation_timeout()page.set_default_timeout() set_default_navigation_timeout set_default_n…...

C++类和对象(中)

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 I do not believe in taking the right decision. I take a decision and make it right. 我不相信什么正确的决定。我都是先做决定&#xff0c;然后把…...

Docker安装EalasticSearch、Kibana,安装Elasticvue插件

使用Docker快速安装部署ES和Kibana的前提&#xff1a;首先需要确保已经安装了Docker环境。 如果没有安装Docker的话&#xff0c;先在Linux上安装Docker。 有了Docker环境后&#xff0c;就可以使用Docker安装部署ES和Kibana了 一、安装ES 1、拉取EalasticSearch镜像 docker p…...

算法训练营 day39 贪心算法 无重叠区间 划分字母区间 合并区间

算法训练营 day39 贪心算法 无重叠区间 划分字母区间 合并区间 无重叠区间 435. 无重叠区间 - 力扣&#xff08;LeetCode&#xff09; 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互…...

c/c++开发,无可避免的文件访问开发案例

一、缓存文件系统 ANSI C标准中的C语言库提供了fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等标准函数&#xff0c;这些函数在不同的操作系统中应该调用不同的内核API&#xff0c;从而支持开发者跨平台实现对文件的访问。 在Lin…...

MySQL学习笔记

MySQL学习笔记一、基础配置二、数据库操作三、表的操作1.创建表2.表选项3.查看表4.修改表5.删除表6.复制表7.检查优化修复表四、数据操作基础增删改查五、字符集编码六、数据类型&#xff08;列类型&#xff09;1.数值类型2.字符串类型3.日期时间类型4.枚举和集合七、列属性&am…...

ccs导入工程失败的处理方法

文章目录当导入CCS新工程时出现下述错误怎么办&#xff1f;方法一 从TI官网下载安装包进行安装&#xff0c;下载链接&#xff1a;软件下载完成 安装路径为上面的文件夹点击安装完成后&#xff0c;导入安装路径&#xff0c;并点击Refresh按钮&#xff0c;依据路径进行更新&#…...

探针台常见的故障及解决方法

症状、 可能原因、 解决方法 移动样品后画面变模糊 —显微镜不垂直&#xff0c;调垂直显微镜 样品台不水平 —调水平样品台 显微镜视场亮度不足&#xff0c;边缘切割或看不到像—转换器不在定位位置上 把转换器转到定位位置上 管镜转盘不在定位位置上 —把管镜转盘转到定…...

域内资源探测

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;内网安全 &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff0c;永远是…...

c# 将数据导出到EXCEL文件

第一步&#xff1a;项目中加入引用。 在鼠标右击项目&#xff0c;点击【添加】弹出菜单列表&#xff0c;选择【项目引用】弹出【引用管理器】对话框&#xff0c;选择【COM】-【Microsoft Excel 16.0 Object Library】&#xff0c;如图所示&#xff1a; 第二步&#xff0c;编辑…...

微服务 分片 运维管理

微服务 分片 运维管理分片分片的概念分片案例环境搭建案例改造成任务分片Dataflow类型调度代码示例运维管理事件追踪运维平台搭建步骤使用步骤分片 分片的概念 当只有一台机器的情况下&#xff0c;给定时任务分片四个&#xff0c;在机器A启动四个线程&#xff0c;分别处理四个…...

批量占满TEMP表空间问题处理与排查

批量占满TEMP表空间问题处理与排查应急处置问题排查查看占用TEMP表空间高的SQL获取目标SQL执行计划方法一&#xff1a;EXPLAIN PLAN FOR方法二&#xff1a;DBMS_XPLAN.DISPLAY_CURSOR方法三&#xff1a;DBMS_XPLAN.DISPLAY_AWR方法四&#xff1a;AUTOTRACE数据库跑批任务占满TE…...

Pytorch中的tensor和variable

Tensor与Variable pytorch两个基本对象&#xff1a;Tensor&#xff08;张量&#xff09;和Variable&#xff08;变量&#xff09; 其中&#xff0c;tensor不能反向传播&#xff0c;variable可以反向传播&#xff08;forword&#xff09;。 反向传播是为了让神经网络更新前面…...

暗月内网渗透实战——项目七

首先环境配置 VMware的网络配置图 环境拓扑图 开始渗透 信息收集 使用kali扫描一下靶机的IP地址 靶机IP&#xff1a;192.168.0.114 攻击机IP&#xff1a;192.168.0.109 获取到了ip地址之后&#xff0c;我们扫描一下靶机开放的端口 靶机开放了21,80,999,3389,5985,6588端口…...

【Java 面试合集】描述下Objec类中常用的方法(未完待续中...)

描述下Objec类中常用的方法 1. 概述 首先我们要知道Object 类是所有的对象的基类&#xff0c;也就是所有的方法都是可以被重写的。 那么到底哪些方法是我们常用的方法呢&#xff1f;&#xff1f;&#xff1f; cloneequalsfinalizegetClasshashCodenotifynotifyAlltoStringw…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...