unity-小白学习Animator建立动画的状态切换


相信你们都学过Animator的一些东西,这里讲在建立状态切换的一些用法,让你状态切换看起来简洁,代码容易理解,不易出现bug

Animator窗口

首先是Animator窗口

刚开始只有Any state,Entry,Exit

Any state:任意状态切换,一般用于死亡状态切换,所以不使用Any state更能看清楚哪些状态之间能切换,哪些不能
Entry,默认动画人口,一般是ilde站立
Exit:动画退出出口,注意一定要加退出动画的状态,不然你可以会出现一直在播放某个动画,我刚开始没加,在代码写退出条件,这样状态一多,自己都看不懂当前的动画切换,而且容易卡在某一个动画上

例子:站立idle,跳跃jump,奔跑run,攻击attack这4个动画的切换

再提一下条件写在一个箭头里是满足所有条件,如果只需满足一个条件你要重新make transition图中就会变成多箭头的形式,一般状态切换为单箭头,退出动画为多箭头

以下是我建的状态切换图:

在这里插入图片描述
每个状态都要不勾选has exit time,以便不需要动画播放结束就可以及时切换

站立idel切换到run条件:在地面isGround为true跟向前速度forwardSpeed大于0.1即可

run到idle:在地面isGround为true跟向前速度forwardSpeed小于0.1即可

run退出动画条件:向前速度forwardSpeed小于0.1 | | isGround为false(跳跃状态)| | attack攻击状态,就是其它状态满足条件时来打断当前动画

idle退出动画条件:向前速度forwardSpeed大于0.1 | | isGround为false(跳跃状态)| | attack攻击状态

站立的3个动画

然后在idle我是create sub-state machine而来,我在创建了一个子状态机,通过设置idle和idlewait的值来实现站立状态切换
在这里插入图片描述
idle_A到idle_B的条件为idle为0 && idlewait(等待时间)大于5的时候切换
idle_B到idle_C的条件为idle为1 && idlewait(等待时间)大于5的时候切换
idle_C到idle_D的条件为idle为2 && idlewait(等待时间)大于5的时候切换
idle_D到idle_A的条件为idle为-1 && idlewait(等待时间)大于5的时候切换

退出条件都一样,前面讲了

要如何设置idle值的变化来播放动画?

双击idle_A动画,在Events,添加事件,在动画末尾添加事件OnIdleEnd,然后在脚本里写对于事件名的方法,在动画结束时将会自动调用
在这里插入图片描述
同理在idle_b,idle_c也添加OnIdleEnd事件,而最后的站立动画要切换为初始状态,我们在动画结尾添加OnIdleStart

public void OnIdleStart()
    {        
        animator.SetInteger("idle", -1);
        
        
    }
    private int b = 0;
    public void OnIdleEnd()
    {
        
        animator.SetInteger("idle", b % 3);
        b++;
        
    }

攻击的3个动画

attack参数是trigger通过鼠标点击触发,而normalizedTime是控制3个攻击动画的切换
run->attack和idle->attack:条件是通过attack触发切换
attack->run:向前速度forwardSpeed大于0.1
attack->idle:向前速度forwardSpeed小于0.1,这样没有鼠标点击就自动切换为站立状态,而不用写退出动画的条件

attack也是create sub-state machine创建而来,下面是3个攻击动画的状态机
在这里插入图片描述
atk2->atk3:attack&&normalizedTime>0.1&&normalizedTime<1
atk3->atk4:attack&&normalizedTime>0.4&&normalizedTime<1
atk4->atk1:attack&&normalizedTime>0.7&&normalizedTime<1

这3个动画取消勾选has exit time,而退出条件勾选has exit time,且设置退出时间为1,不用写退出条件,只要没点击就回到站立状态
在这里插入图片描述

但这里不是通过事件触发的,而是通过normalizedTime的变化来控制的,normalizedTime是当前动画的播放进度,我们要特殊处理让它置于0-1之间

下面是全部代码,看一下设置动画的部分即可

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

public class PlayerController : MonoBehaviour
{

   #region 字段
    public float maxMoveSpeed = 5;

    public float jumpspeed = 7;
    public float gravity = 15;

    public bool isGrouned = true;
    public CharacterController Character;
    //
    public Transform renderCamera;
    //人物角速度
    public float maxAngleSpeed = 1200;
    public float minAngleSpeed = 400;
    //人物加速度
    public float acceleratedSpeedd = 15;

    public float moveSpeed = 0;
    public Animator animator;


    private float verticalspeed = 0;
    private PlayerInput playerInput;

    private Vector3 move;
    //当前动画信息
    private AnimatorStateInfo currentStateInfo;
    //获取指定动画
    //private int idleHash = Animator.StringToHash("Idle_A");
    //private int runHash = Animator.StringToHash("Run");
    //private int jumpHash = Animator.StringToHash("Jump");
    private AudioSource audioSource;
    #endregion

    // Start is called before the first frame update
    private void Awake()
    {
        Character = transform.GetComponent<CharacterController>();
        playerInput = transform.GetComponent<PlayerInput>();
        animator = transform.GetComponent<Animator>();
        audioSource = transform.GetComponent<AudioSource>();
        
    }
    void Start()
    {
        audioSource.enabled = false;
        audioSource.volume = 0.3f;
        
    }

    // Update is called once per frame
    void Update()
    {
        //获取动画层
        currentStateInfo = animator.GetCurrentAnimatorStateInfo(0);
        CaculateMove();
        CaculateVerticalSpeed();
        CaculateForwardSpeed();
        Attack();
        //AnimationSprint();
        //CaculateRotation();
    }
    
    //private void OnAnimatorMove()
    //{
    //    CaculateMove();
    //}

    private void CaculateMove()
    {
        //float h = Input.GetAxis("Horizontal");
        //float v = Input.GetAxis("Vertical");

        //Vector3 move = new Vector3(h, 0, v);
        //动画对于上一帧的偏移
        //print("1");
        //move = animator.deltaPosition;

        move.Set(playerInput.Move.x, 0, playerInput.Move.y);
        move *= Time.deltaTime * moveSpeed;

        //相机跟随人物移动的方向
        move = renderCamera.TransformDirection(move);

        if (move.x != 0 || move.z != 0)
        {
            move.y = 0;
            //旋转速度,根据插值让移动速度影响旋转速度
            float turnSpeed = Mathf.Lerp(maxAngleSpeed, minAngleSpeed, moveSpeed / maxMoveSpeed) * Time.deltaTime;
            //RotateTowards从人物方向旋转到人物移动方向,最后一个参数为过度时间
            transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(move), turnSpeed);
        }
        move += Vector3.up * verticalspeed * Time.deltaTime;
        
        
        Character.Move(move);
        isGrouned = Character.isGrounded;
    }
    private void Attack()
    {
        if(isGrouned)
        {
          //获取动画进度,通过Repeat函数置于0-1
            animator.SetFloat("normalizedTime",Mathf.Repeat(currentStateInfo.normalizedTime,1)  );
            //重新设置鼠标点击
            animator.ResetTrigger("attack");
            if(playerInput.Attack)
            {
                print("攻击");
                t = 0;
                animator.SetFloat("idleWait", t);
                animator.SetTrigger("attack");
            }
            
        }
    }
    public void OnAttackStart()
    {
        
    }
    public void OnAttackEnd()
    {
        
    }
    private float t = 0;
    private void CaculateVerticalSpeed()
    {
        if(isGrouned)
        {
            verticalspeed = -gravity * 0.3f;
            if(playerInput.Jump)
            {
                t = 0;
                animator.SetFloat("idleWait", t);
                
                verticalspeed = jumpspeed;
                isGrouned = false;
            }
        }
        else
        {
            
            verticalspeed -= gravity * Time.deltaTime;
        }
        if (verticalspeed == 0)
            isGrouned = true;
        animator.SetBool("isGround", isGrouned);
        
    }
    

    //人物在移动时速度的过度在0-5之间
    private void CaculateForwardSpeed()
    {
        moveSpeed = Mathf.MoveTowards(moveSpeed, maxMoveSpeed 
            * playerInput.Move.normalized.magnitude, acceleratedSpeedd * Time.deltaTime);
        if (playerInput.Move.x == 0 && playerInput.Move.y == 0)
        {
            t += Time.deltaTime;
            audioSource.enabled = false;            
            if(t>5)
                animator.SetFloat("idleWait", t);
            animator.SetFloat("forwardSpeed", 0.05f);
            
        }
        else
        {
            t = 0;            
            audioSource.enabled = true;
            animator.SetFloat("idleWait", t);            
            animator.SetFloat("forwardSpeed", moveSpeed);
        }
            
    }

    //private void AnimationSprint()
    //{
    //    if (currentStateInfo.shortNameHash == idleHash)
    //        print("当前播发站立动画");
    //    else if (currentStateInfo.shortNameHash == runHash)
    //        print("当前播放跑步动画");
    //    else if (currentStateInfo.shortNameHash == jumpHash)
    //        print("当前播放跳跃动画");
    //}
    //private void CaculateRotation()
    //{
    //    if (playerInput.Move.x != 0 || playerInput.Move.y != 0)
    //    {
    //        Vector3 targetDirection = renderCamera.TransformDirection
    //            (new Vector3(playerInput.Move.x, 0, playerInput.Move.y));
    //        targetDirection.y = 0;

    //        float turnSpeed = Mathf.Lerp(maxAngleSpeed, minAngleSpeed, moveSpeed / maxMoveSpeed) * Time.deltaTime;
    //        transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(targetDirection), turnSpeed);
    //    }
    //}

    public void OnIdleStart()
    {
        
        
        animator.SetInteger("idle", -1);
        
        
    }
    private int b = 0;
    public void OnIdleEnd()
    {
        
        animator.SetInteger("idle", b % 3);
        b++;
        
    }
    
}

如果状态切换上出现is not compatible withcondition type的错误?

只需将状态切换的箭头删除再重新新建即可

  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值