github: https://github.com/lucaschen1993/PRG_DEMO
前言
在之前写状态机文章的时候介绍过,基于事件与委托实现的状态机。这篇文章将介绍状态机的使用,以及人物动画的实现,以及状态的切换。
角色 Animator Controller
我自己找了一个资源包是带动画的,但是要自己添加一个Animator Contoller,然后自己给这个动画控制器添加动画,这里我已经做好了,,废话不多说,直接上图。
这里的Attack状态我使用了子状态机,因为攻击动画不止一个,我又不想整个Base Layer看起来太凌乱,就使用了子状态机(Sub_stateMachine)。
另外的Move我使用了混合树,为了通过计算EasyTouch的摇杆的轴值来控制动画播放走还是跑。
这里有5个参数,MoveFactor是控制就是上面说的,控制角色播放跑还是走的动画,因为使用混合树,可以混合好walk与run的动画。Death是控制死亡动画播放的Trigger,AttackFactor是控制播放哪个攻击动画,ToIdle是回到Idle动画。Jump暂时没有做,但是方法也是一样的,先把Move Death,Attack这些比较重要的先做出来吧。
角色的状态控制以及动画控制
这里开始介绍控制角色的代码,先贴一些Player.cs的参数
public delegate void PlayerStateChange(AllCharacter.StateType stateType);
public event PlayerStateChange StateChange;
public int PlayerHp;
public float AtkCoolDown;
private float attackCoolDownTime;
private AllCharacter.StateType playerType;
public FiniteStateMachine FsMachine;
public ETCJoystick MoveJoystick;
private Animator _anim;
private NavMeshAgent _nav;
delegate与event是角色状态切换的事件与委托;AtkCoolDown与attackCoolDownTime用于制作攻击间隔;MoveJoystick是为了获取EasyTouch的摇杆,为了得到摇杆轴值。
在初始化Start的时候设置好角色的初始值,
void Start()
{
//设置角色的初始状态
PlayerHp = 100;
_anim = GetComponentInChildren<Animator>();
_nav = GetComponent<NavMeshAgent>();
EnterStand();
playerType = FsMachine.CurBaseState.GetStateType();
attackCoolDownTime = 0;
AtkCoolDown = 2.5f;
}
接着在Update里面是状态控制,因为有些参数是要在某些状态内才计算的,所以我在这里使用了switch做状态判断。但是不控制状态的切换。例如攻击冷却间隔只在攻击状态下才会进行计算。由于角色挂了,就啥都不能做了,所以我就把角色的死亡放在switch外。(不过突然觉得这个不是很好,得找个时间修改一下)
void Update ()
{
FsMachine.OnUpdate();
if (IsPlayerDead())
{
StateChange(StateType.STATE_DEAD);
}
switch (playerType)
{
case StateType.STATE_ATTACK:
attackCoolDownTime += Time.deltaTime;
break;
case StateType.STATE_STAND:
break;
case StateType.STATE_DEAD:
break;
case StateType.STATE_MOVE:
break;
}
}
这里使用的EasyTouch按钮通过ETCHolder.cs控制,我就不详细讲了,直接看代码就行。
ETCHolder.cs
using System.Collections;
using System.Collections.Generic;
using HedgehogTeam.EasyTouch;
using UnityEngine;
public class ETCHolder : MonoBehaviour
{
private Player _player;
// Use this for initialization
void Start ()
{
_player = GameObject.Find("Player").GetComponent<Player>();
}
// Update is called once per frame
void Update () {
}
public void OnMoveStart()
{
_player.StickMove();
}
public void OnTouchUp()
{
//松开JoyStick,回到Stand状态,Idle
_player.EnterStand();
}
public void OnAttackDown()
{
_player.DownAttack();
}
}
在这里是直接调用Player里面的方法进行状态切换的,而状态的切换都是通过事件StateChange进行切换的
public void StickMove()
{
//进入移动状态
StateChange(AllCharacter.StateType.STATE_MOVE);
}
public void DownAttack()
{
//进入攻击状态
StateChange(AllCharacter.StateType.STATE_ATTACK);
}
public void EnterStand()
{
//进入Stand状态,Idle
StateChange(AllCharacter.StateType.STATE_STAND);
}
接下讲一下获取Joystick的轴值以及用该轴值控制动画的播放。先贴一部分代码
//获取ETCJoystick轴值
public Vector3 GetJoystickAxis()
{
float h = MoveJoystick.axisX.axisValue;
float v = MoveJoystick.axisY.axisValue;
return new Vector3(h,0,v);
}
public void SetMoveFactor(float factor)
{
_anim.SetFloat("MoveFactor",factor);
}
这里直接通过Joystick的axisX.axisValue、axisY.axisValue就可以获取轴值了,返回的是一个Vector3的向量,接着通过这个返回值来计算其长度再赋值给moveFactor。
MoveState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveState : BaseState
{
private readonly Player _player;
public MoveState(Player player)
{
this._player = player;
}
public override AllCharacter.StateType GetStateType()
{
return AllCharacter.StateType.STATE_MOVE;
}
public override void EnterState(FiniteStateMachine fsMachine, BaseState preState)
{
if (preState != null)
{
//Debug.Log("Enter the MoveState the preState is:" + preState.GetStateType());
}
else
{
//Debug.Log("Enter the MoveState");
}
_player.SetType(AllCharacter.StateType.STATE_MOVE);
//Debug.Log(_player.CurBaseState);
}
public override void UpdateState()
{
Vector3 v3 = _player.GetJoystickAxis();
float moveFactor = Vector3.Magnitude(v3);
_player.SetMoveFactor(moveFactor);
//Debug.Log("I'm Moving.");
}
public override void ExitState(BaseState preState)
{
_player.SetMoveFactor(0f);
//Debug.Log("Exit Move State. " + preState.GetStateType());
}
public override void InputHandle()
{
}
}
这样就可以通过轴值来控制动画的播放了。当摇杆偏移量小的时候是播放walk动画,当偏移量拉满的时候是播放run动画,当该偏移量在这之间,则是播放run与walk的混合动画。
接着是Attack的状态,以及攻击间隔的制作。
我们在Player的update中,已经计算了attackCoolDownTime,每进行一次攻击,就要重置一次攻击冷却时间;在计算攻击冷却时间的时候需要判断,攻击间隔是否大于默认的攻击冷却时间AtkCoolDown,接着能攻击了,就调用Attack。
//随机攻击动画
#region RandomAttack_animation
public void ResetAttackCoolDown()
{
attackCoolDownTime = 0;
}
public bool CanAttack()
{
if (attackCoolDownTime > AtkCoolDown)
{
ResetAttackCoolDown();
return true;
}
return false;
}
public void Attack()
{
StartCoroutine(AttarCoroutine());
}
private IEnumerator AttarCoroutine()
{
int atkFactor = Random.Range(1, 3);
_anim.SetInteger("AttackFactor", atkFactor);
yield return null;
_anim.SetInteger("AttackFactor", 0);
}
public void StopAttack()
{
_anim.SetInteger("AttackFactor", 0);
}
AttackState.cs
using System.Collections;
using System.Collections.Generic;
using BehaviorDesigner.Runtime.Tasks.Basic.Math;
using UnityEngine;
public class AttackState : BaseState
{
private readonly Player _player;
public AttackState(Player player)
{
//向状态机发送事件,请求转换状态
this._player = player;
}
public override AllCharacter.StateType GetStateType()
{
return AllCharacter.StateType.STATE_ATTACK;
}
public override void EnterState(FiniteStateMachine fsMachine, BaseState preState)
{
if (preState != null)
{
Debug.Log("Enter the AttackState the preState is:" + preState.GetStateType());
}
else
{
Debug.Log("Enter the AttackState");
}
_player.SetType(AllCharacter.StateType.STATE_ATTACK);
_player.ResetAttackCoolDown();
_player.Attack();
//Debug.Log(_player.CurBaseState);
}
public override void UpdateState()
{
//战斗状态不间断平A
//Debug.Log("I'm in Battle Mode .");
bool canAttack = _player.CanAttack();
//攻击间隔
if (canAttack)
{
_player.Attack();
}
}
public override void ExitState(BaseState preState)
{
_player.StopAttack();
_player.ResetAttackCoolDown();
Debug.Log("Exit Attack State. " + preState.GetStateType());
}
public override void InputHandle()
{
}
}
在进入攻击状态时要先执行一次攻击,不然就要等默认攻击冷却过后才能进行一次攻击,每次退出状态的时候攻击冷却时间清零。防止下一次攻击的时候该时间混乱。
这样,人物的状态切换动画制作就已经做完啦,给个成品gif。
总结
其实这里人物的动画以及状态切换是比较简单的,因为逻辑并不复杂。看上面的代码其实很清晰就清楚,哪个状态在干啥,通过哪个按键切换到哪个状态。下一篇将介绍一下用状态机制作的游戏AI。
ps:文章没办法面面俱到,所以有啥问题尽管私信我,我会一一回答。