For
Weltall Zero and anyone else interested in using the
Playable API for animations, here is how my hacky way works.
(if anyone with more experience with the API has any tips feel free to point out my dumb approach).
The player script is basically a Class based Finite State Machine built on a slight modification to this fsm system
prime31 StateKit.
For a quick explanation on what a fsm is or how to implement one
http://gameprogrammingpatterns.com/state.html (amazing resource btw, in C++).
Each state has an onEnter(), onUpdate() and onExit() method that get called when the state is entered, exited and onUpdate() is called every frame until the state is exited.
So for example the
neutralPlayer, which is a subclass of the abstract Class
State, is the following:
(i removed some parts of the code to only focus on the animation part)
C#:
public class NeutralPlayer : State
{
AnimationClip idleAnimation;
PlayableGraph animGraph;
public NeutralPlayer (AnimationClip idleAnimation) // constructor
{
this.idleAnimation = idleAnimation;
}
public override void onEnter()
{
base.onEnter();
AnimationPlayableUtilities.PlayClip(actor.animator, idleAnimation, out animGraph);
// the animationClip is played as soon as the state is entered, the graph animGraph is created by the AnimationPlayableUtilities utility.
}
public override void onExit()
{
base.onExit();
animGraph.Destroy(); // destroys the graph, when this state will be re-entered it will be re-created from scratch.
}
public override void onUpdate() { }
public override void check_transition()
{
if (actor.controller.collisions.tDown && myInput.getButtonDown(0,inputAction.Cross))
fsm.changeState<Jump>();
}
}
This works pretty well and is a clean way to separate player behaviours. check_transition() runs every frame and checks if the fsm can move to another state.
Note that an Animator component is still required to play animations, the AnimatorController however is not needed for this approach. In the documentation linked at the beginning of this post there is an example of mixing animations and AnimatorControllers in a graph.
But what if you want to execute some code at a specific frame of an animation?
For example if you have an Attack state and want to know when an attack animation is done so you can change state to neutral, or if from a specific frame of the attack animation you are able to jump cancel, how can you get that information?
My hacky solution is a mix of animation events and a helper Class :
On the animation clip i manually set up events which notify the helper class (which is basically made up of different boolean flags) and sets the corresponding flags to true or false.
When the state is exited these flags are all returned to false.
The code, using the Attack state as an example:
C#:
public abstract class Attack : State
{
public AttackHelper attackHelper;
public ComboTree comboTree;
PlayableGraph animGraph;
public Attack(AttackHelper attackHelper, ComboTree comboTree)
{
this.comboTree = comboTree;
this.attackHelper = attackHelper; // the helper class
}
public override void onEnter()
{
base.onEnter();
myInput = InputManager.inputManagerinstance;
isDone = attackHelper.isAttackDone = false;
attackHelper.unsetFlags();
attackHelper.setAttackCollider();
AnimationPlayableUtilities.PlayClip(actor.animator, comboTree.getCurrentAttack().attackAnim, out animGraph);
}
public override void onExit()
{
base.onExit();
attackHelper.clearHitCollection();
isDone = false;
animGraph.Destroy();
}
public override void onUpdate()
{
isDone = attackHelper.isAttackDone;
if(attackHelper.isActiveFrames)//checks if the attack is active
attackHelper.checkAttackHit(comboTree.getCurrentAttack());
}
public override void check_transition()
{
if (myInput.getButtonDown(0,inputAction.Square))
{
if(attackHelper.canMoveToNextAttack && isDone == false)
{
if(comboTree.hasNextLight())
{
comboTree.moveToNextLight();
resetAttackState();
}
}
}
if (isDone) // if the animation is done return to neutral, isDone gets its value from the AttackHelper helper class
{
comboTree.resetTree();
fsm.changeState<NeutralPlayer>();
}
if(attackHelper.canJumpCancel && myInput.getButtonDown(0,inputAction.Cross))
{
Debug.Log("JUMPCANCELLING");
fsm.changeState<Jump>();
}
}
public virtual void resetAttackState()
{
onExit();
onEnter();
}
}
Some caveats:
The events are kind of annoying to set up manually but only if you have lots of animations.
I don't use any blending between animations, if you do and use animation events make sure that they actually fire. Blending is supported in the Playable API.
In older versions of Unity people found that sometimes animation events would not fire (not sure, found reading old threads), i read somewhere (maybe the Unity forums) that the bug was fixed 100%.
I'm not an expert on animations, actually i'm not an expert period. This is just how my code evolved.
For anyone interested in animating an attack system i found this post where the/one of the devs from punch planet talked about how he handles attack animations, his user is wilpowrr
https://old.reddit.com/r/Unity3D/comments/7nrm85/animating_an_attack_hit_boxes_weapon_trails/
He advises against using animation events, which i agree with, even if i'm using them it "feels" wrong. But i also just grab sprites from the internet for visualization purposes, i don't work with an artist.
I got the idea of using animation events by a mix of necessity, banging my head against a wall and
this GDC talk by platinum games , they talk about animation flags around the 13 minute mark but i recommend watching it fully, very informative and entertaining (plus Yoko Taro).