使用导入的 3D 动画:
我们可以选中状态机窗口中的某一个状态为其设置相关参数,我们可以称之为动画状态设置
主要设置的是当前状态的播放速度等等细节
在 Animator 窗口中选择其中一个状态,可以看见 Inspector 窗口中如下参数:
Motion:分配给此状态的动画剪辑
Speed:动画的默认速度
Multiplier:控制速度的乘数,如果要使用需要勾选的 Parameter 选中配合的参数 float 类型
Motion Time:运动的时间,如果要使用需要勾选的 Parameter 选中配合的参数 float 类型
Mirror:是否为状态生成镜像,仅适用于人形动画,如果要配合参数使用选中旁边的 Parameter 关联参数,参数是 bool 类型
Cycle Offset:循环偏移时间,如果要配合参数使用选中旁边的 Parameter 关联参数,参数是 float 类型
Foot IK:是否遵循 Foot IK,适用于人形人形动画
Write Defaults:AnimatorStates 是否为其运动执行未动画化的属性写回默认值。
Solo / Mute:仅播放该过渡 / 禁用过渡
Solo 和 Mute 如果一起选择,Mute 优先执行
Add Behaviour:添加状态机行为脚本,见 八、状态机行为脚本
我们可以选中状态机窗口中的某一条箭头为其设置相关参数,我们可以称之为动画过渡设置
主要设置的是从一个状态切换到另一个状态时 的表现效果和切换条件
动作和动作之间的连线:
Has Exit Time:是否有退出时间,如果勾选,当切换动画时,动画一定是播放到下方的 Exit Time(百分比)的时间时才过渡到下一个动画
Exit Time:退出时间
当选择上方的 Has Exit Time 时,该值决定了过渡生效的确切时间,该值可以大于 1
如果小于 1,比如 0.85,表示当动画播放到了 85% 的动画时,就会过渡。
如果大于 1,比如 4.5,那么动画将循环 4.5 次后过渡到下一个动画
Fixed Duration:选中后,下方的 Trnaition Duration 过渡持续时间将以秒为单位解读过渡时间,如果不选中,则以百分比解读过渡时间
Transition Duration(s / %):过渡持续时间,相当于从该状态切换到下一状态的过渡动画持续的时间,对应下方两个蓝色箭头包裹区域
Transition Offset:过渡到目标状态的起始播放的时间偏移。如果是 0 则从目标状态开头开始播放,如果是 0.5 则从目标状态的一半开始播放
你可以理解为切入下一个状态的切入点
Interruption Source:该过渡中断的情况
Ordered Interruption:当前过渡是否可在不考虑顺序的情况下被其它过渡中断
选中时,找到有效过渡或当前过渡时,会中断
不选中时,找到有效过渡,会中断
Conditions:过渡条件
如果没有过渡条件,只会考虑 Exit Time
AnyState 和动作之间的连线:
多出如下参数:
Can Transition To self:是否可以过渡到自己
Preview source state:预览各种过渡状态
可以查看从任意状态切换到当前状态的过渡效果
注意点:
Has Exit Time 是否启用。
如果希望瞬间切换动画不需过多等待,取消该选项
Can Transition To self 是否启用。
如果希望自己不要打断自己,取消该选项
动画分层的作用:
游戏中会有这样的需求,人物健康状态时播放正常动画、人物非健康状态时播放特殊动画
比如血量低于一定界限,人物的大部分动作将表现为虚弱状态。我们可以利用动画分层来快速实现这样的功能
动画分层和动画遮罩结合使用:
3D 游戏中我们常常会面对这样的需求,人物站立时会有开枪动作、人物跑动时会有开枪动作、人物蹲下时会有开枪动作
从表现上来看光是开枪动作可能就有 3 种,如果要让美术同学做 3 种开枪动作费时又费资源
我们是否可以这样做,比如开枪动画只影响上半身,下半身根据实际情况播放站立,跑动,蹲下动作,通过上下半身播放不同的动画就可以达到动画的组合播放
动画分层的主要就是达到这两个目的:
在 Animator 窗口的 Layer 面板中创建一个新的层级。新层级与之前的层级共同控制动画的播放。
Weight:权重
当动画同时播放时,如果选择的是叠加状态,会根据权重决叠加的比例
Mask:动画遮罩,该层所有动画都只会作用在遮罩上,遮罩外面的部分不受该层动画影响
创建 Avatar 遮罩:
选择遮罩部分:
其中绿色表示该层将动画应用于此区域,红色部分不会受该层动画的影响
Blending:混合方式
Sync:是否同步其他层
主要用于直接从另一个层复制状态过来,在该层中进行修改,另一个层的设置信息都将保留,我们只需要替换状态对应的动画即可
适用于比如正常状态下有待机走路跑步等动作,但是受伤状态下动作会改变,可以利用同步层方便我们进行编辑
选择后会多一个 Source Layer 表示你要复制哪一层的状态
创建后,New Layer 的状态和状态之间的转换条件将和 MyLayer1 完全相同,只是该层每个状态对应的动画都为 None,自己对应替换需要设置的新状态即可:
Timing:当选中 Sync 同步其他层时,该参数激活
选中,会采用折中方案调整同步层上的动画时长(基于权重计算)
不选中,动画时长将使用原始层作为模板
IK Pass:反向动力学,之后讲解 IK 的时候再讲
代码控制:
private Animator animator;
animator = this.GetComponent<Animator>();
animator.SetLayerWeight(animator.GetLayerIndex("MyLayer2"), 1);
游戏动画中常见的功能就是在两个或者多个相似运动之间进行混合,比如:
根据角色的速度来混合行走和奔跑动画
根据角色的转向来混合向左或向右倾斜的动作
你可以理解是高级版的动画过渡
之前我们学习的动画过渡是处理两个不同类型动作之间切换的过渡效果,而动画混合是允许合并多个动画来使动画平滑混合
在 Animator Controller 窗口,右键 ->
Create State ->
From New Blend Tree
创建好后,双击 Blend Tree 进入混合树,点击后显示右侧的 Inspector 面板。
这里需要在 Motion 列表中点击 + 号添加至少两个动作字段才有如下的参数界面,混合树 Blend Tree 也可以包含混合树:
Parameter:参数,用于控制混合的参数,在参数列表中的参数
蓝色图像:可以在这里控制 n 个动画的阈值
Motion:关联的动画列表,可以用鼠标改变顺序
Threshold:对应动作的临界阈值 当等于这个值时动作权重最大(完全播放该动作)
这个值可以完全自由控制,数值范围不定
:控制动作的播放速度
一般默认为 1,做好的动作速度一般不需要修改
:是否镜像动作
Automate Thresholds:是否自动设置阈值,它会在取值范围内平均分
一般可以取消勾选我们手动控制更准确
Compute Thresholds:计算阈值的方式
会从动画剪辑的根运动中获去数据
举例:
比如你的动画剪辑行走动画时 Speed 速度是 1.5 个单位每秒,慢跑是 2.3 个单位每秒,快跑是 4 个单位每秒,阈值就会根据这些值来进行设置进行混合
Adjust Time Scale:调整时间刻度
1D 混合就是通过一个参数来混合子运动
注意:往混合树里面加入动作时需要找到动画文件进行关联
1D 混合是用一个参数控制动画的混合,之所以叫 1D 是因为一个参数可以看做是 1 维线性的
2D 混合可以简单理解是用两个参数控制动画的混合,之所以叫 2D 是因为两个参数可以看做是 2 维平面 xy 轴的感觉
在之前的 Inspector 窗口中下拉 Blend Type 列表,可以看到有多种不同的混合类型:
2D Simple Directional:2D 简单定向模式。运动表示不同方向时使用,比如向前、后、左、右走
2D Freeform Directional:2D 自由形式定向模式。同上 运动表示不同方向时使用,但是可以在同一方向上有多个运动,比如向前跑和走
2D Freeform Cartesian:2D 自由形式笛卡尔坐标模式。运动不表示不同方向时使用,比如向前走不拐弯、向前跑不拐弯、向前走右转、向前跑右转
Direct:直接模式。自由控制每个节点权重,一般做表情动作等
在 Animator 的 Parameters 中创建两个 Float 变量,并在 Blend Tree 中进行关联
我们设置的 x、y 变量对应于 PosX、PosY,相当于二维平面内的坐标。红色点表示当前 x、y 的值对应的坐标,调整红色点的位置可以在下方窗口预览当前对应的动画:
前三种方式只是针对动作的不同采用不同的算法来进行混合的,第四种可以用多个参数进行融合
混合树中还可以再嵌入混合树,使用上是一致的,根据实际情况选择性使用
子状态机顾名思义就是在状态机里还有一个状态机,它的主要作用就是某一个状态是由多个动作状态组合而成的复杂状态
比如某一个技能它是由 3 段动作组合而成的,跳起,攻击,落下
当我们释放这个技能时会连续播放这 3 个动作,那么我们完全可以把他们放到一个子状态机中
在 Animator Controller 窗口中,右键 ->
Create Sub-State Machine
创建出来后,是个类似菱形的图标,我们双击进入子状态机
子状态机的界面和状态机类似。不同的是有一个上层的图标(Up Base Layer),通过连接该状态来转移到上层状态机的某个状态
在这里选择上一层的某个状态
在这里选择回到哪一层,将播放该层的默认动画
在状态机的层级设置中,开启 IK 通道
继承 MonoBehavior 的类中,Unity 定义了一个 IK 回调函数:OnAnimatorIK
我们可以在该函数中调用 Unity 提供的 IK 相关 API 来控制 IK
Animator 中的 IK 相关 API:
private Animator animator;
public Transform pos;
public Transform pos2;
private void OnAnimatorIK(int layerIndex)
{
// 头部 IK 相关
// weight: LookAt全局权重 0 ~ 1
// bodyWeight: LookAt 时身体的权重 0 ~ 1
// headWeight: LookAt 时头部的权重 0 ~ 1
// eyesWeight: LookAt 时眼镜的权重 0 ~ 1
// clampWeight: 0 表示角色运动时不受限制,1 表示角色完全固定无法执行 LookAt,0.5 表示只能够移动范围的一半
animator.SetLookAtWeight(1, 1f, 1f);
animator.SetLookAtPosition(pos.position);
// animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1);
animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1);
// animator.SetIKPosition(AvatarIKGoal.RightFoot, pos2.position);
animator.SetIKRotation(AvatarIKGoal.RightFoot, pos2.rotation);
}
private void OnAnimatorMove()
{
// 如果动画本身有移动,还需要自己添加代码移动,建议在这里添加
}
IK 在游戏开发中的应用
关于 OnAnimatorIK 和 OnAnimatorMove 两个函数的理解:
我们可以简单理解这两个函数是两个和动画相关的特殊生命周期函数,他们在 Update 之后 LateUpdate 之前调用
他们会在每帧的状态机和动画处理完后调用,OnAnimatorIK 在 OnAnimatorMove 之前调用
OnAnimatorIK 中主要处理 IK 运动相关逻辑
OnAnimatorMove 主要处理动画移动以修改根运动的回调逻辑
他们存在的目的只是多了一个调用时机,当每帧的动画和状态机逻辑处理完后再调用
动画目标匹配主要指的是:当游戏中角色要以某种动作移动,该动作播放完毕后,人物的手或者脚必须落在某一个地方
比如:角色需要跳过踏脚石或者跳跃并抓住房梁,那么这时我们就需要动作目标匹配来达到想要的效果
Unity 中的 Animator 提供了对应的函数来完成该功能,使用步骤是
找到动作关键点位置信息(比如起跳点,落地点,简单理解就是真正可能产生位移的动画表现部分)
将关键信息传入 MatchTargetAPI 中
private void MatchTarget()
{
// 参数一:目标位置
// 参数二:目标角度
// 参数三:匹配的骨骼位置
// 参数四:位置角度权重
// 参数五:开始位移动作的百分比
// 参数六:结束位移动作的百分比
animator.MatchTarget(targetPos.position, targetPos.rotation, AvatarTarget.RightFoot, new MatchTargetWeightMask(Vector3.one, 1), 0.4f, 0.64f);
}
其中,位移动作百分比在这里查看:
调用匹配动画的时机有一些限制:
必须保证动画已经切换到了目标动画上
必须保证调用时动画并不是处于过度阶段而真正在播放目标动画:
如果发现匹配不正确,往往都是这两个原因造成的
需要开启 Apply Root Motion
通常的做法是,为动画添加事件进行触发,事件选在播放动画后的某一点添加
状态机行为脚本时一类特殊的脚本,继承指定的基类,它主要用于关联到状态机中的状态矩形上
我们可以按照一定规则编写脚本,当进入、退出、保持在某一个特定状态时我们可以进行一些逻辑处理
简单解释就是为 Animator Controller 状态机窗口中的某一个状态添加一个脚本,利用这个脚本我们可以做一些特殊功能。比如:
进入或退出某一状态时播放声音
仅在某些状态下检测一些逻辑,比如是否接触地面等等
激活和控制某些状态相关的特效
新建一个脚本继承 StateMachineBehaviour 基类
实现其中的特定方法进行状态行为监听
处理对应逻辑
public class Lesson57_StateMachineBehaviour : StateMachineBehaviour
{
public string stateName; // 可以配置字段实现脚本的通用性
public string musicName;
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
if (stateInfo.IsName(stateName))
Debug.Log("进入HumanoidIdle状态");
}
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
if (stateInfo.IsName("HumanoidIdle"))
Debug.Log("退出HumanoidIdle状态");
}
public override void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
base.OnStateIK(animator, stateInfo, layerIndex);
}
public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
base.OnStateMove(animator, stateInfo, layerIndex);
}
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
base.OnStateUpdate(animator, stateInfo, layerIndex);
if (stateInfo.IsName("HumanoidIdle"))
Debug.Log("处于HumanoidIdle状态");
}
public override void OnStateMachineEnter(Animator animator, int stateMachinePathHash) {
base.OnStateMachineEnter(animator, stateMachinePathHash);
}
public override void OnStateMachineExit(Animator animator, int stateMachinePathHash) {
base.OnStateMachineExit(animator, stateMachinePathHash);
}
}
状态机行为脚本和动画事件如何选择:
游戏开发时经常遇到这样的情况:
有 n 个玩家和 n 个怪物,他们的动画状态机行为都是一致的,只是对应的动作不同而已
这时如果我们为他们每一个对象都创建一个状态机进行状态设置和过渡设置无疑是浪费时间的
所以状态机复用就是解决这一问题的方案,主要用于为不同对象使用共同的状态机行为,减少工作量,提升开发效率
在 Project 窗口,右键 Create ->
Animator Override Controller
为 Animator Override Controller 文件在 Inspector 窗口关联基础的 Animator Controller 文件
关联需要的动画
角色控制器是让角色可以受制于碰撞,但是不会被刚体所牵制
如果我们对角色使用刚体判断碰撞,可能会出现一些奇怪的表现,比如:
在斜坡上往下滑动
不加约束的情况碰撞可能让自己被撞飞
等等
而角色控制器会让角色表现的更加稳定,Unity 提供了角色控制器脚本专门用于控制角色
注意:添加角色控制器后,不用再添加刚体
选中角色模型,在 Inspector 窗口中添加如下组件:
代码控制:
public class Lesson59 : MonoBehaviour
{
private CharacterController cc;
private Animator animator;
// Start is called before the first frame update
void Start() {
cc = this.GetComponent<CharacterController>();
animator = this.GetComponent<Animator>();
// 是否接触了地面
if (cc.isGrounded) print("接触地面了");
// 受重力作用的移动
cc.SimpleMove(Vector3.forward * 10 * Time.deltaTime);
// 不受重力作用的移动
cc.Move(Vector3.forward * 10 * Time.deltaTime);
}
// Update is called once per frame
void Update() {
animator.SetInteger("Speed", (int)Input.GetAxisRaw("Vertical"));
cc.Move(this.transform.forward * (80 * Time.deltaTime * Input.GetAxisRaw("Vertical")));
if (cc.isGrounded) print("接触地面了");
}
// 当角色控制器想要判断和别的碰撞器产生碰撞时,使用该函数,相当于替换了 OnCollisionEnter 函数
private void OnControllerColliderHit(ControllerColliderHit hit) {
print(hit.collider.gameObject.name);
}
// 对角色控制器没用
//private void OnCollisionEnter(Collision collision)
//{
// print("碰撞触发");
//}
//可以检测触发器
private void OnTriggerEnter(Collider other) {
print("触发器触发");
}
}
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线