Hello,
I wanted to show my workflow for creating charge animations in Spine + Unity using just a single animation. The basic idea is that each charge animation is split into three segments:
- Windup - this segment is always played and is the initial windup of the attack.
- Charge - this is the optional charge segment which is played for as long as the character is charging the attack or until the segment has finished, this segment can be skipped entirely for a regular attack (no charge).
- Follow through - this segment is the actual attack swing which is always played and can be always mixed from any point in the charge segment.
Here is how each segment of the animation looks YELLOW is the windup, BLUE is the (optional) charge, and GREEN is the follow through.
and here is a preview of the entire animation:
In order to play this back we will need the animation to playback, the duration of the segments, some state around the current animation, and whether or not the character is charging. To playback a segment of an animation, we modify the animation timing of a TrackEntry and a few mix settings to get the correct look we want when transitioning between them.
Here is some code from my engine that handles the attack animation state which should give you a general idea of how it works:
// This is called when we start the initial action
public void StartAction(Character character) {
isCharging = canCharge;
chargeState = CharacterActionChargeState.Begin;
UpdateAnimationState(character);
}
// This is called to cancel the charge
public virtual void CancelCharge(Character character) {
isCharging = false;
// If we are currently charging, make sure to play the next animation right away.
if(chargeState == CharacterActionChargeState.Charge) {
UpdateAnimationState(character);
}
}
// This is called when the current TrackEntry has completed, note we only track the current playing TrackEntry to avoid
// callbacks from mixed animations.
public void OnAnimationComplete(Character character, TrackEntry trackEntry) {
if(chargeState == CharacterActionChargeState.Followthrough) {
character.FinishAction();
}
else {
UpdateAnimationState(character);
}
}
private void UpdateAnimationState(Character character) {
// This is shorthand for skeletonAnimation.AnimationState.SetAnimation(0, animation, false);
var trackEntry = character.SetAnimation(animation, false);
switch(chargeState) {
// Initial charge state
case CharacterActionChargeState.Begin: {
// If the character can charge and we are currently charging then only play the first segment.
if(canCharge && isCharging) {
trackEntry.AnimationStart = 0.0f;
trackEntry.AnimationLast = trackEntry.AnimationStart;
trackEntry.AnimationEnd = chargeTime.start;
chargeState = CharacterActionChargeState.Windup;
}
// otherwise play the entire track and wait to finish.
else {
chargeState = CharacterActionChargeState.Followthrough;
}
break;
}
// Windup has finished
case CharacterActionChargeState.Windup: {
// If we are still charging, then play the next segment, there is no need to mix from the previous since it starts
// from the current frame.
if(isCharging) {
trackEntry.AnimationStart = chargeTime.start;
trackEntry.AnimationLast = trackEntry.AnimationStart;
trackEntry.AnimationEnd = chargeTime.end;
trackEntry.MixDuration = 0;
chargeState = CharacterActionChargeState.Charge;
}
// otherwise skip the charge segment and play from the last charge frame.
else {
trackEntry.AnimationStart = chargeTime.end;
trackEntry.AnimationLast = trackEntry.AnimationStart;
trackEntry.HoldPrevious = true;
chargeState = CharacterActionChargeState.Followthrough;
}
break;
}
// Charge has finished, play the last segment
case CharacterActionChargeState.Charge: {
trackEntry.AnimationStart = chargeTime.end;
trackEntry.AnimationLast = trackEntry.AnimationStart;
trackEntry.HoldPrevious = true;
chargeState = CharacterActionChargeState.Followthrough;
break;
}
}
}
Finally, here is how it looks in action with a varying amount of time holding the 'charge' button.