Unity 动画系列五 常用脚本API

2021-11-30  本文已影响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 了 !!

So着重看了下SetTrigger。

image.png

解决办法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时:

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

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

三、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中有一些预定义的事件方法:

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

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

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

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

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

Case 1:
我们抽象出

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

Case 2:
我们抽象出

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

总结:
我们不难发现在正常过渡,以及过渡打断的情况下,任意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的作用效果:

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

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

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


image.png

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

此时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);//有过度的切换动画,传入参数(动画名字,过度时间)
上一篇下一篇

猜你喜欢

热点阅读