马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
前言
参考Animancer在状态机中的InputBuffer,在UnityHFSM中实现类似的InputBuffer机制,同时扩展一个状态打断机制
插件先容:
Animancer:Unity的动画插件,易于修改和扩展。
UnityHFSM:Github上的一个开源分层状态机项目
基于继续的方法使用UnityHFSM:
UnityHFSM有非常方便的使用方法,可以在不创建新类的情况下创建状态和状态机,界说转换条件等,但如果状态逻辑比力复杂,而且有同一的父级举动,则发起使用类继续的方式界说状态和状态机类。
本文是笔者在做一个本身的ARPG小项目时做出来的,思绪仅供参考。
预输入
Animancer的InputBuffer:
Animancer的InputBuffer紧张处理惩罚流程如下:
- 在构造函数中绑定一个状态机
- 外部调用Buffer函数,传入盼望的目标状态和超时时长
- InputBuffer在Update函数中轮询是否可以大概转换到目标状态,一旦转换乐成即立即竣事轮询
- 超时后也会竣事轮询
注意点:
- Animancer吸收的是状态机接口,而不是具体的状态机类。但由于须要继续来扩展UntiyHFSM的状态机和状态举动的须要,我在实现时会传入的是类而非接口。
- Animancer的InputBuffer并未继续自Monobehavior,而且没有使用任何UnityEngine的东西,即Update须要在其他Mono类中调用,Time.deltaTime也要靠外部传入,方便起见,我实现时直接使用Time.deltaTime。
在UnityHFSM中实现InputBuffer:
Animancer的状态机有TryResetState函数,而且会返回是否乐成,UntiyHFSM中无此函数,故使用StateMachine类的StateChanged来触发变乱,并与盼望的目标状态举行比对,划一则克制轮询。
Animancer直接使用TryResetState函数来改变状态,状态是否允许进入的条件也写在状态中,UnityHFSM依赖于Transition界说两个指定状态之间的转换,固然也有RequestStateChange来逼迫转换到某个状态,但如许做并不优雅,而且大概堕落。由于这个利用的多样性和复杂性,由外界传入Action来决定轮询时该举行的利用。(大多数时间使用UnityHFSM的StateMachine的Trigger函数来转换状态)
以下是代码,CharacterStateMachineBase是我扩展的脚色状态机基类,ECharacterState是脚色状态的罗列值
- public class InputBuffer
- {
- public bool IsActive => Action != null;
- public float TimeOut;
- public Action Action;
-
- public CharacterStateMachineBase StateMachine;
- public ECharacterState TargetState;
- public InputBuffer(CharacterStateMachineBase stateMachine)
- {
- StateMachine = stateMachine;
- StateMachine.StateChanged += OnStateChanged;
- }
- ~InputBuffer()
- {
- StateMachine.StateChanged -= OnStateChanged;
- }
- public void Buffer(Action action, ECharacterState targetState, float timeOut)
- {
- Action = action;
- TargetState = targetState;
- TimeOut = timeOut;
- }
- public void Update()
- {
- if (IsActive)
- {
- Action();
- TimeOut -= Time.deltaTime;
- if (TimeOut < 0)
- Clear();
- }
- }
- public virtual void Clear()
- {
- Action = null;
- TimeOut = default;
- }
- public void OnStateChanged(UnityHFSM.StateBase<ECharacterState> state)
- {
- if (IsActive && state.name == TargetState)
- {
- Clear();
- }
- }
- }
复制代码 在外部调用时如下
- //CharacterBrain.cs
- //控制的玩家
- public Character ControlledCharacter;
- //攻击缓冲
- public float AttackTimeout = 1f;
- InputBuffer AttackBuffer;
- //绑定操作
- void Start()
- {
- GameInput.Instance.onAttack += () =>
- {
- AttackBuffer.Buffer(
- () => ControlledCharacter.Attack(),
- ECharacterState.Attack,
- AttackTimeout);
- };
- }
- //轮询
- void Update()
- {
- AttackBuffer.Update();
- }
复制代码 Character的Attack函数实际上就是在调用脚色状态机的Trigger函数,如下
- //Character.cs
- public void Attack()
- {
- if (Equipment.CurrentWeapon == null)
- return;
- StateMachine.Trigger("Attack");
- }
复制代码 打断机制
我把打断机制写在了状态机中,作为一种扩展举动,在UnityHFSM中,一个State有一个needsExitTime字段,该字段为true时,除非逼迫转换,否则无法退出本状态,共同Animancer的动画变乱(OnEnd)可以很好的让动画来控制状态的转换(我如许做是盼望脚色的各种动作更加真实,让逻辑和体现更容易同一)。
为此我们只须要新增一个叫做CanExitBeforeEnd的bool变量即可体现本状态是否可以被打断。于此同时,实际上完全没有须要为每个状态新建一个CanExitBeforeEnd变量,将该变量放在状态机类中,供每个状态变更,供外界访问即可。
现在我们只使用CanExitBeforeEnd表近况态盼望被改变,但没有直接改变的逻辑,假设我们当前使用Trigger的方法举行状态变更,则我们只须要在每次Trigger前查询一次当前状态是否须要变更,须要变更且状态的needsExitTime为true,则将其设置为false,再实行Trigger利用即可。
StateMachine的Trigger利用并不是虚函数,只须要在源码中为其添加virtual关键字即可,然后在扩展的CharacterStateMachineBase中重写Trigger逻辑,代码如下
- public class CharacterStateMachineData
- {
- public bool CanExitBeforeEnd;
- }
- public class CharacterStateMachineBase : StateMachine<ECharacterState>
- {
- public Character Owner;
- public CharacterStateMachineData Data;
- public CharacterStateMachineBase(Character owner, CharacterStateMachineData data = null) : base()
- {
- Owner = owner;
- if(data != null )
- Data = data;
- else
- Data = new CharacterStateMachineData();
- }
- //分层状态机递归获取具体状态
- public CharacterStateBase CurrentState
- {
- get
- {
- if (ActiveState is CharacterStateMachineBase)
- return (ActiveState as CharacterStateMachineBase).CurrentState;
- return ActiveState as CharacterStateBase;
- }
- }
- public override void Trigger(string trigger)
- {
- //允许玩家通过一些动作和输入打断当前状态
- if(ActiveState.needsExitTime && Data.CanExitBeforeEnd)
- ActiveState.needsExitTime = false;
- base.Trigger(trigger);
- }
- }
- public class CharacterStateBase : State<ECharacterState>
- {
- public Character Owner;
- new public CharacterStateMachineBase fsm => base.fsm as CharacterStateMachineBase;
- public virtual Vector3 DeltaMotion => Owner.Animancer.Animator.deltaPosition;
- public CharacterStateBase(Character owner) : base()
- {
- Owner = owner;
- }
- public override void OnEnter()
- {
- base.OnEnter();
- fsm.Data.CanExitBeforeEnd = false;
- }
- }
复制代码 以上是在使用Trigger时打断的利用,假设我们使用的是寻常的Transition举行状态变更,那么是无法打断的,比如如下的移动利用,完全没有使用Trigger
- MovementStateMachineBase GroundFSM = new MovementStateMachineBase(owner);
- GroundFSM.AddState(EMovementState.Idle, new IdleState(owner));
- GroundFSM.AddState(EMovementState.Move, new MoveState(owner));
- GroundFSM.AddTwoWayTransition(EMovementState.Idle, EMovementState.Move,
- t => Owner.Parameters.MovementDirection.magnitude > 0);
复制代码 为此,我们可以在盼望被打断的状态机中特殊指出何时可以被打断,我当前只在攻击状态时可以被打断,不打断会放完攻击动画,打断会立即进入其他状态并播放动画
特殊的代码如下,该代码会在AttackState的Update函数中被轮询
Parameters是Character的字段,用于与直接的GameInput解耦
- private void UpdateInterrupt()
- {
- if(!fsm.Data.CanExitBeforeEnd)
- return;
- //移动打断
- if (Owner.Parameters.MovementDirection.magnitude > 0)
- {
- needsExitTime = false;
- }
- }
复制代码 |