Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Stuttering physics2D framerate fixed by changing Fixed Timestep, but is there a better way?

Discussion in 'Scripting' started by tfishell, Apr 26, 2019.

  1. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    (I think I fixed my issues on my own but if somebody could chime in with advice, I'd appreciate it.)

    I had an issue with a choppy physics2D framerate (running via FixedUpdate) on a specific rigidbody2D (affecting only the rotation), and I was able to fix it by increase the rate of Fixed Timestep from (iirc) 0.02 to 0.005. It seems increasing the fixed timestep interval also increases the "power" of physics because the timestep is running at a greater/more frequent interval? I'm working on an "angry birds"-style game, and my sprintjoint2D that seems to have increased "throwing power" at a higher interval so I was curious. (Obviously I can compensate by changing the sprjoint2D settings, lowering the tension so the object doesn't fly so far.)

    I can only assume the rigidbody2D was choppy/stuttering because of the code I'm using (again, affecting only the character's rotation). I'm treating the object like an arrow flying through air, and it wasn't stuttering until I put in code to make the object turn in the direction it was flying.

    Running the code via FixedUpdate it stutters; running the code via Update it seems to be smooth but sometimes the calculated rotation amount is set to zero which causes the object to rotate out of whack several times a second. (degrees: 125, 126, 127, 0, 128 ...) (I can avoid the "0" amount but that just goes back to the FixedUpdate effect of being choppy because it's skipping frames)

    Should I just stick with the increased Fixed Timestep since that seems to work (though affects the physics "power"), or does anyone here have different suggestions?

    Code (CSharp):
    1. //for physics calculations
    2. void FixedUpdate()
    3. {
    4.     //constantly apply the right rotation to jonah
    5.     if (ArrowController.canClick == false && !hitGround)
    6.     {
    7.         //change jonah direction/rotation
    8.         RotationChangeWhileFlying();
    9.     }
    10. }
    11.  
    12. //change the angular velocity depending on jonah's speed in air
    13. void RotationChangeWhileFlying()
    14. {
    15.     //get current position
    16.     currPoint = transform.position;
    17.  
    18.     //get the direction (from previous pos to current pos)
    19.     currDir = prevPoint - currPoint;
    20.  
    21.     //normalize the direction
    22.     currDir.Normalize();
    23.        
    24.     //get angle whose tan = y/x, and convert from rads to degrees
    25.     float rotationZ = Mathf.Atan2(currDir.y, currDir.x) * Mathf.Rad2Deg;
    26.  
    27.     //rotate z based on angle above + an offset (currently 90)
    28.     transform.rotation = Quaternion.Euler(0, 0, rotationZ + rotateOffset);
    29.  
    30.     //store curr position as prev position for next frame
    31.     prevPoint = currPoint;
    32. }
    Here are some reference gifs (Imgur converted them automatically to MP4s), though it may be hard to tell normal vs stuttering because the recording software affected the recorded framerate.

    Working version, running in FixedUpdate, increased Fixed Timestep from 0.02 to 0.005:
    https://i.imgur.com/WRwqW5U.mp4

    Stuttering framerate, running in FixedUpdate, Fixed Timestep is normal at 0.02:
    https://i.imgur.com/KpJFjHq.mp4

    Framerate seems kind of smooth but rotation goes "wacky"/sometimes goes back to 0, running in Update, Fixed Timestep is normal at 0.02:
    https://i.imgur.com/Z6I337O.mp4
     
  2. RockyWallbanger

    RockyWallbanger

    Joined:
    Mar 16, 2014
    Posts:
    85
    I suspect this has something to do with modifying the transform component directly in your RotationChangeWhileFlying method. Try replacing
    Code (csharp):
    1. currPoint = transform.position;
    2. //with
    3. currPoint = rigidBody2D.position;
    and
    Code (csharp):
    1. transform.rotation = Quaternion.Euler(0, 0, rotationZ + rotateOffset);
    2. //with
    3. rigidBody2D.MoveRotation(Quaternion.Euler(0, 0, rotationZ + rotateOffset));
     
    tfishell likes this.
  3. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Thanks, I think that was really useful! However I did have to use (and they both seem to avoid choppy movement I was having)
    Code (CSharp):
    1. rb.rotation = rotationZ + rotateOffset;
    2. //instead of
    3. rb.MoveRotation(rotationZ + rotateOffset);
    I think I can stick with rb.rotation fine (I think it works as smooth as MoveRotation), but if you have time, consider helping me understand the following and below: MoveRotation doesn't seem to give control back to the physics engine right away when a collision happens. rb.rotation seems to allow for a lot more rotation changes right away, whereas (it seems) MoveRotation sort of sticks a rotation upon an initial collision. The difference is hard to explain and subtle.

    I do have some gifs, but they may not provide much help: MoveRotation , rb.rotation
    Maybe this image helps:


    Like I said above I think rb.rotation will work fine, but I'm curious: if you sort of understand what I'm saying, is there a way to give back control to the "natural" physics engine instantaneously once a collision happens, when there's no need for MoveRotation anymore? (the same effect as just using rb.rotation) Like,
    Code (CSharp):
    1. //if there is a collision
    2. if (hitGround)
    3. {
    4. //let player "bounce around" and rotate accordingly/let engine handle rotation again
    5. }
    Thanks again for the help.
     
  4. RockyWallbanger

    RockyWallbanger

    Joined:
    Mar 16, 2014
    Posts:
    85
    There's no reason you can't set the rotation property directly, especially if it's producing the desired behavior. MoveRotation allows the physics engine to smooth the rotation whereas setting the rotation property directly... well sets the rotation directly but the transform won't be updated until the next update. I'm not sure why the behavior is different as subtle as it is. As for turning control over to the physics engine, you basically just need to stop setting the rotation directly when you've collided. Set a hasCollided bool to false in your OnCollisionEnter2D method and include it in your check before calling RotationChangeWhileFlying.
     
    tfishell likes this.
  5. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    I was already doing this with a "hitGround" bool in OnCollisionEnter2D, bypassing RotationChangeWhileFlying() when hitGround = true, but I don't think OnCollisionEnter2D was updating quick enough or something (or maybe because OCE2D is called after Rotation, I may test that). I guess the Order of Execution for OCE2D plays a role here but I don't 100% understand that.

    In any case, if I call a method from Update and that method creates a ray that manages "hitGround", MoveRotation seems to work the same as, or close to, rb.rotation, instantaneously turning physics control back to the engine. (Right now I just have one ray but I figure I can put 3 on each side.)

    I may do that because the "safe side" seems to be managing rotation indirectly, and MoveRotation may be slightly smoother than rb.rotation. (update: after doing some testing I may stick with rb.rotation because MoveRotation still seems finicky even with a raycasting check managing it. We'll see.) Thanks.