Tech Blog 3 - Animation & Loose Coupling
·In the previous blog post, I showcased the transition from DOTween to FEEL. Now it was time to implement animations and tie them to the game logic so that nothing really ties them…naniii?
Let’s start from the beginning. By implementing FEEL, implementation was abstracted to the Editor, so now I can go to the editor and instead of moving characters on the X axis, I can just configure effects and animations right? Well, technically yes:
PlayAnimation is a custom feedback, because we are using Spine Pro for 2D animations, and FEEL doesn’t provide feedback for it out of the box. Great that FEEL is extensible right? Yeah...
Now another topic I didn't mention before is, “when you play animation” vs “when you change state”? Initially i thought this would suffice:
Maybe in some cases that still holds true. But I encountered something that this wasn’t enough for. What exactly? Well let’s compare examples below:
See the difference? Because the simple one is unreal and fast, I didn't notice the problem there. But with real animation the problem is apparent, Character A should visually receive damage only when B is at the appropriate animation stage, right? So how do we do this without coupling things too much?
I moved forward with the animation events approach, and moved it to configuration on ScriptableObject level, so that code can be universal. To put it in short, each Effect in the game consists of a sequence of Nodes:
public class Effect
{
public List nodeList;
}
public abstract class Node
{
public SkeletonDataAsset AnimationDataAsset;
public EventData EventData;
}
Each node, can have animation event information attached, meaning that this node should only trigger when appropriate animation event is fired. When Effect is executed, it goes through Nodes, one by one. If any Node must wait for animation event, than execution of the Effect pauses, and resumes once animation event is fired, easier to showcase with diagram:
Using this approach I am able to separate animation and game state changes, and tie them so that they don’t really know about each other but communicate through events. Code looks a bit ugly, but i should be to simplify it once i get acquinted with FEEL enough. Code and fixed version below:
private async void OnUseAbility(UseAbilityEvent e)
{
if (e.Source != BattleCharacter) return;
_activeFeedback = useAbilityFeedback.PlayFeedbacksTask();
var spinePlayAnimationFeedback = useAbilityFeedback.FeedbacksList
.OfType<MMF_SpinePlayAnimation>().FirstOrDefault();
spinePlayAnimationFeedback?.SubscribeToSpineEvents(animationEvent =>
{
EventBus.Raise(new TriggerAbilityEffectNodeEvent
{
Event = e,
EventData = animationEvent.Data
});
});
await _activeFeedback;
spinePlayAnimationFeedback?.UnsubscribeFromSpineEvents();
Dequeue();
}