Search Unity

Question Root Motion inaccuracy when climbing ladder

Discussion in 'Animation' started by nissekaka, Apr 3, 2021.

  1. nissekaka

    nissekaka

    Joined:
    Oct 8, 2020
    Posts:
    5
    When applying root motion while climbing a ladder my character only moves the correct distance on the Y-axis when climbing one step at a time (playing the climb up or down animation once without looping).
    When playing the slide animation, which has the same root motion change on the Y-axis as the climbing animations, it moves the wrong amount on the Y-axis. (The animation length is shorter than the climbing animations, but the Y-axis change is identical.)
    When looping any of the animation the Y-axis changed differs from the original animation. One loop of any animation should result in the character having their hands and feet on the steps.

    I've tried removing animation compression, tried different Animator settings and I've tried using OnAnimatorMove() to no avail. What's going on here?

    GIF to show what's happening:
    http://i.imgur.com/gpd4PXa.mp4
     
    Last edited: Apr 4, 2021
  2. nissekaka

    nissekaka

    Joined:
    Oct 8, 2020
    Posts:
    5
    I'm thinking of solving it by moving the player up and down through script instead and removing the root motion. Is there any smart way to convert the Y root motion to a curve somehow and use that data for moving the player?
     
  3. nissekaka

    nissekaka

    Joined:
    Oct 8, 2020
    Posts:
    5
    So I found a different way of achieving similar results by removing the root motion from the ladder animations and instead using animator.MatchTarget(). However, I can only seem to make it work for one loop of the animation. My workaround is to call an event at the end of the animation that runs animator.InterruptMatchTarget() however this seems like a bad solution and also creates new problems where it sometimes isn't even doing anything or the slide animation jitters. I've tried to solve this by moving the events further from the end.

    The method inside LadderState, this is being called once at the start of the climbing animations:


    The method inside PlayerMovement:

    The first event fires UpdatePlayerPosition and the end event interrupts MatchTarget():


    Video of what is currently happening:
     
  4. nissekaka

    nissekaka

    Joined:
    Oct 8, 2020
    Posts:
    5
    Another ugly workaround, however t he slide isn't looking optimal, and when limiting the FPS to 20 it barely works so it becomes frame dependent. I wish I could just make the root motion accurate.

    This is what happens at 20 FPS:


    Added startNormalizedTime and targetNormalizedTime to tailor the numbers to the animations. It works okay at normal FPS, but the slide looks choppy (with lower times it sometimes stops completely):



    At higher FPS it works okay but far from optimal:

    The visual offset when using root motion would be much preferred than having a broken climbing system. :(
     
    Last edited: May 7, 2021
  5. bobadi

    bobadi

    Joined:
    Jan 3, 2019
    Posts:
    674
    I think you should try using lerp instead of matchtarget
     
  6. nissekaka

    nissekaka

    Joined:
    Oct 8, 2020
    Posts:
    5
    Could you elaborate on this? I tried using lerp with values from an animation curve but it still won't move the correct amount.

    I'm thinking since the climb up and down animations are each 16 frames long, if I move the character 0.0375 each frame after 16 frames it will have moved 0.6 which is the distance between two steps on the ladder.
     
  7. bobadi

    bobadi

    Joined:
    Jan 3, 2019
    Posts:
    674
    you are moving the root with the targetmatching, so moving with lerp in lateupdate would achieve the same since removed the root motion (if I understand this correctly)

    you don't need to link it to animation frames*, but something like you want to travel that 1 ladder step distance and if there's input you initiate it and if there's no input, you lerp up till that point, but if there's input (when it should stop), you initiate the next 1 step when the previous lerp is complete (edit: 1 step is actually 2 as you said)

    *okay you need to for the speed of animation, but the normalized time of the clip is the same relation as the distance travelled (so it could be directly as animator.play(statename, 0, normtime). I hope I'm not overthinking it)
     
    Last edited: May 8, 2021