角色的待机动画播放本身很简单,但是需要打通动画、状态的逻辑,并且建立一个基本的角色结构。
角色待机状态
玩家的待机动画在其处于待机状态时播放,为此需要为玩家创建一个待机状态脚本来进行控制,同时玩家有可能会有多个状态,为了方便进行角色不同状态的切换和逻辑的复用,位玩家单独创建一个状态基类。
状态作为一种抽象的逻辑,表现为实际功能就是进入,离开,状态中的方法执行,使用程序框架中的状态机来对PlayerState进行管理,在此做简单解释,框架中的状态机主要有两部分组成。
- StateBase类,广义上所有状态上的基类,包含状态的OnEnter,OnExit,Update方法,是模板。
- StateMachine类,状态机控制类,实现了状态的切换,停止,销毁,可以根据泛型和枚举索引获得一个StateBase状态类,持有宿主,实际就是持有stateMachine的那个类,为了方便在写状态逻辑的时候获得宿主的引用做逻辑(比如要在状态中控制玩家进行动画播放)。
/// <summary>
/// 玩家状态的基类
/// </summary>
public abstract class PlayerStateBase : StateBase
{
public Player_Controller player;
public override void Init(IStateMachineOwner owner, int stateType, StateMachine stateMachine)
{
base.Init(owner, stateType, stateMachine);
player = (Player_Controller)owner;
}
}
/// <summary>
/// 玩家待机状态类
/// </summary>
public class Player_IdleState : PlayerStateBase
{
public override void Enter()
{
//播放待机动作
player.PlayAnimation("Idle");
}
}
/// <summary>
/// 玩家状态枚举
/// </summary>
public enum PlayerState
{
Idle,
}
PlayerStateBase作为所有玩家状态的基类(目前只有Idle待机状态),有Init方法来初始化宿主获得PlayerController的引用。
Player_IdleState作为玩家具体的待机状态类,执行进入待机状态播放待机动画的逻辑。
到此为止玩家的状态逻辑已经搭建完成,由PlayerStateBase继承StateBase获得状态机的基础功能并指定自己的宿主PlayerController,并进一步有PlayerIdleState完成实际的待机状态逻辑,通过宿主实现玩家的动画播放逻辑,下一步,我们来完善PlayerController中的逻辑。
玩家的状态机-进入待机状态
在上一节中已经有了玩家的状态基类以及待机状态,我们现在需要使用状态机来实现对状态的切换,同时完成玩家动画播放的实际逻辑,这些功能都由玩家的PlayerController(MVC结构中的Controller层)来完成。
public class Player_Controller : SingletonMono<Player_Controller>,IStateMachineOwner
{
[SerializeField] Player_View view;
private StateMachine stateMachine;
private PlayerState playerState; // 玩家的当前状态
private WarriorConfig warriorConfig;
.......
}
首先需要让PlayerController实现IStateMachineOwner接口来标记为宿主进行使用,使得PlayerState部分能够访问他,这里的IStateMachineOwner底层是一个空接口,仅仅用来容纳不同类型的宿主,这一思想在事件系统中容纳不同参数列表的事件类也用过。
- 虽然接口无法直接创建实例化对象,但是实现了接口的类可以创建对象,我们实现了某一个接口,就相当于创建了这个接口的一个子类,接口就是这个类的父类,而父类引用是可以指向子类对象的。所以我们创建的不是接口对象,而是实现了该接口的类的对象,也就是接口的子类对象。
PlayerController持有玩家的状态机来对状态进行控制(状态机归属于一个类型,而不是全局的状态机),为了方便记录了玩家的当前状态,同时建立了一个配置来表示玩家的动画列表。
//PlayerController类
public void Init()
{
warriorConfig = ResManager.LoadAsset<WarriorConfig>("WarriorConfig");
view.InitOnGame(DataManager.CustomCharacterData);
//初始化状态机
stateMachine = PoolManager.Instance.GetObject<StateMachine>();
stateMachine.Init(this);
//默认为待机动画
ChangeState(PlayerState.Idle);
}
在Init方法中,通过ResManager从AA获取一个动画配置表,初始化数据并还原玩家默认外观后,对状态机进行初始化,首先从对象池管理器中获取一个StateMachine类资源,并传递自己作为宿主初始化,同时设置默认状态为待机状态。
//PlayerController类
/// <summary>
/// 修改状态
/// </summary>
public void ChangeState(PlayerState playerState)
{
this.playerState = playerState;
switch (playerState)
{
case PlayerState.Idle:
stateMachine.ChangeState<Player_IdleState>((int)PlayerState.Idle);
break;
default:
break;
}
}
为了更好的支持拓展性并简化代码,对状态切换逻辑进行一层封装,实际就是加个Switch判断分流,不用写一长串了,在状态机底层通过传进来的泛型和枚举索引双重锁定对应的状态类,这也是为什么stateMachine可以复用的原因,它本身是不携带信息的,只是个工具。
//PlayerController类
/// <summary>
/// 播放动画
/// </summary>
public void PlayAnimation(string animationClipName,float fixedTime = 0.25f)
{
view.Animation.PlayAnimation(warriorConfig.GetAnimationByName(animationClipName),fixedTime);
}
最后来完善下State部分调用宿主播放的逻辑,这里我们结合之前写的PlayAble控制类AnimatorController(View层持有,方便以后更换Controller层还保有View层的组件)传一个切换速度和动画clip来完成。Clip这为了扩展性同样使用SO配置的方式来完成。
[CreateAssetMenu(fileName = "WarriorConfig",menuName = "Config/WarriorConfig")]
public class WarriorConfig : ConfigBase
{
public Dictionary<string, AnimationClip> StandAnimationDic;
public AnimationClip GetAnimationByName(string animationName)
{
return StandAnimationDic[animationName];
}
}
一个字典来存动画列表并提供一个根据名字查动画资源的方法,然后创建对应的SO拖资源并添加到AAGroup中即可。
总结
上面写了这么多,再总结一下hhh,屏蔽框架来看看整个逻辑,首先角色的待机状态实际的逻辑是在进入待机状态播放待机动画,为此我们创建一个PlayerIdleState来执行这个逻辑,具体的动画切换还是交给Player那边做。
为了方便之后写其他状态,写一个PlayerStateBase类,他有一个宿主,也就是持有这个状态的对象本身,方便操作。
PlayerController作为控制中心持有状态机对状态进行切换控制,StateMachine作为一个工具可以被重复利用。
在播放动画部分使用PlayAble进行动画切换,同样为了支持拓展性,使用SO配置对动画资源进行分配。
最后我们来单独看一下框架中StateBase和StateMachine的关系,StateBase作为全局状态基类规定了状态的进入,退出,进行中的方法,方便子类重写,同时有个初始化方法用来指定宿主。StateMachine是用来控制不同StateBase切换的,切换时会执行对应的进入,退出方法。有关宿主的使用主要是为了方便获取引用的,否则可能要通过单例,静态类来获取对象本身的组件。
最终效果,游戏运行,玩家处于待机状态(Player_Controller-> Player_IdleState -> AnimatorController)。