Search Unity

Addforce() values vary with framerate

Discussion in 'Scripting' started by SkillBased, Apr 6, 2016.

  1. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    I'm trying to wrap my head around why Addforce() would increase its effect on Rigidbody's when the framerate is lower.

    I made a quick script to slap onto a GameObject and test it out:

    Code (CSharp):
    1. public class Controller : MonoBehaviour
    2. {
    3.     bool upForce;
    4.  
    5.     void Start()
    6.     {
    7.         Application.targetFrameRate = 30;
    8.         QualitySettings.vSyncCount = 0;
    9.     }
    10.  
    11.     void FixedUpdate()
    12.     {
    13.         if (upForce)
    14.         {
    15.             GetComponent<Rigidbody2D>().AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
    16.         }
    17.     }
    18.  
    19.     void Update()
    20.     {
    21.         if (Input.GetKeyDown(KeyCode.Space))
    22.             upForce = true;
    23.         else
    24.             upForce = false;
    25.     }
    26. }
    If you adjust the framerate to 60, you'll see the AddForce() is applied less than at 30fps.

    As an alternative I tested out using velocity to effect the GameObject, replacing AddForce with:

    Code (CSharp):
    1. GetComponent<Rigidbody2D>().velocity = new Vector2(0, 10);
    And this behaves the same regardless of the framerate. Why would velocity act deterministically regardless of FPS, but not Addforce() ?
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Looks like you are skipping inputs. Remember that FixedUpdate does not run every Update cycle. Here is a sample of how it might run.
    • User hits space bar
    • Update runs, upForce is set to true
    • Update runs, upForce is set to false
    • FixedUpdate runs, no force is applied
    Here is how I would adjust the code to compensate.

    Code (CSharp):
    1. public class Controller : MonoBehaviour
    2. {
    3.     bool upForce;
    4.  
    5.     void Start()
    6.     {
    7.         Application.targetFrameRate = 30;
    8.         QualitySettings.vSyncCount = 0;
    9.     }
    10.  
    11.     void FixedUpdate()
    12.     {
    13.         if (upForce)
    14.         {
    15.             GetComponent<Rigidbody2D>().AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
    16.             upForce = false;
    17.         }
    18.     }
    19.  
    20.     void Update()
    21.     {
    22.         if (Input.GetKeyDown(KeyCode.Space))
    23.             upForce = true;
    24.     }
    25. }
    This does mean that some inputs will still be missed if the player can tap keys faster then the physics frame rate. But that's normally not an issue across most genres of game.
     
  3. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    Thanks for your answer. Your adjustment solved the problem, but it got me thinking about the relationship between Update and FixedUpdate and something didn't add up (in terms of how I thought things were working).

    If I got what you said right... what you're saying is if FixedUpdate runs at the default 50FPS - and meanwhile Update runs at 60FPS - and if the user hits the space key between frame 51-60 at 60FPS, force is never applied in FixedUpdate?

    That indeed was a problem, but not the exact problem I was concerned with. Main my problem was that the force was multiplied at lower FPS.

    So... I went back to my test script and added a Debug.Log outputing upForce in the FixedUpdate, and lowered the FPS to 10:

    Code (CSharp):
    1. public class Controller : MonoBehaviour
    2. {
    3.     bool upForce;
    4.  
    5.     void Start()
    6.     {
    7.         Application.targetFrameRate = 10;
    8.         QualitySettings.vSyncCount = 0;
    9.     }
    10.  
    11.     void FixedUpdate()
    12.     {
    13.         if (upForce)
    14.         {
    15.             Debug.Log(upForce);
    16.             GetComponent<Rigidbody2D>().AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
    17.         }
    18.     }
    19.  
    20.     void Update()
    21.     {
    22.         if (Input.GetKeyDown(KeyCode.Space))
    23.             upForce = true;
    24.         else
    25.             upForce = false;
    26.     }
    27. }
    My theory was that if Update is running 10 times and FixedUpdate runs 50 times, for every Update cycle you have 5 cycles of FixedUpdate running. Therefore, if upForce is true on one update cycle, it will be read as true for 5 FixedUpdate cycles, thus multiplying force in AddForce by 5.

    That's exactly what happened (Console output below):

    True
    UnityEngine.Debug:Log(Object)
    True
    UnityEngine.Debug:Log(Object)
    True
    UnityEngine.Debug:Log(Object)
    True
    UnityEngine.Debug:Log(Object)
    True
    UnityEngine.Debug:Log(Object)

    When you adjust the FPS back to 60:

    Code (CSharp):
    1.         Application.targetFrameRate = 60;
    The Console output returns true only once, or else is skipped (because the input was missed in FixedUpdate (it occurred on frame 51-60 in Update).

    Moral of the story that I learned is you have to think really carefully about the interplay between code in Update (which changes with variable FPS) and elements it impacts in FixedUpdate. I thought I had my head wrapped around this already!

    Thanks a lot for your clarification on this and getting me thinking.
     
    awsapps and Kiwasi like this.
  4. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    I just thought of another thing. Maybe you can help me out with this as well.

    If FixedUpdate runs at 50 cycles per second and Update runs at 60 cycles, then if input is read on (say) the 51st frame of the Update cycle, there will be 10 frames of lag between when my input registers and the force is applied on the next FixedUpdate cycle.

    If I'm using the force for character movement (say, jumping) then this would result in not only controls that feel unresponsive, but sometimes feel responsive and at other times laggy.

    What a nightmare! Am I getting this right. Does FixedUpdate pause for 10 frames to sync with Update or does the next FixedUpdate cycle run on the 51st Update cycle?

    ... I just tested this and it appears the latter is the case. Now when I think about it it makes sense because FixedUpdate doesn't pause for anything. That's the whole point of it being at a fixed interval. I still don't know how to think about how things sync up between FixedUpdate and Update though.
     
    Last edited: Apr 7, 2016
  5. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Unity typically won't stop FixedUpdate for ten frames in a row. Internally Unity keeps track of game time and a physics time. Each frame it adds to game time. Each physics frame it adds to physics time.

    If game time is higher then physics time + fixedDeltaTime the engine will run a physics frame. It will keep running physics frames until the physics time is caught up.

    You can see this effect by debugging Time.time in FixedUpdate and Update.