美文网首页
Unity 动画系列五 常用脚本API

Unity 动画系列五 常用脚本API

作者: 合肥黑 | 来源:发表于2021-11-30 09:36 被阅读0次

参考
学习笔记 --- Unity动画系统
Unity动画系统详解10:子状态机是什么?

一、Parameters
1.脚本中获取/设置动画参数的方法
//这里的名称要与Animator窗口中,动画参数的名称对应
//通常对于调用频繁的动画参数我们使用哈希值进行快速访问
int runHash=Animator.StringToHash("Run");

//下面设置/获取动画参数均有使用String参数名称进行映射的重载和使用哈希值进行映射的重载


//获取设置Float类型参数,通常结合Input轴线
animator.GetFloat(blendHash);
animator.SetFloat(blendHash, Input.GetAxis("Horizontal"));

//获取设置Int类型参数
animator.GetInteger(intHash);
animator.SetInteger(intHash,Number);

//获取设置Bool类型参数
animator.GetBool(boolHash);
animator.SetBool(boolHash, true / false);

//触发,取消触发Trigger的方法
animator.SetTrigger(jumpHash);
animator.ResetTrigger(jumpHash);
2.ResetTrigger

转自Unity之碰到哪说到哪-ResetTrigger
ResetTrigger是个what?再此之前我并不知道,准确说看到过但是并没有care。开始了解它,是 因 为 出 BUG 了 !!

  • 项目中播放动画统一使用全局的一个通用方法。播放动画接口调用SetTrigger。
  • 摇杆开始移动时,调用SetTrigger("Run"),结束时,调用SetTrigger("Idel")。
  • 当角色在run时,点击了一个npc,触发寻路接口移动到npc,当然寻路开始时,也会在调用一次settrigger("Run").
  • 当寻路过程中,再次控制摇杆移动时(打断寻路),没有问题,但是当停止摇杆时,应该播放idle动作,但是实际停止后还是播放run。可是看log。我明明最后一次调用了SetTrigger("Idle")

So着重看了下SetTrigger。

  • SetTrigger可以改变动画状态机的状态,用于触发动画
  • SetTrigger是四个接口之一,其他还有SetFloat、SetInt、SetBool
  • SetTrigger本质上是SetBool,不同点在于,SetBool有两个可选择的值,false/true。但是SetTrigger比较特殊,调用SetTrigger会自动激活状态,同时又会自动设置状态为false。
image.png
  • 当摇杆滑动时,调用SetTrigger播放run动画,可以在当前帧通过GetTrigger("homerun") 看到激活状态是true。 当过了一帧后,再次GetTrigger("homerun") 是false。可以看到,trigger会自动回到false。
  • 摇杆在滑动角色在跑动时,又调用寻路接口,再次触发SetTrigger("homerun"). 这个时候,homerun的trigger状态又被设置成true。 但是重要的是:因为已经在homerun状态了,unity并不会重新进入这个状态,所以homerun的trigger状态并不会自动进入false。
  • 所以在我停止的摇杆的时候,虽然我调用了SetTrigger("comidle"), unity会进入idle状态,但是因为homerun的trigger状态一直是true,所以进入idle状态后,又会进入homerun状态。由此引起的bug。

解决办法ResetTrigger。所以SetTrigger() 之前,我们需要清除可能已经被激活的Trigger。如下方法:

/// <summary>
/// 清除所有的激活中的trigger缓存
/// </summary>
public void ResetAllTriggers(Animator animator)
{
    AnimatorControllerParameter[] aps = animator.parameters;
    for (int i = 0; i < aps.Length; i++)
    {
        AnimatorControllerParameter paramItem = aps[i];
        if (paramItem.type == AnimatorControllerParameterType.Trigger)
        {
            string triggerName = paramItem.name;
            bool isActive = animator.GetBool(triggerName);
            if (isActive)
            {
                animator.ResetTrigger(triggerName);
            }
        }
    }
}
二、State/Transaction
1.脚本中获取State/Transaction状态信息

首先我们要获取动画层ID

int layerID = animator.GetLayerIndex("Base Layer");

这里的LayerID就是Animator窗口中的动画层从上到下的排序


image.png

之后我们可以通过以下方法来获取State状态信息

AnimatorStateInfo animatorStateInfo;
AnimatorTransitionInfo transitionInfo;
//获取当前状态/过渡出发状态的信息
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(layerID);
//获取将要过渡到的状态信息
animatorStateInfo = animator.GetNextAnimatorStateInfo(layerID);
//获取过渡信息
transitionInfo = animator.GetAnimatorTransitionInfo(layerID);
2.状态的shortNameHash与fullPathHash

我们获取到的状态信息中,并不包含State的名称,而是State的短名和完整名的哈希值


例如这个State名为Idle,那么其ShortNameHash就是哈希 Idle
//我们先预设状态的哈希值
int idleHash = Animator.StringToHash("Idle");

//在Update中加入以下代码
int layerID = animator.GetLayerIndex("Base Layer");
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(layerID);
if (animatorStateInfo.shortNameHash == idleHash)//判定当前状态是否是Idle状态
{
    Debug.Log("OnState Idle");
}

测试结果,在Idle状态下产生了输出。

而对于fullPathHash则是追溯动画层,所有子动画组,的整个路径,以及State名称的整个字符串进行哈希算法获得的值。例如对于下面这个状态的fullPathHash为:"Base Layer.FlyMechine.Fly"


image.png
public class InfoDebug : MonoBehaviour
{

    Animator animator;

    AnimatorStateInfo animatorStateInfo;

    int flyHash = Animator.StringToHash("Base Layer.FlyMechine.Fly");
    // Start is called before the first frame update
    void Start()
    {
        animator = gameObject.GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {

        animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);

        if (animatorStateInfo.fullPathHash == flyHash)
        {
            Debug.Log("OnState Fly");
        }

    }
}

运行结果,Fly状态下产生输出

3. tagHash 状态标签

我们可以设置状态的标签名,从而对状态进行归类


image.png
int tagHash = Animator.StringToHash("tagName");

if(animatorStateInfo.tagHash==tagHash){
//do something
}
4.过渡状态的nameHash与userNameHash

对于一个过渡状态,它拥有一个name(下图中对应"Fly -> TakeOn"这个字符串的哈希值,注意空格!!!)以及一个可以在Inspector窗口中设置的userName


image.png
AnimatorTransitionInfo transitionInfo;
transitionInfo = animator.GetAnimatorTransitionInfo(layerID);
Debug.Log(transitionInfo.nameHash);
Debug.Log(transitionInfo.userNameHash);
5.不同状态下 CurrentState NextState Transition 的信息对应

我们抽象出动画状态机三个状态来解释不同阶段下,三种信息的对应关系


状态A 状态A到B的过渡(A -> B) 状态B

在执行状态A时:

  • CurrentStateInfo对应状态A的信息
  • NextStateInfo和TransitionInfo是空信息,它们中包含的各种哈希值都为0

在执行状态A向B的过渡时(A->B):

  • CurrentStateInfo对应状态A的信息
  • NextStateInfo对应状态B的信息
  • TransaitonInfo对应过渡 A -> B 的信息

在过渡完成,执行状态B时:

  • CurrentStateInfo对应状态B的信息
  • NextStateInfo和TransitionInfo是空信息,它们中包含的各种哈希值都为0
三、State Machine Behaviour

State Machine Behaviour是一种特殊的脚本。和通用的Unity脚本(MonoBehaviour)挂到GameObject上面类似,StateMachineBehaviour可以挂到Animator Controller的State上面。可以在StateMachineBehaviour脚本中编写代码,在状态进入、离开、停留在特定的state时执行。你就不需要自己去检测状态的变化。

可能用于的场景举例:

  • 进入、离开状态时播放音效
  • 只在特定的状态中执行一些代码
  • 只在特定的状态中激活特效

选中一个State,点击Inspector中的Add Behaviour按钮可以选择已有的StateMachineBehaviour或创建一个新的StateMachineBehaviour。


image.png
image.png

StateMachineBehaviour中有一些预定义的事件方法:

  • OnStateMachineEnter 转换到一个StateMachine时调用。注意转换到子状态机中的状态时不会调用。
  • OnStateMachineExit 离开StateMachine时调用。注意转换到子状态机中的状态时不会调用。
  • OnStateEnter 进入当前State时调用
  • OnStateExit 离开当前State时调用
  • OnStateUpdate 处于当前状态时,每次Update都会调用(不包括Enter和Exit的两帧)
  • OnStateMove 在MonoBehaviour.OnAnimatorMove之后调用。相当于Mono脚本中OnAnimatorMove的作用,使用之前提到的RootMotion模式三,但仅针对这个State状态运行时
  • OnStateIK 在MonoBehaviour.OnAnimatorIK之后调用。相当于Mono脚本中的OnAnimatorIK的作用,但仅针对这个State状态运行时

触发方法时,都会将下面三个变量作为参数传入

  • Animator:当前脚本所在的State,在游戏运行时对应的Animator组件
  • AnimatorStateInfo:当前脚本所在的State的信息
  • layerIndex:当前脚本所在的State,所在动画层的ID

因此相较于Mono脚本,StateMechinBehaviour脚本能够直接获取到Animator组件以及State信息,并在对应的接口执行一些控制逻辑。并且StateMechinBehaviour脚本能够直接针对某个状态实施一些逻辑,不需要像Mono脚本中针对一些参数,先判定State状态,再进行设定。

一个StateMechineBehaviour可以被挂载多个State上,我们可以根据传入的StateInfo进行分支逻辑,但通常我们都会针对一个State专门创建出一个SMB。

1.OnStateEnter/OnStateUpdate/OnStateExit 的具体触发细节

我们在Unity2018.3版本测试了上面这三个方法,在正常过渡的情况下,以及过渡打断的情况下的触发细节,一遍我们更好的使用上面三个方法。以下我们是经过测试所得出的结论,测试过程相关这里就不过多赘述了

Case 1:
我们抽象出

  • 状态A
  • 状态A到B的过渡(A->B)
  • 状态B

这样的两个状态进行正常过渡的情况下

  • 当执行状态A时:
    每帧执行OnStateUpdata_A

  • 当状态A到B的过渡被触发的那一帧:
    (OnStateEnter会在指向这个状态的过渡被触发时执行)
    执行了OnStateEnter_B
    执行了OnStateUpdate_A

  • 当执行状态A到状态B的过渡时:
    (执行过渡的过程中,每帧先执行CurrentState的Update,之后执行NextState的Update)
    (这个过渡状态下Update的执行顺序是绝对的)
    每帧先执行OnStateUpdata_A,后执行OnStateUpdata_B

  • 当进入到状态B时的那一帧:
    (正常过渡下,进入到其它状态的那一帧会执行上一状态的Exit)
    执行了OnStateExit_A
    执行了OnStateUpdata_B

  • 当执行状态B时:
    每帧执行OnStateUpdata_B

Case 2:
我们抽象出

  • 状态A
  • 状态A到B的过渡(A->B)
  • 状态B
  • 打断(A->B)过渡并指向C的过渡(->C)

这样的执行状态A->B的过渡被打断,并转而向状态C过渡的情况。这里无所谓(->C)究竟是(A->C)Current打断或是(B->)Next打断,我们在这两种情况下得到了相同的结论。

  • 当执行状态A时:
    每帧执行OnStateUpdata_A

  • 当状态A到B的过渡被触发的那一帧:
    执行了OnStateEnter_B
    执行了OnStateUpdate_A

  • 当向状态B过渡的过程中:
    每帧先执行OnStateUpdata_A,后执行OnStateUpdata_B

  • 当过渡被打断的那一帧:
    (Exit被触发的另一种情况,当指向该State的过渡被打断时触发)
    执行了OnStateUpdata_A
    执行了OnStateExit_B
    执行了OnStateEnter_C

  • 当向状态C过渡的过程中:
    每帧先执行OnStateUpdata_A,后执行OnStateUpdata_C

  • 当进入到状态C时的那一帧:
    执行了OnStateExit_A
    执行了OnStateUpdata_C

  • 当执行状态C时:
    每帧执行OnStateUpdata_C

总结:
我们不难发现在正常过渡,以及过渡打断的情况下,任意State中的三个状态方法都是能够形成闭环,在不同状态的切换中,保证都被执行的。结合对上面测试的理解,我们就可以将原先写在Mono脚本中的一些动画参数设置的方法,通过StateMechinBehaviour的三个状态方法来进行简洁,快速的实现。


image.png
int jumpHash = Animator.StringToHash("Jump");
//先预存哈希值

//Update中判定状态以及输入,进行Trigger设定
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
animatorStateInfo2 = animator.GetNextAnimatorStateInfo(0);

if (animatorStateInfo.shortNameHash == runStateHash || animatorStateInfo2.shortNameHash == runStateHash)
{
    //这里我们支持由Idle状态快速向Jump状态过渡,因此不止时CurrentState为RunState
    //在执行Idle向Run状态的过渡,NextState为RunState时就允许设置Trigger
    //我们也开启了 Idle->RunTree 针对NextState的打断
    //从而站立起跑的瞬间就可以进行冲刺翻身跳的过渡
    if (Input.GetMouseButtonDown(0))
    {
        animator.SetTrigger(jumpHash);
    }
}
else
{
    animator.ResetTrigger(jumpHash);
}

我们可以运用StateMechineBehaviour来实现上面的功能,将下面的脚本挂载在RunTree状态上即可。可以看到下面的代码要简洁许多,包括快速过渡的功能在内,下面的代码与上面的代码所实现的功能完全相同

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StateMechine : StateMachineBehaviour
{
        int jumpHash = Animator.StringToHash("Jump");

        override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            if (Input.GetMouseButtonDown(0))
            {
                 animator.SetTrigger(jumpHash);
            }
        }

    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.ResetTrigger(jumpHash);
    }
}
2.OnStateIK/OnStateMove的触发与细节

OnStateIK与OnStateMove,的触发细节相同,都是在这三个时期被调用:

  • 指向该状态的过渡中
  • 该状态运行时
  • 从该状态触发的过渡中

对于OnStateMove的作用效果:

  • 如果Mono脚本中不实现OnAnimatorMove,那么OnStateMove在触发时,都将覆盖掉Apply Root Motion的勾选与不勾选
  • 当执行过渡时,角色将受到所有被触发的OnStateMove共同作用
  • 如果脚本中实现OnAnimatorMove,角色将受到所有被触发的OnStateMove与脚本中的OnAnimatorMove共同作用

OnStateIK的作用效果是同所有触发的OnStateIK和Mono中的OnAnimatorIK一起作用于角色。通常我们只使用OnStateMove/OnAnimatorMove之一,OnStateIK/OnAnimatorIK之一来进行RootMotion,以及IK的控制,使用OnStateMove/OnStateIK时可通过传参判定状态,避免过渡状态下的共同作用。

3.挂载在Layer动画层/子动画组上的StateMechineBehavior

StateMechieBehavior还可以被挂载在动画层(动画组)上


image.png

此时StateMechineBehaviour的接口方法调用就变为了:

  • OnStateEnter:该动画层(组)(包括子动画组)中的任何一个State在Enter时调用
  • OnStateUpdate:该动画层(组)(包括子动画组)中的任何一个State在运行时调用
  • OnStateExit:该动画层(组)(包括子动画组)中的任何一个State在退出时调用
  • OnStateMove:完全相当于Mono脚本中OnAnimatorMove的作用,使用之前提到的RootMotion模式三,但仅针对这个动画层(组)运行时
  • OnStateIK:完全相当于Mono脚本中的OnAnimatorIK的作用,会在后面AnimatorIK中被一起提到,,但仅针对这个动画层(组)运行时

此时StateMechineBehaviour的作用范围是该动画层(组),以及所有子动画组中的状态。触发OnStateEnter/OnStateUpdate/OnStateExit,传入的AnimatorStateInfo会是对应状态的Info,注意使用分支逻辑。

OnStateMove的触发细节与作用效果与挂载在State上时的触发细节和作用效果类似。指向该动画层及子层的过渡中,该动画层及子动画层被运行时,从该动画层出发向父层级的过渡中,OnStateMove都会被触发。

如果Mono中不实现OnAnimatorMove,OnStateMove将覆盖Apply Root Motion的勾选与不勾选,并同所有被触发的OnStateMove共同作用于角色。或是同所有被触发的OnStateMove,与Mono脚本中实现的OnAnimatorMove一起作用于角色。

OnStateIK的触发细节与OnStateMove相同。作用效果和之前一样,同所有触发的OnStateIK和Mono中的OnAnimatorIK一起作用于角色。

四、位置预判SetTarget

测试效果,可以看到平地无阻挡情况下能够正确预判位置,放置圆环。但如果受到其它物理互动影响根节点(重力,碰撞),预判位置仍是理想的动画效果的位置。


主角即将到达某个位置时,生成一个圆环

我们可以使用位置预判,在执行一段动画的过程中,预判当NormalizedTime(百分比进程)到达某一时刻时,人物某一节点的位置和方位。相关方法:

animator.SetTarget(AvatarTarget, normalizedTime);
animator.targetPosition;//获取预判位置的属性
animator.targetRotation;//获取预判方位的属性

AvatarTarget是一个节点枚举类,包括:Body(重心),Root(根节点),Left/Right Hand(左右手),Left/Right Foot(左右脚)

注意!!! SetTarget和获取属性不能在同一时间被一起使用。SetTarget需要多帧的执行,进行运算,才能找到正确的位置和方位,如果获取时SetTarget还没有运算完成,那么将返回所在物体的Transform对应位置和方位。

并且这个方法只能在Apply Root Motion情况下,进行预判,原理是根据Clip中节点的运动进行位置和方位的计算,如果人物受到重力,或碰撞体阻拦,影响到了根节点的运动,那么预判位置就会与实际位置不符。

一段示例代码,在Update中

animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (animatorStateInfo.shortNameHash == jumpHash)
{
    
    if (setPos)
    {
        animator.SetTarget(AvatarTarget.Body, 0.44f);
        if (animatorStateInfo.normalizedTime > 0.2f)
        {
            setPos = false;
            Circle.position = animator.targetPosition;
            Circle.forward = transform.forward;     
        }
    }
}
else
{
    setPos = true;
}
四、Animation API
Play("ation 1" );//播放动画,传入参数为动画名字
Stop("ation 1");//停止动画,传入参数为动画名字
CrossFade("ation 1", 0.5f);//有过度的切换动画,传入参数(动画名字,过度时间)

相关文章

  • Unity 动画系列五 常用脚本API

    参考学习笔记 --- Unity动画系统[https://zhuanlan.zhihu.com/p/1050299...

  • Unity常用API

    转自Unity常用API— 奔跑的蜗牛儿 1、Event Function:事件函数 Reset() :被附加脚本...

  • 02脚本开发基础

    Unity组件开发 Unity脚本生命周期 脚本常用类 实例化预设体和父子关系

  • UnityWebRequest进度条设计(Unity取代WWW的

    这两天逛Unity api时,发现UnityWebRequest,官方文档介绍看这里。,脚本api看这里。WWW存...

  • Unity常用API脚本生命周期

    // 常用void Awake(){Debug.Log ("Awake 唤醒事件,只执行一次");}

  • Unity基础-脚本生命周期

    1.Unity脚本 和 C#脚本区别? unity脚本继承自MonoBehavior。 unity脚本不能new。...

  • Shader

    API Unity Shader 内置函数 Basic ★看看这个CGShader常用函数初探Surface Sh...

  • 内存管理(三)

    Q1:对于Handheld.PlayFullScreenMovie 这个Unity播放开场动画的API,会有内存问...

  • UIView 基础动画

    前面已经写过一篇了,关于UIView的动画,写此篇的目的是总结一下开发中常用的动画API,同时会写一系列iOS 开...

  • Unity3D常用API和功能

    前言 喜欢请点赞 目标阅读者:Unity新手 项目程序员 简单的前置:编程习惯 Unity常用API 按键Inpu...

网友评论

      本文标题:Unity 动画系列五 常用脚本API

      本文链接:https://www.haomeiwen.com/subject/qbkozltx.html