Animator
- Animator窗口
- 站立的3个动画
- 攻击的3个动画
相信你们都学过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的错误?
只需将状态切换的箭头删除再重新新建即可