抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

教程来自b站阿严:https://www.bilibili.com/video/BV1rL4y1W7KH


成果展示:

【[unity]平台跳跃类demo】 https://www.bilibili.com/video/BV1AK411Q7ry?share_source=copy_web&vd_source=644eac695af0d52dcffdec474d1423b1

一 项目的创建和管理插件

image-20220708151216150

删除不需要的插件,安装需要的插件,删除后插件内容:

image-20220708151839520

1.1 安装:

  • Cinemachine,虚拟相机插件。
  • Post Processing,后处理插件。
  • Input System,新的输入系统插件。

1.2 安装完成后图:

image-20220708152529919

1.3 导入资源包

这里我使用的是自己在unity商店买的资源包,替换了unity酱,想要做点不一样的。

image-20220727162106523


二 状态机系统

创建接口文件和状态机类

image-20220708172046551

image-20220708172412575

2.1 Istate接口

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

public interface IState
{
void Enter();//状态进入

void Exit();//状态退出

void LogicUpdate();//状态逻辑更新

void PhysicUpdate();//状态物理更新
}

2.2 StateMachine类

作用:

  • 持有所有状态,并且进行管理和切换;
  • 负责当前状态的更新.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StateMachine : MonoBehaviour
{
//1、持有所有状态,并且进行管理和切换;2、负责当前状态的更新
IState currentState;
void Update()
{
//更新状态逻辑
currentState.LogicUpdate();
}
void FixedUpdate()
{
//更新物理
currentState.PhysicUpdate();
}
protected void SwitchOn(IState newState)
{//状态开启
//新状态赋值给当前状态,然后启动
currentState = newState;
currentState.Enter();
}
public void SwitchState(IState newState)
{//切换状态
//先关闭状态,然后新状态赋值给当前状态,启动
currentState.Exit();
SwitchOn(newState);
}
}


三 扩展状态机系统

创建PlayerStates(玩家状态)并初始化

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

public class PlayerState : ScriptableObject, IState
{//玩家状态类
Animator animator;//动画器
PlayerStateMachine stateMachine;//玩家状态机类

public void Initialize(Animator animator, PlayerStateMachine stateMachine)
{
this.animator = animator;
this.stateMachine = stateMachine;
}

public virtual void Enter()
{

}

public virtual void Exit()
{

}

public virtual void LogicUpdate()
{

}

public virtual void PhysicUpdate()
{

}
}

PlayerStatesMachine(玩家状态机)

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

public class PlayerStateMachine : StateMachine
{//玩家状态机
Animator animator;
private void Awake()
{
animator = GetComponentInChildren<Animator>();

//可以在此初始化 玩家状态
}
}

3.1 状态机系统继承关系图

image-20220708193345419

3.2 使用两种方法实现角色动画

3.2.1、常规方法(不使用状态机)

构建基础状态机系统,创建动画控制器

image-20220715153516317

拖到玩家对象

image-20220715153636233

将动画拖入控制器

image-20220715153742399

更改默认状态

image-20220715154429486

然后在player对象添加玩家控制器脚本

image-20220715155527938

需求分析:

  • 按下A/D键时播放跑步动画
  • 松开按键回到空闲动画

实现方式:

  • 获取键盘的输入信号(Input System)
  • 播放特定动画(Animator)

PlayerController代码如下:

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

public class PlayerController : MonoBehaviour
{
Animator animator;
private void Awake()
{
animator = GetComponentInChildren<Animator>();//获取动画控制组件
}
void Update()
{
//按住当前键盘的a或者d键
//if(Input.GetKey(KeyCode.A)|| Input.GetKey(KeyCode.D))
if (Keyboard.current.aKey.isPressed || Keyboard.current.dKey.isPressed)
{
animator.Play("Run");//播放动画器中特定动画
}
else//松开按键时
{
animator.Play("Idle");
}
}
}

并且同时更改动画器中动画的名字,以匹配代码中的命名

image-20220715161313196

全部改完效果如图:

image-20220715161513401

3.2.2、通过状态机实现动画状态切换

首先将状态机脚本添加到玩家上

image-20220715162041929

其次在Player State文件夹中创建idle和run状态脚本

image-20220719030021036

皆继承于PlayerState

[CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Idle", fileName = "PlayerState_Idle")]

这条代码将代码暴露在编辑器中,可在文件夹中创建可程序化对象文件

image-20220715162722895

image-20220715163129842

举例PlayerState_Idle代码如下:

重写状态函数,更新状态

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

[CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Idle", fileName = "PlayerState_Idle")]
public class PlayerState_Idle : PlayerState
{
public override void Enter()
{
animator.Play("Idle");
}
public override void LogicUpdate()
{
if (Keyboard.current.aKey.isPressed || Keyboard.current.dKey.isPressed)
{
stateMachine.SwitchState(stateMachine.runState);
}
}
}

PlayerState_Idle脚本代码同理。

状态机模式将不同状态分开来处理,这个模式中只需要思考,当前状态如何切换到下一状态,而不再需要同时考虑另一个状态的逻辑问题。

(例如需要落地后才可起跳,常规方法需要使用很多Boolean值来判断状态,而状态机模式只需要在各自状态中实现切换即可)

image-20220719042545564

3.3 状态机模式优缺点

优点

  • 逻辑分开到各个状态中处理,不需要考虑状态之间的约束,让思路更清楚。
  • 代码性能提升

缺点

  • 文件数量增加
  • 代码重复

3.4 继续完善状态机功能

打开玩家状态机脚本PlayerStateMachine

目前所有新状态都需要新建初始化,是否可以声明一个鸡和然后将所有状态都扔进去呢?当然可以。

首先修改玩家状态机类的代码,(注释中是原代码)

我们新建一个states数组,通过遍历获取玩家具体状态。

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

public class PlayerStateMachine : StateMachine
{//玩家状态机
Animator animator;
//public PlayerState_Idle idleState;
//public PlayerState_Run runState;

[SerializeField] PlayerState[] states;//持有所有状态

private void Awake()
{
animator = GetComponentInChildren<Animator>();

//可以在此初始化 玩家状态
//idleState.Initialize(animator, this);
//runState.Initialize(animator, this);
foreach (PlayerState state in states)
{
state.Initialize(animator, this);
}
}
private void Start()
{
SwitchOn(【idleState】);//默认idle,【idleState】报错,需要通过字典
}
}

这么做idleState会出现报错,

这时需要在父类创建字典,声明一个键为System.Type值为IState类型的字典

然后在玩家状态机类PlayerStateMachine中的状态遍历循环中,给它赋值。

最后就可以通过键来获取状态。以下是父类,也就是状态+6机类StateMachine中代码:

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

public class StateMachine : MonoBehaviour
{
//1、持有所有状态,并且进行管理和切换;2、负责当前状态的更新
IState currentState;

//新建一个 键为System.Type 值为IState类型的字典
protected Dictionary<System.Type, IState> stateTbale;

void Update()
{
//更新状态逻辑
currentState.LogicUpdate();
}
void FixedUpdate()
{
//更新物理
currentState.PhysicUpdate();
}
protected void SwitchOn(IState newState)
{//状态开启
//新状态赋值给当前状态,然后启动
currentState = newState;
currentState.Enter();
}
public void SwitchState(IState newState)
{//切换状态
//先关闭状态,然后新状态赋值给当前状态,启动
currentState.Exit();
SwitchOn(newState);
}
}

接着在玩家状态机类awake()中初始化,更改后的玩家状态机类代码如下:

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

public class PlayerStateMachine : StateMachine
{//玩家状态机
Animator animator;
//public PlayerState_Idle idleState;
//public PlayerState_Run runState;

[SerializeField] PlayerState[] states;//持有所有状态
private void Awake()
{
//获取动画
animator = GetComponentInChildren<Animator>();
//字典长度为状态数组长度
stateTbale = new Dictionary<System.Type, IState>(states.Length);
//可以在此初始化 玩家状态
//idleState.Initialize(animator, this);
//runState.Initialize(animator, this);
foreach (PlayerState state in states)
{
state.Initialize(animator, this);
stateTbale.Add(state.GetType(), state);//朝字典添加数据
}
}
private void Start()
{
Debug.Log(typeof(PlayerState_Idle));
//在字典中传入PlayerState_Idle的类型
//默认idle
SwitchOn(stateTbale[typeof(PlayerState_Idle)]);
}
}

接着会遇到两个报错:PlayerState_Run和PlayerState_Idle类中的状态切换里的变量不存在了。

我们需要回到状态机主类StateMachine,重载状态切换函数SwitchState。将变量类型改为字典需要的键(System.Type类)对比如下:

public void SwitchState(IState newState)
{//切换状态
//先关闭状态,然后新状态赋值给当前状态,启动
currentState.Exit();
SwitchOn(newState);
}
public void SwitchState(System.Type newStateType)
{//切换状态
//通过字典获取状态,再调用上面那个SwitchState();
SwitchState(stateTbale[newStateType]);
}

这样在报错的PlayerState_Run和PlayerState_Idle中修改相应的变量即可:

stateMachine.SwitchState(runState);

改成了

stateMachine.SwitchState(typeof(PlayerState_Run));

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

[CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Idle", fileName = "PlayerState_Idle")]
public class PlayerState_Idle : PlayerState
{
public override void Enter()
{
animator.Play("Idle");
}
public override void LogicUpdate()
{
if (Keyboard.current.aKey.isPressed || Keyboard.current.dKey.isPressed)
{
stateMachine.SwitchState(typeof(PlayerState_Run));
}
}
}


四 Input System输入系统

image-20220720175109099

如果需要某个功能对应不同按键都可触发,使用InputSystem会更加方便。不使用输入系统的话需要多个判断语句实现。

4.1 新建Input Actions

命名为PlayerInputActions,玩家输入动作

image-20220722155719378

4.1.1、创建移动动作

新建GamePlay动作表,重命名动作名为Axes 轴。右边动作类型选value,值类型选二维向量

image-20220722160205417

删除默认的按键,添加预设的上下左右

image-20220727162430682

通过listen绑定按键

image-20220727162634092

手柄和键盘的模式都改成digital,这样手柄将和键盘一样xy取值只有0,-1,1

image-20220727163045286

4.1.2、创建跳跃动作

image-20220727163504217

记得保存资产

image-20220727163532695

4.1.3、生成c#文件

更改文件位置

image-20220727163837612

4.2 初始化玩家输入

4.2.1、新建脚本PlayerInput 玩家输入类,并且添加到player对象中。

using UnityEngine;
public class PlayerInput : MonoBehaviour
{//玩家输入类

PlayerInputActions playerInputAction;//inputsystem生成的c#文件

public bool PlayerMove => AxisX != 0f;//通过判断x轴是否为0,判断是否移动

//获取轴的值 x,y
Vector2 axes => playerInputAction.GamePlay.Axes.ReadValue<Vector2>();
public float AxisX => axes.x;

//WasPressedThisFrame()是否按下
public bool playerjump => playerInputAction.GamePlay.Jump.WasPressedThisFrame();

//WasReleasedThisFrame()是否松开按键
public bool playerstopJump => playerInputAction.GamePlay.Jump.WasReleasedThisFrame();

private void Awake()
{
playerInputAction = new PlayerInputActions();//初始化
}
public void EnableGameplayInputs()
{
//启用gameplay动作表(inputsystem中自带的方法,通过.GamePlay.Enable()启动)
playerInputAction.GamePlay.Enable();
Cursor.lockState = CursorLockMode.Locked;//鼠标设置为锁定模式
}
}

4.2.2、更改PlayerState 玩家状态脚本,加入玩家输入 初始化

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerState : ScriptableObject, IState
{//玩家状态类
protected Animator animator;//动画器
protected PlayerInput input;//玩家输入类
protected PlayerStateMachine stateMachine;//玩家状态机类
public void Initialize(Animator animator, PlayerInput input, PlayerStateMachine stateMachine)
{
//初始化状态动画,玩家输入类,玩家状态机
this.animator = animator;
this.input = input;
this.stateMachine = stateMachine;
}
public virtual void Enter()
{
}
public virtual void Exit()
{
}
public virtual void LogicUpdate()
{
}
public virtual void PhysicUpdate()
{
}
}

4.2.3、修改PlayerState_Idle和PlayerState_Run脚本

因为在PlayerInput中,新建了一个变量表示玩家是否移动。原来判断玩家是否在移动的语句优化如下:

旧版通过检测按键a或者d判断:

if (Keyboard.current.aKey.isPressed || Keyboard.current.dKey.isPressed)

新版判断输入事件x轴是否为0,得出玩家是否正移动:

if (input.PlayerMove)

4.2.4、在PlayerStateMachine 玩家状态机类中初始化玩家输入

部分代码如下:

PlayerInput input;//玩家输入
private void Awake()
{
//获取动画
animator = GetComponentInChildren<Animator>();
//获取玩家输入
input = GetComponent<PlayerInput>();

//字典长度为状态数组长度
stateTbale = new Dictionary<System.Type, IState>(states.Length);
//可以在此初始化 玩家状态
//idleState.Initialize(animator, this);
//runState.Initialize(animator, this);
foreach (PlayerState state in states)
{
state.Initialize(animator, input, this);
stateTbale.Add(state.GetType(), state);//朝字典添加数据
}
}

4.2.5、在PlayerController 玩家控制器类中添加玩家输入,并且启用动作表

public class PlayerController : MonoBehaviour
{
//玩家控制器
PlayerInput input;//玩家输入类
private void Awake()
{
input = GetComponent<PlayerInput>();//获取对象
}
private void Start()
{
input.EnableGameplayInputs();//启用动作表
}
#region 常规方法(不使用状态机)...
}

五 玩家移动、转向

5.1 玩家移动功能实现

分析:通过刚体模拟物体运动,需要获取player身上的刚体组件。

将玩家移动功能写在PlayerController玩家控制器脚本中。

新建刚体变量,获取实例,创建了三个改变刚体速度的函数

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{//玩家控制器
PlayerInput input;//玩家输入类
Rigidbody rigidBody;//刚体类引用变量
private void Awake()
{
input = GetComponent<PlayerInput>();//获取对象实例
rigidBody = GetComponent<Rigidbody>();//获取刚体组件实例
}
private void Start()
{
input.EnableGameplayInputs();//启用动作表
}
public void SetVelocity(Vector3 velocity)
{
//直接修改刚体速度
rigidBody.velocity = velocity;
}
public void SetVelocityX(float velocityX)
{
//将刚体x轴的速度设置为参数的值,y不变。左右移动
rigidBody.velocity = new Vector3(velocityX, rigidBody.velocity.y);
}
public void SetVelocityY(float velocityY)
{
//将刚体y轴的速度设置为参数的值,x不变。跳跃或者下落
rigidBody.velocity = new Vector3(rigidBody.velocity.x, velocityY);
}

#region 常规方法(不使用状态机)...
}

PlayerState 玩家状态类中添加玩家控制器类,并初始化(部分代码)

protected PlayerController player;//玩家控制器

public void Initialize(PlayerController player, Animator animator, PlayerInput input, PlayerStateMachine stateMachine)
{
//初始化玩家控制器,状态动画,玩家输入类,玩家状态机
this.player = player;
this.animator = animator;
this.input = input;
this.stateMachine = stateMachine;
}

PlayerStateMachine 玩家状态机类一起更新

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

public class PlayerStateMachine : StateMachine
{//玩家状态机

Animator animator;//动画
PlayerInput input;//玩家输入
PlayerController playerController;//玩家状态机

[SerializeField] PlayerState[] states;//持有所有状态
private void Awake()
{
//获取动画
animator = GetComponentInChildren<Animator>();
//获取玩家输入
input = GetComponent<PlayerInput>();
//获取玩家控制器
playerController = GetComponent<PlayerController>();

//字典长度为状态数组长度
stateTbale = new Dictionary<System.Type, IState>(states.Length);
//可以在此初始化 玩家状态
//idleState.Initialize(animator, this);
//runState.Initialize(animator, this);
foreach (PlayerState state in states)
{
state.Initialize(playerController, animator, input, this);
stateTbale.Add(state.GetType(), state);//朝字典添加数据
}
}
private void Start()
{

//Debug.Log(typeof(PlayerState_Idle));
//在字典中传入PlayerState_Idle的类型
//默认idle
SwitchOn(stateTbale[typeof(PlayerState_Idle)]);
}
}

PlayerState_Run 跑步状态脚本中**重写PhysicUpdate()**方法实现玩家移动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Run", fileName = "PlayerState_Run")]
public class PlayerState_Run : PlayerState
{
[SerializeField] float runSpeed = 5f;//跑步速度

public override void Enter()
{
animator.Play("Run");
}
public override void LogicUpdate()
{
if (!input.PlayerMove)
{//if玩家没移动输入 则切换状态为idle
stateMachine.SwitchState(typeof(PlayerState_Idle));
}
}
public override void PhysicUpdate()
{
player.SetVelocityX(runSpeed);
}
}

新建变量runSpeed 跑步速度 将它序列化,已暴露在编辑器中,至此移动功能实现。

image-20220729025838477

5.2 玩家转向

5.2.1、通过镜像翻转改变玩家朝向

首先在PlayerController 玩家控制器中增加Move(),

通过更改Player的localScale的属性x轴正负(对应axisX轴)值,同步修改角色朝向

左右移动功能使用速度x方向

image-20220729034552855

然后修改PlayerState_Run中**PhysicUpdate()**函数

player.SetVelocityX(runSpeed);改为player.Move(runSpeed);

新的比旧的多了转向功能。

5.5.2、停止移动

**PlayerState_Idle类Enter()**中新加一条代码player.SetVelocityX(0f);x轴速度归零

image-20220729035245706

5.3 玩家加速和减速

5.3.1、实现玩家加速

玩家控制器中添加一个方法,获取玩家移速。

image-20220803155755085

打开玩家状态类声明浮点型变量currentSpeed,当前速度

image-20220803170504220

修改PlayerState_Run脚本,新增变量acceration加速度,Enter()中获取当前速度,LogicUpdate()中新增缓慢加速的算法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Run", fileName = "PlayerState_Run")]
public class PlayerState_Run : PlayerState
{
[SerializeField] float runSpeed = 5f;//跑步速度
[SerializeField] float acceration = 10f;//加速度
public override void Enter()
{
animator.Play("Run");
currentSpeed = player.MoveSpeed;//获取当前速度
}
public override void LogicUpdate()
{
if (!input.PlayerMove)
{//if玩家没移动输入 则切换状态为idle
stateMachine.SwitchState(typeof(PlayerState_Idle));
}
//从当前速度到指定速度之间随着acceration加速度增加。
currentSpeed = Mathf.MoveTowards(currentSpeed, runSpeed, acceration * Time.deltaTime);
}
public override void PhysicUpdate()
{
//player.Move(runSpeed);
player.Move(currentSpeed);
}
}

5.3.2、实现玩家减速

修改PlayerState_Idle脚本,新增变量deceleration表示减速加速度,Enter()中记录当前速度,LogicUpdate()中新增缓慢减速算法。重写PhysicUpdate()方法设置减速。

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

[CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Idle", fileName = "PlayerState_Idle")]
public class PlayerState_Idle : PlayerState
{
[SerializeField] float deceleration = 5f;
public override void Enter()
{
animator.Play("Idle");//播放动画器里的idle动画
//player.SetVelocityX(0f);//x轴速度归零
currentSpeed = player.MoveSpeed;//记录当前速度
}
public override void LogicUpdate()
{
if (input.PlayerMove)
{//判断是否移动
stateMachine.SwitchState(typeof(PlayerState_Run));
}
//减速
currentSpeed = Mathf.MoveTowards(currentSpeed, 0, deceleration * Time.deltaTime);
}
public override void PhysicUpdate()
{
//因为已停止移动 所以不能用Move(),并且需要获取玩家朝向layer.transform.localScale.x
player.SetVelocityX(currentSpeed * player.transform.localScale.x);
}
}

5.4 相机跟随玩家

使用Cinemachine虚拟相机插件实现。

新建虚拟相机命名为Virtual Camera Player Follow

image-20220803182643175

拖到Cameras下。

image-20220804124847239

提前重置两个相机位置,然后将player拖到follow槽中

image-20220804125015412

取消勾选Aim选项,这个是用作瞄准用的。

image-20220804125321555

将body机身改成Framing Transposer取景器

image-20220804131918252

调试参数,选中这个,在试玩时做的操作也会保存

image-20220804132030035

image-20220804132047150


六 优化改进动画播放

PlayerState玩家状态脚本中新增三条属性

image-20220804135546469

分别用来获取状态动画名称、动画切换时间、声明int类型哈希值。

onenable()中,将获取的字符串转哈希值(哈希值占用资源少)。

Enter()中,使用animator.crossFade()过渡动画,淡入淡出效果

参数1:字符串或者哈希值,参数2:持续时间。

image-20220804135713005

玩家状态PlayerState_Idle和PlayerState_Run中的animator.Play()改为base.Enter();继承父类

回到编辑器中只需要在可视化脚本中输入动画名即可.

image-20220804140201463


七 玩家跳跃功能

7.1 地面检测

要实现跳跃功能,首先要检测地面。

在Player对象下的Ground Detector空对象中添加新的脚本:PlayerGroundDetector 玩家地面检测器

代码如下:

using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerGroundDetector : MonoBehaviour
{
[SerializeField] float detectionRadius = 0.1f;//检测半径
[SerializeField] LayerMask groundLayer;//层级
Collider[] colliders = new Collider[1];//碰撞数组

//Physics.OverlapSphereNonAlloc 检测碰撞个数,不触发回收机制 判断如不为0则返回true
//IsGrounded:可以判断玩家是否在地面
public bool IsGrounded => Physics.OverlapSphereNonAlloc(transform.position, detectionRadius, colliders,groundLayer) != 0;

//OnDrawGizmosSelected():系统提供的方法,不是自己建的。
void OnDrawGizmosSelected()
{//在编辑器中展示 方便调试
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, detectionRadius);
}
}

PlayerController 玩家控制器脚本中新增代码,以使用玩家地面检测器的参数.

新增内容:

PlayerGroundDetector groundDetector;//玩家地面检测

//获取地面检测结果
public bool IsGrounded => groundDetector.IsGrounded;//true 为已接触

//判断玩家是否为正在下落,下落时刚体的y速度为负数,并且需要未落地.
public bool IsFalling => rigidBody.velocity.y < 0f && !IsGrounded;

7.2 检测当前播放动画是否完成

需要获取:1.动画播放开启的时间;2.当前状态持续时长;3.动画自身播放时长

打开玩家状态脚本 PlayerState,新增代码.

float stateStartTime;//状态开始时间

//动画是否播放完毕,通过判断 [当前状态的持续时间]是否大于等于[动画本身长度],若大于则动画结束.
protected bool IsAnimationFinished => StateDuration >= animator.GetCurrentAnimatorStateInfo(0).length;

//当前状态的持续时间,当前时间减去状态开始时间.
protected float StateDuration => Time.time - stateStartTime;

Enter()中给stateStartTime赋值

public virtual void Enter()
{//animator.CrossFade()交叉淡化函数,可传入哈希值或者string
animator.CrossFade(stateHash, transitionDuration);
stateStartTime = Time.time;//记录下状态开始时间
}

7.3 玩家跳跃

新建三个状态类PlayerState_Fall,PlayerState_JumpUp,PlayerState_Land

玩家按下跳跃键,并且玩家在地面上

评论