Search Unity

scaling animation causes object end position to be inaccurate [causes/fixes]

Discussion in 'Animation' started by Boogafreak, Sep 13, 2018.

  1. Boogafreak


    Oct 2, 2017

    This is my experience and issues - it is somewhat long, but if it is interesting to you and you read it, please try to also help with the questions if you can :)

    Chapter 0 : The animation rounding issue
    I have square tiles (basically sprites), but their actual size is determined in code in relation to the screen size, and I'd like their size and local position to be integers so I have control over their exact tile local position.

    So if my tile prefab is 90 pixels, and my actual tile (on a specific screen size) should be 86, I scale the tile by 86/90, and set it's (integer) location.
    This achieves the correct scaling of the animation too, as the animation should move the tile 90 pixels, but modifies this by the scaling factor.

    However, due to decimal calculations in scale (my guess), instead of moving the tile 86 pixels (90 * (86 / 90)),
    the tile moves 90 * 0.9555556 = 86.000004 .
    This gets even weirder as it seems this is not the true value of animation movement either.
    The value changes from one run to another on the same debug session/screen size etc..
    I get values of animation movement between 86.001 to 86.01 .

    Question 0 : How do other programmers get rid of this rounding error on animation scaling? Am I doing something wrong?

    Chapter 1 : Fixing the rounding issue

    The working solution :
    In my game for this example, 2 tiles are switching location, and when they are finished their up/down animation, they switch back.

    Before doing the second switch , I check that in both tiles animation is in idle state.
    After the second switch, I also check that in both tiles animation is in idle state before fixing the location with the following code :

    Code (CSharp):
    1. previous.transform.localPosition = new Vector3(Mathf.RoundToInt(previous.transform.localPosition.x) , Mathf.RoundToInt(previous.transform.localPosition.y), 0f);
    2. chosen.transform.localPosition = new Vector3(Mathf.RoundToInt(chosen.transform.localPosition.x) , Mathf.RoundToInt(chosen.transform.localPosition.y), 0f);
    This correctly places the tiles in their correct (integer) location.

    The better solution (not working, with questions) :
    After finding the above and seeing that it worked, I realized I would have to do this for many other tile moves, so it would be best to place similar code in either the OnStateExit() of the up/down state of the animation or in the OnStateEnter() method of the Idle State of the animation.
    For some reason, both fail with several issues.
    Let's talk about placing the location fix the OnStateEnter() method of the Idle State, as it is more correct to do IMHO, and there is only one state that is involved.

    Here's my code from the Idle SMB :
    Code (CSharp):
    1. override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    2.     {
    3.         bool this_animator = false;
    4.         Tile tile = null;
    5.         BoardManager board_manager = GameObject.Find("BoardManager").GetComponent<BoardManager>();
    7.         tile = board_manager.GetChosenTile();
    8.         this_animator = animator.Equals (tile.GetComponent<Animator> ());
    9.         if (this_animator)
    10.         {
    11.             board_manager.FixPosChosenTile ();
    12.             return;
    13.         }
    15.         tile = board_manager.GetPreviousTile();
    16.         this_animator = animator.Equals (tile.GetComponent<Animator> ());
    17.         if (this_animator)
    18.         {
    19.             board_manager.FixPosPreviousTile ();
    20.             return;
    21.         }
    22.     }
    And from Board Manager:
    Code (CSharp):
    1. public Tile GetChosenTile ()
    2.     {
    3.         return (tiles [index_x, index_y].GetComponent<Tile> ());
    4.     }
    6.     public Tile GetPreviousTile ()
    7.     {
    8.         return (tiles [previousSelected_x, previousSelected_y].GetComponent<Tile> ());
    9.     }
    11.     public void FixPosChosenTile ()
    12.     {
    13.         Tile t = GetChosenTile ();
    14.         Debug.Log("FixPosChosenTile from" + t.transform.localPosition.y + " to " + Mathf.RoundToInt(t.transform.localPosition.y));
    15.         t.transform.localPosition = new Vector3(Mathf.RoundToInt(t.transform.localPosition.x) , Mathf.RoundToInt(t.transform.localPosition.y), 0f);
    16.         Debug.Log("FixPosChosenTile result : " + t.transform.localPosition.y);
    17.     }
    19.     public void FixPosPreviousTile ()
    20.     {
    21.         Tile t = GetPreviousTile ();
    22.         Debug.Log("FixPosPreviousTile from" + t.transform.localPosition.y + " to " + Mathf.RoundToInt(t.transform.localPosition.y));
    23.         t.transform.localPosition = new Vector3(Mathf.RoundToInt(t.transform.localPosition.x) , Mathf.RoundToInt(t.transform.localPosition.y), 0f);
    24.         Debug.Log("FixPosPreviousTile result : " + t.transform.localPosition.y);
    25.     }
    My first issue is finding out which tile is activating the Idle State OnStateEnter() method.
    It should be simple to check out which tile I'm on, but from the parameters I can only see "animator" to compare with, so I unwillingly went with that and compared animators to check which tile I was in (this is most of the above code).
    I found the tile, as only 2 tiles should be moving, but the code to find the tile is ugly.
    So, that worked, but -
    Question 1: How do I know which object's OnStateEnter() I'm in? The above code compares the animator of the tiles to the animator of OnStateEnter(). Is there some other solution?

    My second issue is that the position fix isn't working.
    The FixPosXXXXX functions work correctly, and by the logs I would think it worked, but the actual tiles are not in their rounded position in the editor.

    Question 2: Why are my tiles not in the rounded position (or even their pre-rounded position!!) when their transform.localPosition.y seems to be rounded in the logs??

    Here's a screenshot of this happening after one swap.
    Only Y location is changing. The first tile Y location should be 47 - it is rounded to 47 in the log, but the tile in the editor has a different Y is 46.9993 :(

  2. Deeeds


    Mar 15, 2018
    Have you looked at the legacy Animation Component? This might be much more suited to what you're trying to do, and simpler/funner to work with.

    I'm sorry if this isn't helpful to the root problems you're having. It may well be misguided advice. I've switched everything I'm doing to the Animation Component (legacy) and have really enjoyed it. Much more controllable (ironically) and much easier to work with, and more reliable, and an order of magnitude easier to conceive of and then execute new ideas with.
  3. Deeeds


    Mar 15, 2018
    It also might be that you're only two issues away from getting this rig to work. As per your specific questions. But what I've found... every problem I solve within the Animator Controller way of doing things reveals two more problems/issues/oddities.
  4. Boogafreak


    Oct 2, 2017
    Thanks -
    I'm trying to learn how to use Unity, so I'd rather learn using components like the animator.

    I CAN force this to work (see the 2 code lines that are the current solution), but it should be done differently -
    The rounding error begs an explanation, and the position should be set in the OnStateEnter().

    So this is something I hope to solve but it isn't a failure to find a workaround :)
  5. Deeeds


    Mar 15, 2018
    Animator wasn't conceived well. Is the feeling I get. And you've no doubt found the dozens of oddities in the workflow of editing animations. That's just the tip of the iceberg. The manner in which it responds to code manipulation is truly next level byzantine and bureaucratic.