Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Showcase [Sources Included] A simple animation system to get started quickly

Discussion in 'DOTS Animation' started by PhilSA, Jan 28, 2021.

  1. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I started looking into DOTS animation lately, and I gotta say the learning curve is pretty steep right now. So for that reason, I made a simple animation system/component that handles taking a list of clips, and letting you call for transitions between these clips. It also allows more manual control over Weights, Speeds, etc.... if you want more than just the default TransitionTo() function. It's something that should allow people to at least have some basic animation working easily while we wait for the real user-friendly animation tools

    I'm certain there are flaws in it right now, because I'm still in the process of learning DOTS Animation. I'm hoping this thread can serve as a way to get feedback on possible improvements & how to handle things better

    A sample project is available in the attached files, as well as just the core scripts

    • Make sure your project has the "ENABLE_HYBRID_RENDERER_V2" define
    • Make sure the "SimpleAnimationAuthoring" is on same GameObject as the RigComponent, and assign a list of clips in the "SimpleAnimationAuthoring"
    • Make sure your skinned mesh uses materials/shaders that support skinning
    • In a job, use the various functions of the "SimpleAnimation" component to control the animation (TransitionTo, SetWeight, SetSpeed, etc....) always use the Setter functions instead of setting the values of the weight/speed/etc variables directly in the components
    Example code of calling a transition:
    Code (CSharp):
    1. protected override void OnUpdate()
    2. {
    3.     bool jump = UnityEngine.Input.GetKeyDown(UnityEngine.KeyCode.Space);
    4.  
    5.     Entities.ForEach((ref SimpleAnimation simpleAnimation, ref DynamicBuffer<SimpleAnimationClipData> simpleAnimationClipDatas) =>
    6.     {
    7.         if (jump)
    8.         {
    9.             simpleAnimation.TransitionTo(jumpClipIndex, transitionDuration, ref simpleAnimationClipDatas, false);
    10.         }
    11.     }).Schedule();
    12. }




    -
     

    Attached Files:

    Last edited: Feb 16, 2021
    CodeRonnie, jaydenm, JohngUK and 22 others like this.
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Two things I would need help with:
    • How can I setdata/sendmessages to nodes in bursted jobs? The job in SimpleAnimationSystem that handles updating clip weights/speeds is using .Run(), because I don't currently know how to set the weights of an NMixerNode or the Speeds of ClipNodes other than with the NodeSet class, which prevents me from using jobs. If someone has any tips regarding this, let me know
    • How do I get/read the Time value of a given ClipPlayerNode? I only know how to set it
    @Adam-Mechtley
     
    Last edited: Jan 28, 2021
    Egad_McDad and Occuros like this.
  3. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Graph updates are main thread only. So if you have heavy logic on the data you update the graph with, it can pay to separate that out and do it in a job. Then main thread all you are doing is iterating components/arrays and setting the node data but no logic beyond that.
     
    PhilSA likes this.
  4. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    857
    It gets a little complex but to bypass NodeSet and set the data directly from your components, you'll need a node that can read that component and then setup the connections your graph.

    So this is one such node I have, that reads from a "SimpleInput" component which itself is just modified by a regular system.

    Create the controller node and connect your entity node into it as a feedback type.
    var controller = graphSystem.CreateNode<SimpleControllerInputNode>(data.Graph);
    set.Connect(entityNode, controller, SimpleControllerInputNode.KernelPorts.Input, NodeSet.ConnectionType.Feedback);


    You can also write back to components, connect the controller node's DataOutput port named in this case WriteOutput to the entity node
    set.Connect(controller, SimpleControllerInputNode.KernelPorts.WriteOutput, entityNode);


    The node code:
    Code (CSharp):
    1.  
    2. public struct SimpleInput : IComponentData
    3. {
    4.     public bool  PlaySingleAnimationClip;
    5.     public float MixerWalkJobBlend;
    6.     public float TimeCounterSpeed;
    7.     public float MixerSpeedBlend;
    8. }
    9.  
    10. public class SimpleControllerInputNode : KernelNodeDefinition<SimpleControllerInputNode.KernelDefs>
    11. {
    12.     public struct KernelDefs : IKernelPortDefinition
    13.     {
    14.         public DataInput<SimpleControllerInputNode, SimpleInput>      Input;
    15.         public DataOutput<SimpleControllerInputNode, bool>            PlaySingleAnim;
    16.         public DataOutput<SimpleControllerInputNode, float>           MixerWalkJobBlend;
    17.         public DataOutput<SimpleControllerInputNode, float>           TimeCounterSpeed;
    18.         public DataOutput<SimpleControllerInputNode, float>           MixerSpeedBlend;
    19.         public DataOutput<SimpleControllerInputNode, SimpleInput>     Output;
    20.         public DataOutput<SimpleControllerInputNode, PhysicsVelocity> WriteOutput; // just included as an example
    21.     }
    22.  
    23.     struct KernelData : IKernelData {}
    24.  
    25.     [BurstCompile]
    26.     struct Kernel : IGraphKernel<KernelData, KernelDefs>
    27.     {
    28.         public void Execute(RenderContext ctx, in KernelData data, ref KernelDefs ports)
    29.         {
    30.             var input = ctx.Resolve(ports.Input);
    31.        
    32.             ctx.Resolve(ref ports.PlaySingleAnim)    = input.PlaySingleAnimationClip;
    33.             ctx.Resolve(ref ports.MixerWalkJobBlend) = input.MixerWalkJobBlend;
    34.             ctx.Resolve(ref ports.TimeCounterSpeed)  = input.TimeCounterSpeed;
    35.             ctx.Resolve(ref ports.MixerSpeedBlend)   = input.MixerSpeedBlend;
    36.  
    37.             // Reset trigger values
    38.             input.PlaySingleAnimationClip = false;
    39.             ctx.Resolve(ref ports.Output) = input;
    40.        
    41.             // Arbitrarily write to the physics velocity component
    42.             ctx.Resolve(ref ports.WriteOutput) = new PhysicsVelocity{ Linear = new float3(0,20,0) };
    43.         }
    44.     }
    45. }

    I dont know if this is really feasible for even a moderately complex character, because it gets so confusing(well for me) to remember whats what and everything looks very similar on first, second and third glance. I stopped working with it for a few weeks and it feels like I have to relearn it every time I come back.

    I know that at the least they integrated the Compositor package with the animation package so maybe the future is just visual scripting to wire up connect custom nodes, rather than hard coding these things. Oh and note the current UIToolkit package breaks the Animation graph(visual scripting) window, because of course it did :)
     
    hugokostic and PhilSA like this.
  5. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I actually had no idea the animation graph even existed! So maybe I did this SimpleAnimation for nothing

    Is it useable currently? I only see AnimationClip and 2-clip-mixer nodes
     
    LudiKha likes this.
  6. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    857
    Im not sure tbh, I tried it on its own by making a new project with
    com.unity.compositor
    and was able to get more nodes there
    upload_2021-1-28_20-29-9.png
    But it does appear limited inside the animation package. I will probably stick with code graphs until the team says otherwise.
     
    PhilSA likes this.
  7. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    *** UPDATE ***
    • Bug fixes
    • Small optimisations
    • Added per-clip root motion option
    ______

    I was able to use this SimpleAnimation system in practice to handle all the animation in this little prototype project, if that can help you gain trust in the tool:


    To give you an idea, the entirety of the code that controls the animation you see in this video looks like this:
    Code (CSharp):
    1. using Unity.Animation;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8.  
    9. namespace Samples.Platformer
    10. {
    11.     [UpdateBefore(typeof(SimpleAnimationSystem))]
    12.     [UpdateBefore(typeof(DefaultAnimationSystemGroup))]
    13.     [UpdateAfter(typeof(PlatformerInputsSystem))]
    14.     public class PlatformerCharacterAnimationSystem : SystemBase
    15.     {
    16.         protected override void OnUpdate()
    17.         {
    18.             Entities
    19.                 .ForEach((
    20.                     ref SimpleAnimation simpleAnimation,
    21.                     ref DynamicBuffer<SimpleAnimationClipData> simpleAnimationClipDatas,
    22.                     in PlatformerCharacterAnimationComponent characterAnimation) =>
    23.                 {
    24.                     Rotation characterRotation = GetComponent<Rotation>(characterAnimation.CharacterEntity);
    25.                     KinematicCharacterBody characterBody = GetComponent<KinematicCharacterBody>(characterAnimation.CharacterEntity);
    26.                     PlatformerCharacterComponent characterComponent = GetComponent<PlatformerCharacterComponent>(characterAnimation.CharacterEntity);
    27.                     PlatformerCharacterStateMachine characterStateMachine = GetComponent<PlatformerCharacterStateMachine>(characterAnimation.CharacterEntity);
    28.                     PlatformerInputs inputs = GetComponent<PlatformerInputs>(characterAnimation.CharacterEntity);
    29.  
    30.                     float velocityMagnitude = math.length(characterBody.RelativeVelocity);
    31.                     switch (characterStateMachine.CurrentCharacterState)
    32.                     {
    33.                         case CharacterState.GroundMove:
    34.                             {
    35.                                 if (math.length(inputs.Move) < 0.01f)
    36.                                 {
    37.                                     simpleAnimation.TransitionTo(characterAnimation.IdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    38.                                     simpleAnimation.SetSpeed(characterAnimation.IdleSpeedMultiplier, characterAnimation.IdleClip, ref simpleAnimationClipDatas);
    39.                                 }
    40.                                 else
    41.                                 {
    42.                                     if (characterComponent.IsSprinting)
    43.                                     {
    44.                                         float velocityRatio = velocityMagnitude / characterComponent.GroundSprintMaxSpeed;
    45.                                         simpleAnimation.TransitionTo(characterAnimation.SprintClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    46.                                         simpleAnimation.SetSpeed(velocityRatio * characterAnimation.SprintSpeedMultiplier, characterAnimation.SprintClip, ref simpleAnimationClipDatas);
    47.                                     }
    48.                                     else
    49.                                     {
    50.                                         float velocityRatio = velocityMagnitude / characterComponent.GroundRunMaxSpeed;
    51.                                         simpleAnimation.TransitionTo(characterAnimation.RunClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    52.                                         simpleAnimation.SetSpeed(velocityRatio * characterAnimation.RunSpeedMultiplier, characterAnimation.RunClip, ref simpleAnimationClipDatas);
    53.                                     }
    54.                                 }
    55.                             }
    56.                             break;
    57.                         case CharacterState.Crouched:
    58.                             {
    59.                                 if (math.length(inputs.Move) < 0.01f)
    60.                                 {
    61.                                     simpleAnimation.TransitionTo(characterAnimation.CrouchIdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    62.                                     simpleAnimation.SetSpeed(characterAnimation.CrouchIdleSpeedMultiplier, characterAnimation.CrouchIdleClip, ref simpleAnimationClipDatas);
    63.                                 }
    64.                                 else
    65.                                 {
    66.                                     float velocityRatio = velocityMagnitude / characterComponent.CrouchedMaxSpeed;
    67.                                     simpleAnimation.TransitionTo(characterAnimation.CrouchMoveClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    68.                                     simpleAnimation.SetSpeed(velocityRatio * characterAnimation.CrouchMoveSpeedMultiplier, characterAnimation.CrouchMoveClip, ref simpleAnimationClipDatas);
    69.                                 }
    70.                             }
    71.                             break;
    72.                         case CharacterState.AirMove:
    73.                             {
    74.                                 simpleAnimation.TransitionTo(characterAnimation.InAirClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    75.                                 simpleAnimation.SetSpeed(characterAnimation.InAirSpeedMultiplier, characterAnimation.InAirClip, ref simpleAnimationClipDatas);
    76.                             }
    77.                             break;
    78.                         case CharacterState.Dashing:
    79.                             {
    80.                                 simpleAnimation.TransitionTo(characterAnimation.DashClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    81.                                 simpleAnimation.SetSpeed(characterAnimation.DashSpeedMultiplier, characterAnimation.DashClip, ref simpleAnimationClipDatas);
    82.                             }
    83.                             break;
    84.                         case CharacterState.WallRun:
    85.                             {
    86.                                 bool wallIsOnTheLeft = math.dot(MathUtilities.GetRightFromRotation(characterRotation.Value), characterComponent.LastKnownWallNormal) > 0f;
    87.                                 int clipIndex = wallIsOnTheLeft ? characterAnimation.WallRunLeftClip : characterAnimation.WallRunRightClip;
    88.                                 simpleAnimation.TransitionTo(clipIndex, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    89.                                 simpleAnimation.SetSpeed(characterAnimation.WallRunSpeedMultiplier, clipIndex, ref simpleAnimationClipDatas);
    90.                             }
    91.                             break;
    92.                         case CharacterState.RopeSwing:
    93.                             {
    94.                                 simpleAnimation.TransitionTo(characterAnimation.RopeHangClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    95.                                 simpleAnimation.SetSpeed(characterAnimation.RopeHangSpeedMultiplier, characterAnimation.RopeHangClip, ref simpleAnimationClipDatas);
    96.                             }
    97.                             break;
    98.                         case CharacterState.Climbing:
    99.                             {
    100.                                 float velocityRatio = velocityMagnitude / characterComponent.ClimbingSpeed;
    101.                                 simpleAnimation.TransitionTo(characterAnimation.ClimbingMoveClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    102.                                 simpleAnimation.SetSpeed(velocityRatio * characterAnimation.ClimbingMoveSpeedMultiplier, characterAnimation.ClimbingMoveClip, ref simpleAnimationClipDatas);
    103.                             }
    104.                             break;
    105.                         case CharacterState.LedgeGrab:
    106.                             {
    107.                                 float velocityRatio = velocityMagnitude / characterComponent.LedgeMoveSpeed;
    108.                                 simpleAnimation.TransitionTo(characterAnimation.LedgeGrabMoveClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    109.                                 simpleAnimation.SetSpeed(velocityRatio * characterAnimation.LedgeGrabSpeedMultiplier, characterAnimation.LedgeGrabMoveClip, ref simpleAnimationClipDatas);
    110.                             }
    111.                             break;
    112.                         case CharacterState.LedgeStandingUp:
    113.                             {
    114.                                 simpleAnimation.TransitionTo(characterAnimation.LedgeStandUpClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, true);
    115.                                 simpleAnimation.SetSpeed(characterAnimation.LedgeStandUpSpeedMultiplier, characterAnimation.LedgeStandUpClip, ref simpleAnimationClipDatas);
    116.                             }
    117.                             break;
    118.                         case CharacterState.Sliding:
    119.                             {
    120.                                 simpleAnimation.TransitionTo(characterAnimation.SlidingClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    121.                                 simpleAnimation.SetSpeed(characterAnimation.SlidingSpeedMultiplier, characterAnimation.SlidingClip, ref simpleAnimationClipDatas);
    122.                             }
    123.                             break;
    124.                         case CharacterState.Swimming:
    125.                             {
    126.                                 float velocityRatio = velocityMagnitude / characterComponent.SwimmingMaxSpeed;
    127.                                 if (velocityRatio < 0.1f)
    128.                                 {
    129.                                     simpleAnimation.TransitionTo(characterAnimation.SwimmingIdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    130.                                     simpleAnimation.SetSpeed(characterAnimation.SwimmingIdleSpeedMultiplier, characterAnimation.SwimmingIdleClip, ref simpleAnimationClipDatas);
    131.                                 }
    132.                                 else
    133.                                 {
    134.                                     simpleAnimation.TransitionTo(characterAnimation.SwimmingMoveClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    135.                                     simpleAnimation.SetSpeed(velocityRatio * characterAnimation.SwimmingMoveSpeedMultiplier, characterAnimation.SwimmingMoveClip, ref simpleAnimationClipDatas);
    136.                                 }
    137.                             }
    138.                             break;
    139.                         case CharacterState.Rolling:
    140.                             {
    141.                                 simpleAnimation.TransitionTo(characterAnimation.IdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    142.                                 simpleAnimation.SetSpeed(characterAnimation.IdleSpeedMultiplier, characterAnimation.IdleClip, ref simpleAnimationClipDatas);
    143.                             }
    144.                             break;
    145.                         case CharacterState.FlyingNoCollisions:
    146.                             {
    147.                                 simpleAnimation.TransitionTo(characterAnimation.IdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    148.                                 simpleAnimation.SetSpeed(characterAnimation.IdleSpeedMultiplier, characterAnimation.IdleClip, ref simpleAnimationClipDatas);
    149.                             }
    150.                             break;
    151.                         case CharacterState.DisabledNoCollisions:
    152.                             {
    153.                                 simpleAnimation.TransitionTo(characterAnimation.IdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    154.                                 simpleAnimation.SetSpeed(characterAnimation.IdleSpeedMultiplier, characterAnimation.IdleClip, ref simpleAnimationClipDatas);
    155.                             }
    156.                             break;
    157.                         case CharacterState.DisabledWithCollisions:
    158.                             {
    159.                                 simpleAnimation.TransitionTo(characterAnimation.IdleClip, characterAnimation.TransitionDuration, ref simpleAnimationClipDatas, false);
    160.                                 simpleAnimation.SetSpeed(characterAnimation.IdleSpeedMultiplier, characterAnimation.IdleClip, ref simpleAnimationClipDatas);
    161.                             }
    162.                             break;
    163.                     }
    164.                 }).Schedule();
    165.         }
    166.     }
    167. }

    I am calling "TransitionTo" and "SetSpeed" every frame, because both of these functions make inexpensive checks for changes, and only apply changes if the values are different (unless you specify you want to force the transition). So it doesn't really have much of a negative impact

    ______

    Some general thoughts on handling animation in games:

    I'm actually a pretty big fan of having your animation "state machine" be entirely on the side of your gameplay code instead of on the animation side in a node graph. What often happens with the way people typically make node-based animation state machines (in the Unity community, at least) is that they end up with two sources of truth; a state machine on their gameplay side, and an equivalent node-based state machine on their animation side. And they kinda just hope that the two will always stay in perfect sync based on the transitions that they have set up in the node graph. This often leads to bugs when you expected a transition to be detected but it didn't happen. It's very easy to forget to manually set up certain transitions in a node graph, and very easy to end up with an unmanageable spaghetti graph.

    I prefer the approach of having your gameplay code have complete authority over what state you're in, and it just forces your animation to transition to the right state at all times so there are no chances of desync between your true state and your anim state. It also has the advantage of letting you procedurally setup your transitions instead of having to do everything manually. I think it's a more robust approach for animation networking as well, because networking can sometimes require transitions between states that you didn't think were ever supposed to happen. Any animation state must be able to transition to any animation state

    Of course, I think there is still a place for node-based animation state machines, and good ways to use them. When an animation transition isn't related to the "game simulation", I think it's a good idea to setup the transitions & parameters in the node graph and not in the code. For example, a jump animation that has a start anim and then auto-transitions into a falling loop anim. Or setting up directional blend trees. They are purely visual/aesthetic things and don't represent an actual change in the way your simulation logic is handled

    ______
     
    Last edited: Feb 1, 2021
  8. zhovat

    zhovat

    Joined:
    Mar 16, 2017
    Posts:
    1
    Hey Phil,

    Thanks for digging into this. I'm not having much luck getting your SimpleAnimation code working, but I'm sure I just have something misconfigured. Could you add a sample project so I can try to figure out what I'm missing?
     
  9. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    good idea, I added a very straightforward sample project to the downloads (uses URP and unity 2021 beta but should probably work with 2020.1 or 2020.2 as well... maybe)
     
    Last edited: Feb 14, 2021
  10. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    To get it to work on 2020.2, I had to add ENABLE_HYBRID_RENDERER_V2 to the compile directives and downgrade the urp version to 10.0.0 (or any of the 10.X.X versions should work) but that was it.

    EDIT: I also had to resave the animation shader graph to get rid of "V2 incompatibility errors" but that seems to be as simple as opening up the shadergraph and resaving the assset
     
    Last edited: Feb 16, 2021
    andreiagmu, Egad_McDad and PhilSA like this.
  11. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Hi Phil,

    it nice to talk to you again.

    So in the next version of dots animation (0.10.0) we are extracting time from DFG node. Basically animation timer are now ECS data that are updated by an ECS system and then send to each ClipSamplerNode. It allow any ECS system to reason about time. Think about animation events/markups/ or custom system like simple animation that want to drive time manually.

    The ClipPlayerNode will eventually be deprecated because
    1. it manage the time internally which make it imposible to use it with Fixed pipeline like for physics.
    2. if any system like markups/animation events need to reason about time it really hard to extract the data from DFG node.
     
  12. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    @Mecanim-Dev

    I've encountered an issue where root motion position deltas calculated in the AnimatedRootMotion component never seem include any motion on the Y axis. I've triple-checked my animation and made sure the root bone that I'm using DOES have animated motion on the Y axis



    The yellow line in this gif (the one parallel to the floor) represents a Debug.DrawRay of the AnimatedRootMotion.Delta.pos every frame. Normally that line should go diagonally towards the animation's movement, but it doesn't. It's staying entirely on the XZ plane

    Should this be considered a bug, or is there a setting somewhere that clamps root motion deltas on the Y axis?
     
  13. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    @PhilSA - You need to remove the line
    _processDefaultAnimationGraphSystem.Set.Dispose();
    from SimpleAnimationSystem.cs

    It breaks in the presence of other graphs, as the Set.Dispose() is automatically called by RemoveRef() if there are no more references.
     
    Egad_McDad and PhilSA like this.
  14. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Thank you, I've applied the fix and reuploaded!
     
  15. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Hi phil, root motion is a projection of the motion node of the floor, yes the Y axis is completly clamped

    you can see how it compute in com.unity.animation\Unity.Animation.Graph\RootMotion\InPlaceMotionNode.cs
    see method
    Code (CSharp):
    1. void ProjectMotionNode(float3 t, quaternion q, out float3 projT, out quaternion projQ, bool bankPivot)
     
  16. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    how should we deal with cases where we need the Y root motion?

    In my case: I have a "climb up a ledge" animation that should be moving my entire character entity along with it via rootmotion (meaning my entity's translation should always be between the two feet of the character). I feel like having the rootmotion be unprojected by default, and letting us project it if we want to, would allow better flexibility

     
    Last edited: Feb 26, 2021
  17. anarkiastd

    anarkiastd

    Joined:
    Feb 3, 2018
    Posts:
    8
    Hi Phil,

    I am trying to get it work on Android but authoring file only works in editor, which is linked to "ToDenseClip()". Is there any way to store the converted blobassetreferences or get it working on builds? Do you have any leads?
     
  18. StickyKevin

    StickyKevin

    Joined:
    Jul 1, 2020
    Posts:
    22
    You can "store" the blob assetreference by converting your animated object in a subscene and store the converted data in a componentdata.
    Then you can spawn entity instances of the converted animated object at runtime.
     
    anarkiastd likes this.
  19. yifanchu183

    yifanchu183

    Joined:
    Jul 4, 2019
    Posts:
    41
    Where I can get the animation clip that useful in ecs, I download animation from mixamo, but it not work correctly
     
  20. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
    so mixamo clips works, you just have to make sure you setup your gameobject correctly in the scene.

    First make sure the model has Read/Write enabled
    upload_2021-5-30_13-48-12.png

    Then add either the LateAnimationGraphTransformWriteHandle or DefaultAnimationGraphTransformWriteHandle to the "root" bone of your model (the root bone may be different per model but for mixamo it's usually the mixamorig:Hips)

    upload_2021-5-30_13-51-37.png

    Then make sure that the RigComponent is attached to your entity as such
    upload_2021-5-30_13-53-22.png

    Oh, and don't forget to add the ConvertToEntity component added as well.

    ALSO: don't forget to replace your materials with one that works with either ComputerDeformations or VertextSkinning (THe Unity.Animation samples have some you could use).

    Here is the link to the ones i made for testing purposes. I still use them.
    https://github.com/Steven9Smith/Some_Free_Stuff_I_Link_To/tree/main/Unity_Shaders

    after downloading the shadergraph you should be to create a material that looks like this
    upload_2021-5-30_14-1-36.png
     
    Last edited: May 30, 2021
    hugokostic, andreiagmu and Fibonaccov like this.
  21. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
    I've been thinking of doing a tutorial with using mixamo for unity stuff...if anyone's interested, just let me know.
     
  22. yifanchu183

    yifanchu183

    Joined:
    Jul 4, 2019
    Posts:
    41
    today I try again to get a new animation clip for the showcase project, it success. Then I move it to my project, and successed too. But I make me so annoying becasue several days before same step not work. F***. I still dont know why failed before,haha. anyway, it worked today.
    For your tutorial, i think the other setting for animation, this vedio may be useful,How to play Animation with Unity ECS.#UnityECS - YouTube
     
    Last edited: May 31, 2021
  23. yifanchu183

    yifanchu183

    Joined:
    Jul 4, 2019
    Posts:
    41
    By the way, does any tutorial told how to use the animation Graph for several clips and state switch
     
  24. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
    not that i know of, the previous tutorial posted by yifanchu183 explains one of the ways to animate but you would have to use the other samples in the Unity.Animations package to get an understanding on how it works and create your own. Or you could use one someone else has already created. I have one but Its still a work in progress so i haven't released it yet.
     
  25. PBN_Pure

    PBN_Pure

    Joined:
    Jan 10, 2020
    Posts:
    23
    would F***ing love ot man
     
  26. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
    Here you go. This is a simple tutorial on setting up mixamo stuff for unity. The next one will be modifying the bones and skinning of mixamo models so you can create custom models and skinning and add more bones like I did with the ears and head of my Cinderace model....oh dang, youtube cut off some of my text :(
     
    Last edited: Jul 4, 2021
  27. PBN_Pure

    PBN_Pure

    Joined:
    Jan 10, 2020
    Posts:
    23
    ill have to check this out thanks man
     
  28. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
    no problem. I'll make a second video on how to edit/modify the model to add more bones without messing up the mixamo animations. In the Cinderace model I use I added bones to the ears, hair, and face whisker things which allows me to animate them with the mixamo animations.
     
  29. NT_Ninetails

    NT_Ninetails

    Joined:
    Jan 21, 2018
    Posts:
    196
  30. Krooq

    Krooq

    Joined:
    Jan 30, 2013
    Posts:
    196
    I love this little tool. Really solid, simple animation workflow.
     
    Last edited: Aug 16, 2021
  31. Arnold_2013

    Arnold_2013

    Joined:
    Nov 24, 2013
    Posts:
    286
    Thank you for sharing this code, its really nice to be able to get some animations in a DOTS project without waiting for a new version.

    Does anyone know the correct way of destroying an animated object with this system?

    My solution:
    1.Remove "SimpleAnimation" and run "SimpleAnimationSystem" before Destroy() entity.
    2. Change "SimpleAnimationSystem" line 75 to "EntityManager.RemoveComponent<SimpleAnimationGraphData>(e);"

    (I found a solution for my issue while writing this (rubber ducky FTW), but maybe this can help someone else... here is my original problem with all the exceptions that were thrown)

    Original problem:

    When I just Destroy() on an Entity all the animations work perfectly, but I get an error when I stop the game. This is thrown in NodeSet.cs line 347. and the amount of nodes leaked increases when more instances are spawned.

    NodeSet leak warnings: 17 leaked node(s),0 leaked graph value(s) and 0 leaked buffer upload requests left!


    I saw in the "SimpleAnimationSystem" there is a ".WithName("DestroyGraph")" section that runs when the "SimpleAnimation"-component is removed. So now before destroying the instance I remove this component and run this system. The leaked node error is now solved.

    But I get a new error that does not consistently happen, with 9 instances it always happens (the chunk capacity is 12, and all 9 are in the same chunk).

    ArgumentException: Item of Unity.DataFlowGraph.InternalNodeData was disposed or doesn't exist anymore


    When I was writing this a possible solution came to mind. When I change line 75 in "SimpleAnimationSystem" from "ecb.RemoveComponent<SimpleAnimationGraphData>(e);" to "EntityManager.RemoveComponent<SimpleAnimationGraphData>(e);"
    it seems to fix the issue. I think the DestroyGraph() is directly applied, but the ecb is not executed yet so the entity is still found by "// Update graph values" ForEach loop but the graph has already been destroyed.

    full Stacktrace:
    Code (CSharp):
    1. ArgumentException: Item of Unity.DataFlowGraph.InternalNodeData was disposed or doesn't exist anymore
    2. Unity.Collections.VersionedList`1[T].Validate (Unity.Collections.VersionedHandle handle) (at Library/PackageCache/com.unity.dataflowgraph@0.19.0-preview.7/Runtime/Library/VersionedList.cs:288)
    3. Unity.DataFlowGraph.InputPair..ctor (Unity.DataFlowGraph.NodeSetAPI set, Unity.DataFlowGraph.NodeHandle destHandle, Unity.DataFlowGraph.InputPortArrayID destinationPort) (at Library/PackageCache/com.unity.dataflowgraph@0.19.0-preview.7/Runtime/PortPairs.cs:27)
    4. Unity.DataFlowGraph.NodeSetAPI.SetData[TType,TDefinition] (Unity.DataFlowGraph.NodeHandle`1[TDefinition] handle, Unity.DataFlowGraph.InputPortArrayID port, TType& data) (at Library/PackageCache/com.unity.dataflowgraph@0.19.0-preview.7/Runtime/Messaging.cs:307)
    5. Unity.DataFlowGraph.NodeSet.SetData[TType,TDefinition] (Unity.DataFlowGraph.NodeHandle`1[TDefinition] handle, Unity.DataFlowGraph.DataInput`2[TDefinition,TType] port, TType& data) (at Library/PackageCache/com.unity.dataflowgraph@0.19.0-preview.7/Runtime/Messaging.cs:193)
    6. SimpleAnimationSystem+<>c__DisplayClass_OnUpdate_LambdaJob3.OriginalLambdaBody (Unity.Entities.Entity entity, SimpleAnimationGraphData& simpleAnimationGraphData, Unity.Entities.DynamicBuffer`1[SimpleAnimationClipData]& simpleAnimationClipDatas) (at Assets/aT_ShaderGraph/Animation/NewAnimationCode/SimpleAnimationScripts/SimpleAnimationSystem.cs:137)
    7. SimpleAnimationSystem+<>c__DisplayClass_OnUpdate_LambdaJob3.IterateEntities (Unity.Entities.ArchetypeChunk& chunk, SimpleAnimationSystem+<>c__DisplayClass_OnUpdate_LambdaJob3+LambdaParameterValueProviders+Runtimes& runtimes) (at <cb6f43dc26b44fe99215c7f4be66947c>:0)
    8. SimpleAnimationSystem+<>c__DisplayClass_OnUpdate_LambdaJob3.Execute (Unity.Entities.ArchetypeChunk chunk, System.Int32 chunkIndex, System.Int32 firstEntityIndex) (at <cb6f43dc26b44fe99215c7f4be66947c>:0)
    9. Unity.Entities.JobChunkExtensions.RunWithoutJobs[T] (T& jobData, Unity.Entities.ArchetypeChunkIterator& chunkIterator) (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/IJobChunk.cs:182)
    10. SimpleAnimationSystem+<>c__DisplayClass_OnUpdate_LambdaJob3.RunWithoutJobSystem (Unity.Entities.ArchetypeChunkIterator* archetypeChunkIterator, System.Void* jobData) (at <cb6f43dc26b44fe99215c7f4be66947c>:0)
    11. Unity.Entities.InternalCompilerInterface.RunJobChunk[T] (T& jobData, Unity.Entities.EntityQuery query, Unity.Entities.InternalCompilerInterface+JobChunkRunWithoutJobSystemDelegate functionPointer) (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/CodeGeneratedJobForEach/LambdaJobDescription.cs:390)
    12. SimpleAnimationSystem.OnUpdate () (at Assets/aT_ShaderGraph/Animation/NewAnimationCode/SimpleAnimationScripts/SimpleAnimationSystem.cs:115)
    13. Unity.Entities.SystemBase.Update () (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/SystemBase.cs:412)
    14. Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/ComponentSystemGroup.cs:472)
    15. UnityEngine.Debug:LogException(Exception)
    16. Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/Stubs/Unity/Debug.cs:19)
    17. Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/ComponentSystemGroup.cs:477)
    18. Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/ComponentSystemGroup.cs:417)
    19. Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/ComponentSystem.cs:114)
    20. Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/ScriptBehaviourUpdateOrder.cs:333)
    21.  
     
  32. StickyKevin

    StickyKevin

    Joined:
    Jul 1, 2020
    Posts:
    22
    If you have created your Nodes with ProcessAnimationGraphBase.CreateNode<YourNode>(GraphHandle), you must call Dispose on that same GraphHandle object before removing the SimpleAnimationGraphData component.

    Or, if you have created your Nodes using NodeSet.Create<YourNode>(), you must use NodeSet.Destroy(yourNode) with the same NodeSet object you used to create it.
     
    Arnold_2013 likes this.
  33. andreiagmu

    andreiagmu

    Joined:
    Feb 20, 2014
    Posts:
    175
    @PhilSA I'm using your Rival character controller, which uses this animation system on its Platformer sample.

    I added my own character model to the Platformer character prefab. But I'm having difficulty adding other animations to the character.
    Can third-party animations be used/retargeted with your SimpleAnimationSystem? Or does it have some limitations?

    I already removed Animator, added RigComponent and PlatformerCharacterAnimationAuthoring.
    I already set "Read/Write Enabled" to true in both my model and animations.
    I use skinning-compatible materials.

    I guess the model and animations need to be Generic instead of Humanoid, right?
    I tried setting them to Generic, but it still didn't work.

    Can someone help me with this? Maybe I'm missing some steps.
     
  34. andreiagmu

    andreiagmu

    Joined:
    Feb 20, 2014
    Posts:
    175
    About my retargeting question, I'm almost finishing a custom version of SimpleAnimationSystem, with retargeting support (based on the Retarget Map sample from the Unity Animation samples repository).
    I'll share my custom version here, during the weekend. ;)

    But first, I'm trying to fix the leaked nodes issue. I'm making some tests in the vanilla SimpleAnimationSample project, that same "leaked nodes issue" also happens (not always) when simply exiting playmode.

    In this case, during SimpleAnimationSystem.cs OnDestroy() callback, when it tries to get the
    graphsQuery = GetEntityQuery(typeof(SimpleAnimationGraphData), typeof(SimpleAnimationClipData));
    , sometimes the entity still has the SimpleAnimationClipData component, then the graph is correctly disposed.
    But other times, it seems Unity randomly destroys that component before executing that query (or, OnDestroy() is randomly executed later than it should), then the query fails.

    This also happens when the entity is disposed during scene unload (for example, when I unload the game scene and go back to the title screen in my game). In this case, the system tries to run the "DestroyGraph" .ForEach on OnUpdate(), but that query also fails because of the same "missing SimpleAnimationClipData" issue.

    Seems like a system update order issue (or conversion destroy order issue).
    I'm trying to find a way to ensure OnDestroy will execute at the correct time (when SimpleAnimationClipData component still exists on the entity).
     
    Last edited: Oct 2, 2021
  35. andreiagmu

    andreiagmu

    Joined:
    Feb 20, 2014
    Posts:
    175
    I managed to fix the leaked nodes issue! ;)
    With the help of this tip by @DreamingImLatios:
    https://forum.unity.com/threads/inc...-during-subscene-unload.1177657/#post-7541728

    In summary, I created a new component, SimpleAnimationClipDataSystemState, which implements ISystemStateBufferElementData. Then, after I create the graph, I copy the allocated ClipNodes data to that component.
    As SystemState components aren't auto-destroyed like standard components, then I can query that component and clean up the nodes data correctly. :D

    @PhilSA I attached a version of SimpleAnimationSystem with my fix for the leaked nodes.
    @Arnold_2013 You may want to check out this version, maybe this has the real fix for your earlier issue.

    Now, onwards to the finishing touches of my retarget integration! :p
     

    Attached Files:

  36. andreiagmu

    andreiagmu

    Joined:
    Feb 20, 2014
    Posts:
    175
    @PhilSA Here's my custom version of SimpleAnimationSystem with retargeting support.
    My implementation is probably not the best one, I had to use workarounds in certain parts.
    Feel free to share your feedback, especially if anyone has a better implementation for something there :D

    I attached a .zip with the scripts, another .zip with a sample project, and the .pdf documentation (also included in the .zip files).

    EDIT: (October 7, 2021) Updated documentation
     

    Attached Files:

    Last edited: Oct 7, 2021
  37. Luxxuor

    Luxxuor

    Joined:
    Jul 18, 2019
    Posts:
    89
    If you use the IAnimationGraphSystem to create your nodes then you will only need to keep the GraphHandle to safely dispose of all the nodes of your graph, something like this:
    Code (CSharp):
    1.  
    2. var graphHandle = graphSystem.CreateGraph();
    3. var set = graphSystem.Set;
    4.  
    5. var graphData = new AnimationGraphSystemState
    6. {
    7.     graphHandle = graphHandle,
    8.     // this component node creation causes a structural change
    9.     entityNode = graphSystem.CreateNode(graphHandle, entity),
    10.     deltaTimeNode = graphSystem.CreateNode<ConvertAnimationDeltaTimeToFloatNode>(graphHandle),
    11.     nMixerNode = graphSystem.CreateNode<NMixerNode>(graphHandle),
    12. };
    This makes the destroy method trivial:
    Code (CSharp):
    1. private static void DestroyGraph(IAnimationGraphSystem graphSystem, in AnimationGraphSystemState graphSystemState)
    2. {
    3.     graphSystem.Dispose(graphSystemState.graphHandle);
    4. }