Hello! Hope you're enjoying the plugin. One of my subscribers pointed out that calling new() every frame would induce a lot of garbage collection. A work around is to instantiating everything locally beforehand. Such as: private readonly AnimationData IDLE = new(Animations.IDLE); private readonly AnimationData RUN = new(Animations.RUN); private readonly AnimationData ATTACK = new(Animations.ATTACK1, true, new()); Then pass those in when you need them!
Would be nice to have a tool to bake this automatically. Specifically the animation names as well. You should be able to generate the enum code block from a animation controller pretty easily.
Nice update on this. Regarding the animator values. I think I would prefer a use of generics instead of having a static list of enum values. Since for bigger projects there will be alot of animation names. So this enum will explode in size. Instead you could use generics with a forced type of System.Enum. This would break your default ctor for the AnimationData but I guess just having an ctor override for an empty new() could easely do the trick and replace the reset enum value just with a bool. So the AnimationCoder class top would look something like this: public abstract class AnimationCoder : Monobehaviour where TAnimEnum : System.Enum The animationData would also need this generic. With this change the class can just define which enum it wants to use for setting the animations.
So I tried this plugin, and it was a straightforward approach for my project but when dealing with multiple Animation layers, the reset almost always does not work. I found the issue, it was happening because there was only one currentCoroutine for various layers. To fix this issue: public abstract void DefaultAnimation(int layer); public Animator animator; private Animations[] currentAnimation; private bool[] layerLocked; private ParameterDisplay[] parameters; private Coroutine[] currentCoroutine = null; /// Sets up the Animator Brain public void Initialize() { AnimatorValues.Initialize(); Animator animator = GetComponent(); layerLocked = new bool[animator.layerCount]; currentAnimation = new Animations[animator.layerCount]; currentCoroutine = new Coroutine[animator.layerCount];
Duude, you are a genius for finding that bug. I really couldn't have made this plugin without this community. Most of the ideas for this plugin came from you all. Really appreciate all the feedback and suggestions. I have committed a new version with these changes on the github page.
@@SmallHedgeHQ[Edit] The above methods works fine there was a mistake in myside of the code that was causing it to through an error. Hello there this is very important, Please replace the array of the coroutines with a list, otherwise the plugin will give not work but remain stuck on the default animation. private List currentCoroutine = new List(); /// Sets up the Animator Brain public void Initialize() { AnimatorValues.Initialize(); //Animator animator = GetComponent(); layerLocked = new bool[animator.layerCount]; currentAnimation = new Animations[animator.layerCount]; // Coroutine[] currentCoroutine = new Coroutine[animator.layerCount]; currentCoroutine = new List(animator.layerCount); // this.animator = animator; for (int i = 0; i < animator.layerCount; ++i) { layerLocked[i] = false; currentCoroutine.Add(null); int hash = animator.GetCurrentAnimatorStateInfo(i).shortNameHash; for (int k = 0; k < AnimatorValues.Animations.Length; ++k) { if (hash == AnimatorValues.Animations[k]) { currentAnimation[i] = (Animations)Enum.GetValues(typeof(Animations)).GetValue(k); k = AnimatorValues.Animations.Length; } } } //rest of initialize} And later in the Play function if (layerLocked[layer] || currentAnimation[layer] == data.animation) return false; if (currentCoroutine[layer] != null) { StopCoroutine(currentCoroutine[layer]);} layerLocked[layer] = data.lockLayer; currentAnimation[layer] = data.animation; animator.CrossFade(AnimatorValues.GetHash(currentAnimation[layer]), data.crossfade, layer); if (data.nextAnimation != null) {
currentCoroutine.Insert(layer, StartCoroutine(Wait())); IEnumerator Wait() { animator.Update(0); float delay = animator.GetNextAnimatorStateInfo(layer).length; if (data.crossfade == 0) delay = animator.GetCurrentAnimatorStateInfo(layer).length; yield return new WaitForSeconds(delay - data.nextAnimation.crossfade); SetLocked(false, layer); Play(data.nextAnimation, layer); } } I hope you will see this comment and change the Repository again. There is some problem with using an array. Using array only works when there is no next animation for some reason.
@@SmallHedgeHQ I am such an idiot. It was working fine I had a typo there in the initialize Please remember the following text: "Coroutine[]"was added by mistake currentCoroutine = new Coroutine[animator.layerCount];
That's a great approach. I'll try to use it in my game, I'm tired of correcting dependencies in the standard animator. But for us lazy people, it would be easier if you also put the character control code in the kit, for example ))).
i actually used a system similar to this in my AI framework asset, and it is waaaaay easier and nicer than the transitions (asset name: Alek AI Framwork). nice work!
So first off, nice bit of work there. Very clean. A couple honest questions: 1. Is there a performance gain from this? 2. And either way, outside of ditching visual chaos, what is the benefit from your perspective?
Very important and valid questions. 1. I am not aware how the Unity Animator functions under the hood (other than it is a FSM), so I can't do a direct comparison. In terms of the performance of AnimatorCoder, everything is converted to hashes and the AnimatorCoder.Play() method is O(1) (unless you chain animations together), so it is very quick. The user is in control of how performant it is. Whether they want to do a bunch of checks every frame to play animations, or play animations due to an event. The performance is in the users hands. 2. First I will point out that is heavily depends on preference. If you are a natural programmer, converting all animations into code can make animations much more intuitive and logical. It is quite frequent in the Unity Animator that every state needs to be connected to every other state, and that takes a lot of time to set up the animator parameters and transition attributes. By default, the states in AnimatorCoder are already connected to every other state. It also allows you to alter the animation transitions at runtime. Since you can easily chain animations together with AnimatorCoder, you can create you own mini FSM and have then cancel whenever you want to. The main advantage of AnimatorCoder is it is quick, efficient, and simple and only requires one line of code to run AnimatorCoder.Play()
This looks awesome, but I wonder how would it work with multiple different animators and layers, maybe instead of using one big enum for all the animations you could use scriptable object that stores the names of animations and than just swap that whenever you want to use new animations instead of having them all in one big enum.
[SOLVED] When I add the "AnimatorCoder" instead of the MonoBehaviour, I have "error CS0118: 'AnimatorCoder' is a namespace but is used like a type" [SOLUTION] After on the video
Hi, I'm running into a problem where when I Jump i go through the FALLING Parameter into my IN_AIR Animation and then with the GROUNDED Parameter into the JUMP_LAND Animation. When I'm in Idle Animation (I've got 5 idle Animations that i loop through) and press JumpButton, the JUMP_START Animation plays but I think it's affected by the Idle Animation before. I would guess it's because of Crossfade. But then again, when I'm in WALKING or RUNNING Animation this Error doesn't occur even though I have crossfade on these as well. Also the JUMP_LAND Animation does often not play when I'm not moving and it goes straight back to IDLE. If anyone seem to recognize this or may know where I made a mistake, please answer. Help is greatly appreceated! 3D Game I used another Script for my Character Controller My Code: using System.Collections; using UnityEngine; using SHG.AnimatorCoder; public class NewAnimationSystem : AnimatorCoder { public static NewAnimationSystem instance; private BasicPlayerMovement basicPlayerMovement; private Vector2 movement; private bool playIdle; private bool isCautious; [Tooltip("The time between Idle Animation switch when standing")] public float switchIdleTime = 7f; private int randomIdleAnimation = 0; private int randomCautiousIdleAnimation = 0; Animator anim; void Awake(){ instance = this; } void Start() { Initialize(); basicPlayerMovement = GetComponent(); anim = GetComponent(); StartCoroutine(ChangeIdle()); IEnumerator ChangeIdle(){ while (true){ yield return new WaitForSeconds(switchIdleTime); randomIdleAnimation = Random.Range(0, 6); randomCautiousIdleAnimation = Random.Range(0, 2); } } } void Update() { movement.x = Input.GetAxisRaw("Horizontal"); movement.y = Input.GetAxisRaw("Vertical"); DefaultAnimation(0); StandMoveAnim(); CrouchAnim(); JumpAnim(); if(playIdle) LoopThroughIdleAnimations(); } void FixedUpdate(){ SetBool(Parameters.GROUNDED, basicPlayerMovement.grounded); SetBool(Parameters.FALLING, !GetBool(Parameters.GROUNDED) && basicPlayerMovement.rb.velocity.y < 0); SetBool(Parameters.MOVING, movement != Vector2.zero); SetBool(Parameters.WALKING, basicPlayerMovement.currentMoveSpeed = basicPlayerMovement.crouchSpeed + 0.1f); SetBool(Parameters.SPRINTING, basicPlayerMovement.currentMoveSpeed > basicPlayerMovement.sprintSpeed - 1.5f); } void StandMoveAnim(){ if(Input.GetKey(KeyCode.LeftControl)) return; if(GetBool(Parameters.GROUNDED) && GetBool(Parameters.SPRINTING)){ PlayNormalAnimationWithCrossfade(Animations.RUNNING); } else if(GetBool(Parameters.GROUNDED) && GetBool(Parameters.WALKING)){ PlayNormalAnimationWithCrossfade(Animations.WALKING); } else if (GetBool(Parameters.FALLING)){ PlayNormalAnimationWithCrossfade(Animations.IN_AIR); } } void CrouchAnim(){ if(!GetBool(Parameters.MOVING) && Input.GetKeyDown(KeyCode.LeftControl)){ SetLocked(false, 0); Play(new(Animations.STAND_TO_CROUCH, true, null, 0.1f)); } if(Input.GetKey(KeyCode.LeftControl)){ playIdle = false; if(GetBool(Parameters.MOVING)){ PlayNormalAnimationWithCrossfade(Animations.CROUCH_WALK, 0.15f); } else{ PlayNormalAnimationWithCrossfade(Animations.CROUCH_IDLE_01, 0.135f); } } if(!GetBool(Parameters.MOVING) && Input.GetKeyUp(KeyCode.LeftControl)){ SetLocked(false, 0); Play(new(Animations.CROUCH_TO_STAND, true, new(), 0.1f)); } } void JumpAnim(){ if(GetBool(Parameters.GROUNDED) && Input.GetKeyDown(KeyCode.Space) && !basicPlayerMovement.readyToJump){ Debug.Log("Current Transition: " + anim.GetAnimatorTransitionInfo(0)); SetLocked(false, 0); Play(new(Animations.JUMP_START, true, null, 0.1f)); } }
void LoopThroughIdleAnimations(){
if(!isCautious){ switch (randomIdleAnimation){ case 0: PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_01); break; case 1: PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_02); break; case 2: PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_03); break; case 3: PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_04); break; case 4: PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_05); break; } } else{ switch (randomCautiousIdleAnimation){ case 0: PlayNormalAnimationWithCrossfade(Animations.STANDING_CAUTIOUS_IDLE_01); break; case 1: PlayNormalAnimationWithCrossfade(Animations.STANDING_CAUTIOUS_IDLE_02); break; } } } //Just to make it shorter private void PlayNormalAnimationWithCrossfade(Animations _animation, float _crossfade = 0.2f){ Play(new(_animation, false, null, _crossfade)); } public override void DefaultAnimation(int layer) { if(Input.GetKey(KeyCode.LeftControl)) return; if(!GetBool(Parameters.MOVING) && GetBool(Parameters.GROUNDED) && !GetBool(Parameters.FALLING)) playIdle = true; else playIdle = false; } } My Parameters in the Animator Editor are: On JUMP_START: Falling Parameter -> Play IN_AIR Animation On IN_AIR: Grounded Parameter -> Play JUMP_LAND Animation -> Next Play RESET On JUMP_LAND: Walking Parameter -> WALKING Animation ; Running Parameter -> RUNNING Animation ;
😊 nice work. Rather than call New() for your animation reset, i would change your input to a second boolean isReset, and then an optional overload after that for the chained animation. Then pass the job of creating that reset to your animator class based on the boolean. But using the overload you get the option to specify or not. It would be easier for newbies, me thinks
AnimatorCoder.Play() is O(1) by default, so it depends how you use it in your scripts. I had a quick look at the Mesh Animator asset. If all the animations are baked, could you combine AnimatorCoder and Mesh Animator together by changing what AnimatorCoder.Play() does?
Hey, i am getting errors when i use (On Parameter), the error is : NullReferenceException: Object reference not set to an instance of an object. its saying at line 23 in OnParameter.cs. Thanks
The difference does seem pretty minimal but might work slightly faster if you have a mega animation chain :D. Otherwise not sure how much difference it would make.
I keep getting the error “Animator.GotoState: State could not be found UnityEngine.Animator:CrossFade (int,single,int)” And it sends me to line 138 of the AnimatorCoder script. animator.CrossFade(AnimatorValues.GetHash(currentAnimation[layer]), data.crossfade, layer); Any idea what’s happening? I’ve been searching for hours and can’t figure it out.
The most likely explanation is the enum value passed in Play() does not exist as a state in the animator. For example, if your animations enum looks like Enum Animations { RUN, WALK, IDLE } Then there must be a state that is named RUN, IDLE, or WALK
Hey man! I am using this instead of the animator now, and I am loving this! But I have a small question regarding blend-trees. If I want to use a Strafe left/right on top of walking/running, how can I achieve this using your code?
Sorry for late reply! Took time off and slowly getting through comments. I got asked a lot about blend trees. Unfortunately this is set up to optimise single state transitions, not blending multiple animations together. Best bet is to add more crossfade (transition time) to have walk left, right, forward, back animations and transition between them.
Hello, I'm aiming to be a new user of this system, but I have one worry. See, when making the system on your own like in the last video, I can use inheritance to give every entity in my game access to their own brain, as I can just make a new class that contains functionally the same things but with the proper data for the entity it will be used with. How do you handle separate brains with this plugin version of it? Because if I'm reading this right, there's only one AnimatorValues script, and all implementation of this plugin will call back to that script, with no way to tell it what values it should be using.
I suppose this is where there is a balance for convenience and optimisation. This method prioritised convenience (having one global AnimatorValues) and ease of use rather than each animator having their own set of values. A design decision that I thought was a good trade off.
So I suppose this plugin can only work with the situation that there’s no transition between animations and the animation must fully played before playing the next animation?
AnimatorCoder does allow transitions and overridable animations. Transitions: In the Play() method, you can set the crossfade of the animations so transition them over a number of seconds. Fully played: When you call Play(), it will automatically override the current playing animation. Unless you set LayerLocked = true, then no other new animations can play while the animator is locked. So you can play animations fully if it is an important animation. Or set it to be overridable by other animations.
Does this handle animation blend trees? Seems great for platformers but omnidirectional animations, like top down or 3d animations absolutely require blends.
Unfortunately it does not support blend trees and I agree. This plugin excels when the only form of blending are simple cross fade transitions. Which typically occurs in 2d projects and certain 3d situations. I did make a video using this plugin in 3d with multiple animator layers. One animator layer was the Upper Body and the other Lower Body. Then the animation logic in code was done in a way to try simulate blend trees by playing forward, back, left, and right walk animations when conditions were met. So like any plugin, it has great strengths and also some limitations, it would be just knowing what situations to use it and when not to use it. th-cam.com/video/Db88Bo8sZpA/w-d-xo.html
Each layer holds one singular current animation at a time and is tracked through an enum. So to pick a random animation, you could do Animations[Random.Range(0, Enum.GetNames(TypeOf(Animations)).length] to get a random animation. Or make a list of Enum values to randomly choose from. Each Enum value is given an integer, which allows you to look up them through an array.
8 หลายเดือนก่อน
it was good until the onparameter script, which is the same as the animator transitions just with less options... if you make it that we can add these data to the animationdata, then it will be much better solution
The parameter check only needs to occur when you are in the specific animator state. This was eliminating the need to check parameters each frame when you don’t need to.
Hello! Sure thing. Playables are like nodes connected in the PlayableGraph, It is a FSM. Playables Workflow: Have a PlayableGraph and add nodes (new animations) when you need them. Link the nodes together and now you have a FSM. It runs in the background and it will play the desired animations dependant on how it's set. Pros: Replicate the FSM nature of the animator into code Cons: Takes a while to setup Animator Workflow: Link animation states together using transition lines which play depending on animator parameters Pros: Highly visual, quick to setup, lots of features (blend trees, transition options, behavior states etc) Cons: Becomes more complicated the bigger it gets. Gets hard to keep track of everything AnimatorCoder Workflow: Create a list of conditions that need to be met to play an animation and use AnimatorCoder.Play() to play them. It will execute a bunch of O(1) checks to validate the request and then the animation is played. Pros: Quick to setup and requires only one universal setup, easy to use, minimal code, highly intuitive Cons: Doesn't provide functionality for blend trees We worked on AnimatorCoder because it is an animation system that works no (or very little) setup and works straight out of the box. It's the most intuitive animation scripting design I know.
I deliberately set it up that way! In the demo scene, there was the samurai and the wizard and they shared their state names. Using AnimatorCoder.Play(Animations.ATTACK1) on the samurai triggers the swing animations, while it triggered the firebolt animation on the wizard. Try to overlap them as much as you can. Most units have a RUN animation, WALK animation, multiple IDLE animations, multiple ATTACK animations, JUMPSTART JUMPAIR JUMPEND animations etc. It was done this way so you only need to set up AnimatorCoder once and not for every single unit you create.
Sounds like you’re going to use multiple animation layers and ik positions. Start small and pick one of them to start off with. If you would like an example of multi-layered animation workflow in 3d, I would check out the previous video on animations th-cam.com/video/Db88Bo8sZpA/w-d-xo.htmlsi=V5b2KJ-YV4BWuAYc
Nice work. Nice video. But I usually play animations using Animator.Play or Animator.CrossFade(for blending) and I don't see any difference between using your asset and my method. Maybe I'm wrong. If I am then let me know.
Someone else had a similar question! So I'm just going to copy paste the response. I suppose you can treat AnimatorCoder as Animator.Crossfade+. Here's a list of additional features it gives to Animator.Crossfade(): 1. Hold the CurrentAnimation: So you don't use Animator.Crossfade() on an already playing animation 2. Ability to lock/unlock layers: Some animations need to play all the way through without interuption 3. Play consequent animations: When an animation finishes, choose what animation to play next 4. Select animations through enums: Passing through an enum to play an animation is quick and easy In a nutshell, it adds a bunch additional checks to Animator.Crossfade() to ensure the requested animation is allowed to play.
I haven't give visual scripting a go so not too sure! If you are able to play methods from specfic scripts attacted to gameobjects, you should be able to!
@@SmallHedgeHQ Well, at least the unity Animator window doesn't list all parameters of all animation controllers in a single window You can introduce generic type parameters in your AnimationCoder class to force the inheritor to pass in their own enum classes for both the Animations and Parameters
yes but you can search them. i use a similar approach where i hash the animations before hand. if you play something in code you can always look up where it is triggered and from what.
Reset is pronounced as "Reset" not "Receipt". Bool is pronounced as "Bool" not "Ball". Check is also pronounced as "Check" not "Chick". BTW great asset overall, marvellous!!!
Hello! Hope you're enjoying the plugin. One of my subscribers pointed out that calling new() every frame would induce a lot of garbage collection. A work around is to instantiating everything locally beforehand. Such as:
private readonly AnimationData IDLE = new(Animations.IDLE);
private readonly AnimationData RUN = new(Animations.RUN);
private readonly AnimationData ATTACK = new(Animations.ATTACK1, true, new());
Then pass those in when you need them!
Would be nice to have a tool to bake this automatically. Specifically the animation names as well. You should be able to generate the enum code block from a animation controller pretty easily.
Damn, this looks amazing, great work! Will definitely test this out!
Definitely going to try it!!
I'm trying to improve my first character and your plugin will definitely come in clutch!
Thanks a lot!
Thank you. This video has inspired me to try to make a even more intuitive version of animating through coding!
Nice update on this. Regarding the animator values. I think I would prefer a use of generics instead of having a static list of enum values. Since for bigger projects there will be alot of animation names. So this enum will explode in size. Instead you could use generics with a forced type of System.Enum. This would break your default ctor for the AnimationData but I guess just having an ctor override for an empty new() could easely do the trick and replace the reset enum value just with a bool.
So the AnimationCoder class top would look something like this:
public abstract class AnimationCoder : Monobehaviour where TAnimEnum : System.Enum
The animationData would also need this generic.
With this change the class can just define which enum it wants to use for setting the animations.
Good idea. Having a different list of Enums for each unit. The reason why it was set up like this was so that it’s all in one place and easy to setup.
Thank you very much for this excellent plugin! People like you are a rare breed. :)
:) Just wanting to help out best I can
That's an excellent feedback response to comments!!!
Amazing!
Take a sneak peak into the Playable API when you have the chance :D
Love for animal Controller
Nice. You even adressed making an exception to allow the hit animation to play regardless of the current state.
this dude is insane and extremely underrated, I hope this video blows up
This should be the default behavior. Good work!
as a noob that knows little to know coding this was an incredible explanation
So I tried this plugin, and it was a straightforward approach for my project but when dealing with multiple Animation layers, the reset almost always does not work. I found the issue, it was happening because there was only one currentCoroutine for various layers. To fix this issue:
public abstract void DefaultAnimation(int layer);
public Animator animator;
private Animations[] currentAnimation;
private bool[] layerLocked;
private ParameterDisplay[] parameters;
private Coroutine[] currentCoroutine = null;
/// Sets up the Animator Brain
public void Initialize()
{
AnimatorValues.Initialize();
Animator animator = GetComponent();
layerLocked = new bool[animator.layerCount];
currentAnimation = new Animations[animator.layerCount];
currentCoroutine = new Coroutine[animator.layerCount];
Duude, you are a genius for finding that bug. I really couldn't have made this plugin without this community. Most of the ideas for this plugin came from you all. Really appreciate all the feedback and suggestions. I have committed a new version with these changes on the github page.
@@SmallHedgeHQ[Edit] The above methods works fine there was a mistake in myside of the code that was causing it to through an error.
Hello there this is very important, Please replace the array of the coroutines with a list, otherwise the plugin will give not work but remain stuck on the default animation.
private List currentCoroutine = new List();
/// Sets up the Animator Brain
public void Initialize()
{
AnimatorValues.Initialize();
//Animator animator = GetComponent();
layerLocked = new bool[animator.layerCount];
currentAnimation = new Animations[animator.layerCount];
// Coroutine[] currentCoroutine = new Coroutine[animator.layerCount];
currentCoroutine = new List(animator.layerCount);
// this.animator = animator;
for (int i = 0; i < animator.layerCount; ++i)
{
layerLocked[i] = false;
currentCoroutine.Add(null);
int hash = animator.GetCurrentAnimatorStateInfo(i).shortNameHash;
for (int k = 0; k < AnimatorValues.Animations.Length; ++k)
{
if (hash == AnimatorValues.Animations[k])
{
currentAnimation[i] = (Animations)Enum.GetValues(typeof(Animations)).GetValue(k);
k = AnimatorValues.Animations.Length;
}
}
}
//rest of initialize}
And later in the Play function
if (layerLocked[layer] || currentAnimation[layer] == data.animation) return false;
if (currentCoroutine[layer] != null) { StopCoroutine(currentCoroutine[layer]);}
layerLocked[layer] = data.lockLayer;
currentAnimation[layer] = data.animation;
animator.CrossFade(AnimatorValues.GetHash(currentAnimation[layer]), data.crossfade, layer);
if (data.nextAnimation != null)
{
currentCoroutine.Insert(layer, StartCoroutine(Wait()));
IEnumerator Wait()
{
animator.Update(0);
float delay = animator.GetNextAnimatorStateInfo(layer).length;
if (data.crossfade == 0) delay = animator.GetCurrentAnimatorStateInfo(layer).length;
yield return new WaitForSeconds(delay - data.nextAnimation.crossfade);
SetLocked(false, layer);
Play(data.nextAnimation, layer);
}
}
I hope you will see this comment and change the Repository again. There is some problem with using an array. Using array only works when there is no next animation for some reason.
@@OrbitZeroStudios hmm that is really bizarre. I’ll look into it tomorrow. Thanks for letting me know
@@SmallHedgeHQ I am such an idiot. It was working fine I had a typo there in the initialize
Please remember the following text: "Coroutine[]"was added by mistake currentCoroutine = new Coroutine[animator.layerCount];
@@OrbitZeroStudios aha that would make sense!
That's a great approach.
I'll try to use it in my game, I'm tired of correcting dependencies in the standard animator.
But for us lazy people, it would be easier if you also put the character control code in the kit, for example ))).
i actually used a system similar to this in my AI framework asset, and it is waaaaay easier and nicer than the transitions (asset name: Alek AI Framwork). nice work!
So first off, nice bit of work there. Very clean.
A couple honest questions:
1. Is there a performance gain from this?
2. And either way, outside of ditching visual chaos, what is the benefit from your perspective?
Very important and valid questions.
1. I am not aware how the Unity Animator functions under the hood (other than it is a FSM), so I can't do a direct comparison. In terms of the performance of AnimatorCoder, everything is converted to hashes and the AnimatorCoder.Play() method is O(1) (unless you chain animations together), so it is very quick. The user is in control of how performant it is. Whether they want to do a bunch of checks every frame to play animations, or play animations due to an event. The performance is in the users hands.
2. First I will point out that is heavily depends on preference. If you are a natural programmer, converting all animations into code can make animations much more intuitive and logical. It is quite frequent in the Unity Animator that every state needs to be connected to every other state, and that takes a lot of time to set up the animator parameters and transition attributes. By default, the states in AnimatorCoder are already connected to every other state. It also allows you to alter the animation transitions at runtime. Since you can easily chain animations together with AnimatorCoder, you can create you own mini FSM and have then cancel whenever you want to. The main advantage of AnimatorCoder is it is quick, efficient, and simple and only requires one line of code to run AnimatorCoder.Play()
This is amazing!! Thank you so much
This looks awesome, but I wonder how would it work with multiple different animators and layers, maybe instead of using one big enum for all the animations you could use scriptable object that stores the names of animations and than just swap that whenever you want to use new animations instead of having them all in one big enum.
Maby is more manageable even for multiplayer games. I will give this a try
Well then, this is actually super epic. Nice job!
[SOLVED]
When I add the "AnimatorCoder" instead of the MonoBehaviour, I have "error CS0118: 'AnimatorCoder' is a namespace but is used like a type"
[SOLUTION]
After on the video
Hi, I'm running into a problem where when I Jump i go through the FALLING Parameter into my IN_AIR Animation and then with the GROUNDED Parameter into the JUMP_LAND Animation.
When I'm in Idle Animation (I've got 5 idle Animations that i loop through) and press JumpButton, the JUMP_START Animation plays but I think it's affected by the Idle Animation before. I would guess it's because of Crossfade. But then again, when I'm in WALKING or RUNNING Animation this Error doesn't occur even though I have crossfade on these as well.
Also the JUMP_LAND Animation does often not play when I'm not moving and it goes straight back to IDLE.
If anyone seem to recognize this or may know where I made a mistake, please answer.
Help is greatly appreceated!
3D Game
I used another Script for my Character Controller
My Code:
using System.Collections;
using UnityEngine;
using SHG.AnimatorCoder;
public class NewAnimationSystem : AnimatorCoder
{
public static NewAnimationSystem instance;
private BasicPlayerMovement basicPlayerMovement;
private Vector2 movement;
private bool playIdle;
private bool isCautious;
[Tooltip("The time between Idle Animation switch when standing")]
public float switchIdleTime = 7f;
private int randomIdleAnimation = 0;
private int randomCautiousIdleAnimation = 0;
Animator anim;
void Awake(){
instance = this;
}
void Start()
{
Initialize();
basicPlayerMovement = GetComponent();
anim = GetComponent();
StartCoroutine(ChangeIdle());
IEnumerator ChangeIdle(){
while (true){
yield return new WaitForSeconds(switchIdleTime);
randomIdleAnimation = Random.Range(0, 6);
randomCautiousIdleAnimation = Random.Range(0, 2);
}
}
}
void Update()
{
movement.x = Input.GetAxisRaw("Horizontal");
movement.y = Input.GetAxisRaw("Vertical");
DefaultAnimation(0);
StandMoveAnim();
CrouchAnim();
JumpAnim();
if(playIdle) LoopThroughIdleAnimations();
}
void FixedUpdate(){
SetBool(Parameters.GROUNDED, basicPlayerMovement.grounded);
SetBool(Parameters.FALLING, !GetBool(Parameters.GROUNDED) && basicPlayerMovement.rb.velocity.y < 0);
SetBool(Parameters.MOVING, movement != Vector2.zero);
SetBool(Parameters.WALKING, basicPlayerMovement.currentMoveSpeed = basicPlayerMovement.crouchSpeed + 0.1f);
SetBool(Parameters.SPRINTING, basicPlayerMovement.currentMoveSpeed > basicPlayerMovement.sprintSpeed - 1.5f);
}
void StandMoveAnim(){
if(Input.GetKey(KeyCode.LeftControl)) return;
if(GetBool(Parameters.GROUNDED) && GetBool(Parameters.SPRINTING)){
PlayNormalAnimationWithCrossfade(Animations.RUNNING);
}
else if(GetBool(Parameters.GROUNDED) && GetBool(Parameters.WALKING)){
PlayNormalAnimationWithCrossfade(Animations.WALKING);
}
else if (GetBool(Parameters.FALLING)){
PlayNormalAnimationWithCrossfade(Animations.IN_AIR);
}
}
void CrouchAnim(){
if(!GetBool(Parameters.MOVING) && Input.GetKeyDown(KeyCode.LeftControl)){
SetLocked(false, 0);
Play(new(Animations.STAND_TO_CROUCH, true, null, 0.1f));
}
if(Input.GetKey(KeyCode.LeftControl)){
playIdle = false;
if(GetBool(Parameters.MOVING)){
PlayNormalAnimationWithCrossfade(Animations.CROUCH_WALK, 0.15f);
}
else{
PlayNormalAnimationWithCrossfade(Animations.CROUCH_IDLE_01, 0.135f);
}
}
if(!GetBool(Parameters.MOVING) && Input.GetKeyUp(KeyCode.LeftControl)){
SetLocked(false, 0);
Play(new(Animations.CROUCH_TO_STAND, true, new(), 0.1f));
}
}
void JumpAnim(){
if(GetBool(Parameters.GROUNDED) && Input.GetKeyDown(KeyCode.Space) && !basicPlayerMovement.readyToJump){
Debug.Log("Current Transition: " + anim.GetAnimatorTransitionInfo(0));
SetLocked(false, 0);
Play(new(Animations.JUMP_START, true, null, 0.1f));
}
}
void LoopThroughIdleAnimations(){
if(!isCautious){
switch (randomIdleAnimation){
case 0:
PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_01);
break;
case 1:
PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_02);
break;
case 2:
PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_03);
break;
case 3:
PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_04);
break;
case 4:
PlayNormalAnimationWithCrossfade(Animations.STANDING_IDLE_05);
break;
}
}
else{
switch (randomCautiousIdleAnimation){
case 0:
PlayNormalAnimationWithCrossfade(Animations.STANDING_CAUTIOUS_IDLE_01);
break;
case 1:
PlayNormalAnimationWithCrossfade(Animations.STANDING_CAUTIOUS_IDLE_02);
break;
}
}
}
//Just to make it shorter
private void PlayNormalAnimationWithCrossfade(Animations _animation, float _crossfade = 0.2f){
Play(new(_animation, false, null, _crossfade));
}
public override void DefaultAnimation(int layer)
{
if(Input.GetKey(KeyCode.LeftControl)) return;
if(!GetBool(Parameters.MOVING) && GetBool(Parameters.GROUNDED) && !GetBool(Parameters.FALLING))
playIdle = true;
else
playIdle = false;
}
}
My Parameters in the Animator Editor are:
On JUMP_START: Falling Parameter -> Play IN_AIR Animation
On IN_AIR: Grounded Parameter -> Play JUMP_LAND Animation -> Next Play RESET
On JUMP_LAND: Walking Parameter -> WALKING Animation ; Running Parameter -> RUNNING Animation ;
😊 nice work. Rather than call New() for your animation reset, i would change your input to a second boolean isReset, and then an optional overload after that for the chained animation. Then pass the job of creating that reset to your animator class based on the boolean. But using the overload you get the option to specify or not. It would be easier for newbies, me thinks
You might be onto something, can you clarify what you mean with code examples?
Is it more performant than mesh animator ? Which convert our animations to shader
AnimatorCoder.Play() is O(1) by default, so it depends how you use it in your scripts.
I had a quick look at the Mesh Animator asset. If all the animations are baked, could you combine AnimatorCoder and Mesh Animator together by changing what AnimatorCoder.Play() does?
@@SmallHedgeHQ how can i combine while 1 is an animator and the other is a sprite sheet of animations ?
this is so amazing, we need a discord!!
It might happen!
I just started watching but... Animation.CrossFade is literary what we used before Unity 4.0 and Mechanim system :D
Awsome ! But what about strafing ? Like what we use in 2D blend trees?
Hey, i am getting errors when i use (On Parameter), the error is : NullReferenceException: Object reference not set to an instance of an object. its saying at line 23 in OnParameter.cs. Thanks
Would it be possible to provide your player script in the Git repo please?
Can I make this work for top-down 2d?
Not tried it yet but wondering if using Cysharp's Unitask could be a way to reduce garbage?
The difference does seem pretty minimal but might work slightly faster if you have a mega animation chain :D. Otherwise not sure how much difference it would make.
Very useful and very good
Thank you so much 🎉
I keep getting the error “Animator.GotoState: State could not be found UnityEngine.Animator:CrossFade (int,single,int)”
And it sends me to line 138 of the AnimatorCoder script. animator.CrossFade(AnimatorValues.GetHash(currentAnimation[layer]), data.crossfade, layer);
Any idea what’s happening? I’ve been searching for hours and can’t figure it out.
The most likely explanation is the enum value passed in Play() does not exist as a state in the animator. For example, if your animations enum looks like
Enum Animations
{
RUN,
WALK,
IDLE
}
Then there must be a state that is named RUN, IDLE, or WALK
@@SmallHedgeHQ Ok, I'll try looking at that again! Thanks!
You are the best 😍😍😍😍 Thanks!!
Great job!
Hey man! I am using this instead of the animator now, and I am loving this! But I have a small question regarding blend-trees. If I want to use a Strafe left/right on top of walking/running, how can I achieve this using your code?
Sorry for late reply! Took time off and slowly getting through comments.
I got asked a lot about blend trees. Unfortunately this is set up to optimise single state transitions, not blending multiple animations together. Best bet is to add more crossfade (transition time) to have walk left, right, forward, back animations and transition between them.
Hello, I'm aiming to be a new user of this system, but I have one worry. See, when making the system on your own like in the last video, I can use inheritance to give every entity in my game access to their own brain, as I can just make a new class that contains functionally the same things but with the proper data for the entity it will be used with. How do you handle separate brains with this plugin version of it? Because if I'm reading this right, there's only one AnimatorValues script, and all implementation of this plugin will call back to that script, with no way to tell it what values it should be using.
I suppose this is where there is a balance for convenience and optimisation. This method prioritised convenience (having one global AnimatorValues) and ease of use rather than each animator having their own set of values. A design decision that I thought was a good trade off.
So I suppose this plugin can only work with the situation that there’s no transition between animations and the animation must fully played before playing the next animation?
AnimatorCoder does allow transitions and overridable animations.
Transitions: In the Play() method, you can set the crossfade of the animations so transition them over a number of seconds.
Fully played: When you call Play(), it will automatically override the current playing animation. Unless you set LayerLocked = true, then no other new animations can play while the animator is locked.
So you can play animations fully if it is an important animation. Or set it to be overridable by other animations.
Does this handle animation blend trees?
Seems great for platformers but omnidirectional animations, like top down or 3d animations absolutely require blends.
Unfortunately it does not support blend trees and I agree. This plugin excels when the only form of blending are simple cross fade transitions. Which typically occurs in 2d projects and certain 3d situations. I did make a video using this plugin in 3d with multiple animator layers. One animator layer was the Upper Body and the other Lower Body. Then the animation logic in code was done in a way to try simulate blend trees by playing forward, back, left, and right walk animations when conditions were met. So like any plugin, it has great strengths and also some limitations, it would be just knowing what situations to use it and when not to use it.
th-cam.com/video/Db88Bo8sZpA/w-d-xo.html
What if I want to randomly play multiple animations of each type with this plugin
Each layer holds one singular current animation at a time and is tracked through an enum. So to pick a random animation, you could do Animations[Random.Range(0, Enum.GetNames(TypeOf(Animations)).length] to get a random animation. Or make a list of Enum values to randomly choose from. Each Enum value is given an integer, which allows you to look up them through an array.
it was good until the onparameter script, which is the same as the animator transitions just with less options... if you make it that we can add these data to the animationdata, then it will be much better solution
The parameter check only needs to occur when you are in the specific animator state. This was eliminating the need to check parameters each frame when you don’t need to.
Bro your the Goat
Amazing!
Hi, do you have some kind of comparison post about this vs animator vs playables?
Hello! Sure thing. Playables are like nodes connected in the PlayableGraph, It is a FSM.
Playables Workflow:
Have a PlayableGraph and add nodes (new animations) when you need them. Link the nodes together and now you have a FSM. It runs in the background and it will play the desired animations dependant on how it's set.
Pros: Replicate the FSM nature of the animator into code
Cons: Takes a while to setup
Animator Workflow:
Link animation states together using transition lines which play depending on animator parameters
Pros: Highly visual, quick to setup, lots of features (blend trees, transition options, behavior states etc)
Cons: Becomes more complicated the bigger it gets. Gets hard to keep track of everything
AnimatorCoder Workflow:
Create a list of conditions that need to be met to play an animation and use AnimatorCoder.Play() to play them. It will execute a bunch of O(1) checks to validate the request and then the animation is played.
Pros: Quick to setup and requires only one universal setup, easy to use, minimal code, highly intuitive
Cons: Doesn't provide functionality for blend trees
We worked on AnimatorCoder because it is an animation system that works no (or very little) setup and works straight out of the box. It's the most intuitive animation scripting design I know.
Am I missing something or do I have to add ALL animations I have to one Enum? What if I have 10 different entities each with different animation?
I deliberately set it up that way! In the demo scene, there was the samurai and the wizard and they shared their state names. Using AnimatorCoder.Play(Animations.ATTACK1) on the samurai triggers the swing animations, while it triggered the firebolt animation on the wizard.
Try to overlap them as much as you can. Most units have a RUN animation, WALK animation, multiple IDLE animations, multiple ATTACK animations, JUMPSTART JUMPAIR JUMPEND animations etc. It was done this way so you only need to set up AnimatorCoder once and not for every single unit you create.
Does this work with bone animations? (2D Skinning)
Yes should work, if your animation is an animation state in the animator window
Very Nice!
how would i use this to implement swimming diving paragliding and free climbing
Sounds like you’re going to use multiple animation layers and ik positions. Start small and pick one of them to start off with. If you would like an example of multi-layered animation workflow in 3d, I would check out the previous video on animations th-cam.com/video/Db88Bo8sZpA/w-d-xo.htmlsi=V5b2KJ-YV4BWuAYc
i love the coding community
As a non programmer: What?
xD
Nice work. Nice video. But I usually play animations using Animator.Play or Animator.CrossFade(for blending) and I don't see any difference between using your asset and my method. Maybe I'm wrong. If I am then let me know.
Someone else had a similar question! So I'm just going to copy paste the response. I suppose you can treat AnimatorCoder as Animator.Crossfade+. Here's a list of additional features it gives to Animator.Crossfade():
1. Hold the CurrentAnimation: So you don't use Animator.Crossfade() on an already playing animation
2. Ability to lock/unlock layers: Some animations need to play all the way through without interuption
3. Play consequent animations: When an animation finishes, choose what animation to play next
4. Select animations through enums: Passing through an enum to play an animation is quick and easy
In a nutshell, it adds a bunch additional checks to Animator.Crossfade() to ensure the requested animation is allowed to play.
@@SmallHedgeHQ oh I get it now thanks. It's more like you upgraded state machine side of things. Cool. I'll have a look at it.
Can i use it if i am using visual scripting in unity??
I haven't give visual scripting a go so not too sure! If you are able to play methods from specfic scripts attacted to gameobjects, you should be able to!
@@SmallHedgeHQi am gonna try it today
This is not really scalable to an actual game lol, the Animations and Parameters enums are going to grow to unwieldy sizes
I suppose so is the Unity animator window xD
@@SmallHedgeHQ Well, at least the unity Animator window doesn't list all parameters of all animation controllers in a single window
You can introduce generic type parameters in your AnimationCoder class to force the inheritor to pass in their own enum classes for both the Animations and Parameters
yes but you can search them. i use a similar approach where i hash the animations before hand. if you play something in code you can always look up where it is triggered and from what.
Reset is pronounced as "Reset" not "Receipt". Bool is pronounced as "Bool" not "Ball". Check is also pronounced as "Check" not "Chick". BTW great asset overall, marvellous!!!
Bro’s really making fun of his accent, how petty can you get
지리네ㅎㄷㄷ
I cannot follow this. I'm good with the animatior so far, just learn to use blendtrees, and layers.
good solution but will not work with network animator (mirror , netcode , fishnet)