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

Unity笔记(十一)——换装、Spine骨骼动画、3D动画相关

写在前面:

写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。这里只有代码相关部分,没有面板部分。

五、换装

1、换装准备

在制作人物系统时,会涉及到换装。首先为对象创建骨骼,生成蒙皮,调整权重(这一步不作细致说明):

接下来,创建一个Sprite Library Asset,创建方式是在Project窗口右键—Create—2D—Sprite Library Asset。创建后点击:

打开后即可在界面左侧创建分类,右侧为分类关联该类别的子对象。创建完成后,点击Save。

创建完成后,在场景上需要换装的对象身上创建Sprite Library脚本,并为其关联先前创建的Sprite Library Asset。

为需要换装的子对象都添加脚本:Sprite Resolver,选择相应的类别。

这样,就做好了换装准备,可以进行代码换装了。

2、代码换装

换装可以采用的API是

public SpriteResolver sr;

sr.SetCategoryAndLabel(sr.GetCategory(), "CASK 1"); 第一个参数是需要换装的类别,第二个参数是需要换为的对象名。当想要换装的部件很多时,需要关联多个SpriteResolver对象,较为麻烦。可以使用如下代码,先存储换装信息为一个字典,再换装:

//public SpriteResolver sr;private Dictionary<string, SpriteResolver> equipDic = new Dictionary<string, SpriteResolver>();void Start()
{//sr.SetCategoryAndLabel(sr.GetCategory(), "CASK 1");SpriteResolver[] srs = this.GetComponentsInChildren<SpriteResolver>();for(int i = 0; i < srs.Length; i++){equipDic.Add(srs[i].GetCategory(), srs[i]);}ChangeEquip("Cask", "CASK 1");
}public void ChangeEquip(string category, string equipName)
{if (equipDic.ContainsKey(category)){equipDic[category].SetCategoryAndLabel(category, equipName);}
}

六、Spine骨骼动画

Spine动画系统与Unity自带的动画系统不同。将从Spine导出的文件直接拖入Hierarchy中,会弹出如下窗口

选择第一个,即可创建骨骼动画。对象身上会自动创建如下脚本:

可以通过获取SkeletonAnimation脚本来使用代码控制动画播放。

1、动画播放

修改播放的动画有两种方式:

(1)直接改变SkeletonAnimation中的参数

若想要修改动画的循环方式,需要先改循环再改动画,否则循环不会生效。

直接使用sa.AnimationName = "idle",修改播放的动画名,即可修改播放的动画

private SkeletonAnimation sa;void Start()
{sa = this.GetComponent<SkeletonAnimation>();sa.loop = false;sa.AnimationName = "idle";
}

(2)使用动画状态改变函数

直接使用API,sa.AnimationState.SetAnimation(0, "jump", false)。括号内传入了3个参数,第一个参数为索引值,填0即可;第二个参数是需要播放的动画名,第三个是是否循环播放。

此外,还可以使用排队播放:sa.AnimationState.AddAnimation(0, "walk", true, 0)。会等待上一个运行完,前三个参数和前者相同,第四个参数是延迟播放时间。

最后,如果需要动画转向,可以使用API,sa.skeleton.ScaleX。当sa.skeleton.ScaleX = -1时表示在X轴上进行镜像翻转。

private SkeletonAnimation sa;void Start()
{sa = this.GetComponent<SkeletonAnimation>();sa.AnimationState.SetAnimation(0, "jump", false);sa.AnimationState.AddAnimation(0, "walk", true, 0);//转向sa.skeleton.ScaleX = -1;
}

2、动画事件

共有4种动画事件,这四种事件函数都必须传入参数,该参数能得到动画相关信息:

在动画开始时进行的事件:sa.AnimationState.Start

在动画中断or清除时进行的事件:sa.AnimationState.End

在动画完成时进行的事件:sa.AnimationState.Complete

为动画添加自定义事件:sa.AnimationState.Event,注意,需要传入两个参数,示例中的参数e是具体发生的事件,该事件的添加在Spine中制作动画时,而非Unity中,因此这里只是事件的监听。

private SkeletonAnimation sa;void Start()
{sa = this.GetComponent<SkeletonAnimation>();//动画开始sa.AnimationState.Start += (t) =>{print(sa.AnimationName+"动画开始");};//动画中断or清除sa.AnimationState.End += (t) =>{print(sa.AnimationName + "动画中断or清除");};//动画完成sa.AnimationState.Complete += (t) =>{print(sa.AnimationName + "动画完成");};//动画添加自定义事件sa.AnimationState.Event += (t, e) =>{print(sa.AnimationName + "自定义事件");};
}

3、便捷特性

设置特性,可以让我们Inspector面板及代码的使用更加便捷。例如,添加了动画特性[SpineAnimation]后,Inspector窗口上就会出现你声明的AnimationName变量,并且打开下拉列表可以为其选择动画。这样,代码后续想要使用该动画时,动画名直接传入AnimationName即可,不用再去看动画名是什么。

[SpineAnimation]
public string AnimationName;

常用的特性有这几个:

动画特性[SpineAnimation]
骨骼特性[SpineBone]
插槽特性[SpineSlot]
附件特性[SpineAttachment]

这里,插槽指的是身体上的一个装备栏位(比如“右手”这个栏位)

附件指的是可以放进该栏位的具体装备(比如“木剑”、“铁剑”、“火把”或“空手”)

private SkeletonAnimation sa;[SpineBone]
public string boneName;[SpineSlot]
public string slotName;[SpineAttachment]
public string attachmentName;
void Start()
{sa = this.GetComponent<SkeletonAnimation>();//获取骨骼Bone b = sa.skeleton.FindBone(boneName);//设置插槽附件sa.skeleton.SetAttachment(slotName, attachmentName);
}

七、3D动画相关

1、动画遮罩

假如我们需要制作瞄准动画,人物在蹲下和站起分别进行瞄准时,上半身的动作都为举枪瞄准动作,有区别的只是下半身的动作,即是站立还是蹲下。为了节省制作成本,可以使用动画遮罩,即我们只修改下半身的动作,用同一套上半身动作即可。

(1)动画层次

在Animator窗口新建层次,新建后点击右上角的齿轮,在Blending处可以选择混合模式:Override为覆盖,Additive为叠加。在Weight处可设置权重,权重越大该层造成的影响越大,例如:新层次new Layer模式选择Override,Weight设为1,就会完全覆盖Base Layer。

(2)遮罩

若想只覆盖下半身,需要创建遮罩:在Project窗口右键Create Avatar Mask即可创建动画遮罩。

可点击肢体进行取消选中,动画就不会影响到红色取消部位。

最后将动画遮罩与对应层次关联即可。

(3)代码修改层次权重

可通过animator.SetLayerWeight()来设置对应层次权重。括号内第一个参数传入层次索引,可通过animator.GetLayerIndex("层次名")利用层次名获得层次索引。第二个参数是设置的层次大小。

private Animator animator;void Start()
{animator = this.GetComponent<Animator>();animator.SetLayerWeight(animator.GetLayerIndex("MyLayer1"), 1);
}

2、动画IK控制

(1)开启IK

动画IK控制可以实现开枪瞄准目标时,人物身体跟随目标移动;人物眼神跟随指定物体移动等效果。若想要通过代码设置动画IK,可以在Animator窗口的Layers窗口齿轮处,点击勾选IK pass。之后就可以在代码中进行动画IK控制 。

(2)代码相关

①OnAnimatorIK和OnAnimatorMove

OnAnimatorIK和OnAnimatorMove是两个和动画相关的特殊生命周期函数。它们会在Update之后和LateUpdate之前调用。

IK相关代码需要写在函数OnAnimatorIK中。

OnAnimatorMove主要处理动画移动以修改根运动的回调逻辑。若你的模型中原本就带有移动而你又想继续添加移动,就可以在这个函数中写逻辑。

②头部IK相关

设置头部IK使用两个API,分别是animator.SetLookAtWeight()和animator.SetLookAtPosition()。后者是设置看向的位置,这里拖入了一个物体的位置。前者是设置各部分的权重,最多可以传入5个参数。这5个参数从左到右分别是:

weight:LookAt全局权重0~1;

bodyWeight:LookAt时身体权重0~1;

headWeight:LookAt时头部权重0~1;

eyesWeight:LookAt时眼睛权重0~1;

clampWeight:0表示角色运动时不受限制,1表示角色完全固定无法执行LookAt,0.5表示只能够移动一半;

下例中只设置了全局、身体、头部权重。

private Animator animator;public Transform pos;void Start()
{animator = this.GetComponent<Animator>();
}private void OnAnimatorIK(int layerIndex)
{animator.SetLookAtWeight(1,0.5f,0.5f);animator.SetLookAtPosition(pos.position);
}

③四肢IK相关

四肢IK使用四个API,分别是:

SetIKPositionWeight(),设置对应肢体位置的权重,第一个参数是需要控制的肢体,使用AvatarIKGoal点出,例如右手是AvatarIKGoal.RightHand,下同;第二个参数为设置的权重大小。

SetIKRotationWeight(),设置对应肢体角度的权重,第一个参数是需要控制的肢体;第二个参数为设置的权重大小。

SetIKPosition(),设置对应肢体的位置,第一个参数是需要控制的肢体;第二个参数是肢体的位置。

SetIKRotation(),设置对应肢体的角度,第一个参数是需要控制的肢体;第二个参数是肢体的角度。

private Animator animator;public Transform pos2;void Start()
{animator = this.GetComponent<Animator>();
}private void OnAnimatorIK(int layerIndex)
{animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);animator.SetIKPosition(AvatarIKGoal.RightHand, pos2.position);animator.SetIKRotation(AvatarIKGoal.RightHand, pos2.rotation);
}

3、动画目标匹配

(1)作用

当游戏中角色要以某种动作移动,该动作播放完毕后,人物的手或者脚必须落在某一个地方。比如角色需要跳过踏脚石或者跳跃并抓住房梁。这时候就需要动作目标匹配来达到想要的效果。

使用步骤是:

1、找到动作关键点位置信息,比如起跳点,落地点

2、将关键信息传入MatchTargetAPI中

(2)代码相关

如图所示人物脚底为起跳点,立方体上的空物体即为选取的落地点。需要将落地点targetPos关联到代码中。

实例中想要实现的是:人物在待机时播放待机动画,按下空格键播放跳跃动画并跳至平台上。构建如下状态机并与人物模型关联:

添加Trigger参数,触发时切换到跳跃动作。

①注意事项

调用匹配动画的时机有一些限制。

1、必须保证动画已经切换到了目标动画上。例如,我们需要在跳跃时设置动画匹配,若此时没有切换到跳跃动画上而一直是待机动画,就肯定不会进行动画匹配。

2、必须保证调用时动画并不是出于过渡阶段而真正在播放目标动画。例如下图:

待机动画切换到跳跃动画有0.25s的过渡时间,如果在该过渡时间内进行动画匹配就会出现很奇怪的效果。

3、需要开启Apply Root Motion。在模型的Animator脚本处勾选即可。

②代码控制

在注意事项中我们知道,必须保证调用时动画并不是出于过渡阶段而真正在播放目标动画。我们制作的动画中,从Idle状态到Jump的状态中有0.25s的过渡时间,在该时间内不能进行动画匹配。因此,可以在动作Jump的Inspector窗口中在0.25s后为其添加一个事件,利用事件来进行动画目标匹配:

只要在对象挂载的脚本中书写了同名函数MatchTarget,该函数就会自动执行。

动画目标匹配使用的API是: animator.MatchTarget(),其中,可传入6个参数

参数一:目标位置
参数二:目标角度
参数三:匹配的骨骼位置,可以通过AvatarTarget来点出需要匹配的肢体。例如AvatarTarget.LeftFoot匹配左脚。
参数四:位置角度权重
参数五:开始位移动作的百分比
参数六:结束位移动作的百分比
关于参数5,6需要在动画预览窗口查看。例如下图,在动画播放到40%处人物准备起跳(开始位移),结束位移动作百分比也同理。

private Animator animator;
public Transform targetPos;void Start()
{animator = this.GetComponent<Animator>();
}void Update()
{if(Input.GetKeyDown(KeyCode.Space)){animator.SetTrigger("Jump");}
}private void MatchTarget()
{animator.MatchTarget(targetPos.position, targetPos.rotation, AvatarTarget.LeftFoot, new MatchTargetWeightMask(Vector3.one, 1), 0.4f, 0.64f);
}

4、状态机行为脚本

(1)作用

状态机行为脚本是一类特殊的脚本,可以实现一些特殊功能:进入或退出某一状态时播放声音、仅在某些状态下检测一些逻辑等。

(2)代码

新建一个脚本继承StateMachineBehaviour基类

选择自己需要的特定方法进行重写:

OnStateEnter:进入状态时,第一个Update中调用

OnStateExit:退出状态时,最后一个Update中调用

OnStateIK:OnAnimatorIK后调用

OnStateMove:OnAnimatorMove后调用

OnStateUpdate:除第一帧和最后一帧,每个Update上调用

OnStateMachineEnter:子状态机进入时调用,第一个Update中调用

OnStateMachineExit:子状态机退出时调用,最后一个Update中调用

public class lession10_StateMachineBehaviour : StateMachineBehaviour
{public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){if(stateInfo.IsName("HumanoidIdle"))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){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);}
}

http://www.dtcms.com/a/461541.html

相关文章:

  • 面向汽车网络安全的轻量级加密技术
  • 《投资-114》价值投资者的认知升级与交易规则重构 - 从大规模分工的角度看,如何理解“做正确的事”,即满足下游正确的需求
  • 添加一路AXI总线对DDR进行读写时,XDMA测试不通过
  • 基于python的机器学习(十)—— 评估算法(三)
  • 男女做那个的的视频网站检察院门户网站建设成效
  • Oracle的SID是什么
  • Oracle大会临近,23ai 本地版会发布吗?
  • 【Python刷力扣hot100】11. Container With Most Water
  • 通信建设网站做网站主页上主要放哪些内容
  • 《常用 IDL(接口定义语言)详解与对比》
  • 做二手房产网站多少钱河南建设工程信息网站
  • K230基础-获取触摸坐标
  • Linux应用--网络编程
  • 鸟哥的Linux私房菜 第三部分: 学习shell与shell script
  • 鸿蒙中 UDP 数据包发不出去?一文教你从权限到代码彻底排查!
  • 前端小白学习路线(参考)
  • 大连工业大学图书馆网站建设优化培训班
  • 浅谈 富文本编辑器
  • 有手机网站了还要微网站吗设计平台兼职
  • **发散创新:状态函数在编程中的深度应用与实现**在编程领域,状态函数是一个核心概
  • 【OCR】PaddleX
  • Python 元组与集合详解
  • 微信小程序的页面生命周期 以及onShow的应用场景
  • 微信小程序入门学习教程,从入门到精通,微信小程序核心 API 详解与案例(13)
  • 企业建站系统知识库管理系统方案
  • 购物网站的排版wordpress个人主页
  • 51c视觉~3D~合集7
  • 生鲜买菜商城APP:便捷生活,触手可及的新鲜体验
  • 网站seo去哪个网站找好做化妆品的网站有哪些
  • Java求职面试:从Spring Boot到Kafka的技术探讨