This is the Player's attack state which has combos and separate animations for ground, air, and while aiming upwards. It could be used by other characters, but none of the enemies have more than one attack animation and their Brains would also need to be changed to make use of combos and jumping.
Fields
The fields are all arrays of AttackTransition
s, though the Ground Animations
are the only group with multiple animations to perform a combo:
public sealed class AdvancedAttackState : AttackState
{
const string TooltipPrefix = "The animation combo used when the character is";
[SerializeField]
[Tooltip(TooltipPrefix + "grounded")]
private AttackTransition[] _GroundAnimations;
[SerializeField]
[Tooltip(TooltipPrefix + "grounded and trying to move upwards")]
private AttackTransition[] _GroundUpAnimations;
[SerializeField]
[Tooltip(TooltipPrefix + "airborne")]
private AttackTransition[] _AirAnimations;
[SerializeField]
[Tooltip(TooltipPrefix + "airborne and trying to move upwards")]
private AttackTransition[] _AirUpAnimations;
Initialization
All of the animations use the same OnAnimationEnd
method to handle Combos:
private void Awake()
{
Action onEnd = OnAnimationEnd;
for (int i = 0; i < _GroundAnimations.Length; i++)
_GroundAnimations[i].Events.OnEnd += onEnd;
for (int i = 0; i < _GroundUpAnimations.Length; i++)
_GroundUpAnimations[i].Events.OnEnd += onEnd;
for (int i = 0; i < _AirAnimations.Length; i++)
_AirAnimations[i].Events.OnEnd += onEnd;
for (int i = 0; i < _AirUpAnimations.Length; i++)
_AirUpAnimations[i].Events.OnEnd += onEnd;
It also registers a callback to the CharacterBody2D.OnGroundedChanged
event so that the attack will be cancelled if the character lands during an air attack or becomes airbourne during a ground attack:
Character.Body.OnGroundedChanged += OnGroundedChanged;
}
private void OnGroundedChanged(bool isGrounded)
{
if (Character.StateMachine.CurrentState == this)
Character.StateMachine.ForceSetDefaultState();
}
State Entry
Entering this state resets the current attack index to start with the first animation in whichever group ends up getting played and also clears the Combo flag before playing an animation:
private int _CurrentIndex;
private bool _Combo;
public override void OnEnterState()
{
base.OnEnterState();
_CurrentIndex = 0;
_Combo = false;
PlayAnimation();
}
To play an animation, it first clears any active Hit Boxes then determines which group of animations it should be using and plays the one at the current index:
private void PlayAnimation()
{
Character.Animancer.EndHitSequence();
var animation = CurrentAnimations[_CurrentIndex];
Character.Animancer.Play(animation);
}
Determining which animation group to use is simply based on the whether or not the character is currently on the ground and aiming upwards:
private AttackTransition[] CurrentAnimations
{
get
{
if (Character.Body.IsGrounded)
{
if (Character.MovementDirectionY > 0.5f && _GroundUpAnimations.Length > 0)
return _GroundUpAnimations;
else
return _GroundAnimations;
}
else
{
if (Character.MovementDirectionY > 0.5f && _AirUpAnimations.Length > 0)
return _AirUpAnimations;
else
return _AirAnimations;
}
}
}
Combos
While in this state, if the Character Brain attempts to enter it again, it sets the _Combo
flag but still returns false
to finish the current attack:
public override bool CanExitState
{
get
{
if (Character.StateMachine.NextState == this)
_Combo = true;
return false;
}
}
If the _Combo
flag was set when the animation ends, the current attack index is incremented and if there is still another animation remaining in the current group it is played:
private void OnAnimationEnd()
{
if (_Combo)
{
_Combo = false;
_CurrentIndex++;
if (_CurrentIndex < CurrentAnimations.Length)
{
PlayAnimation();
return;
}
}
Otherwise the character returns to their Idle state normally:
Character.StateMachine.ForceSetDefaultState();
}
}