Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Occasional Jittering when Physics Calculations Interact with Transform.Position Boundaries

Discussion in 'Scripting' started by Sengarden, Mar 26, 2021.

  1. Sengarden

    Sengarden

    Joined:
    Oct 8, 2020
    Posts:
    9
    Hello everyone,

    This is my first post to the forums, and I'm only a few months into my coding experience, so please forgive any obvious errors I may have overlooked in my theory or my code. I'm currently in the Junior Programmer Pathway, and have been working on my personal project. I'm at the stage of coding in object interactions and getting the player movement controls just right. Everything seems to be working almost exactly the way I want it to, except for one small problem.

    The game is an infinite runner style, with the player character currently being represented by a sphere running and jumping left and right as a series of (eventually) infinitely spawning terrain tiles made to look like a city street flow beneath their feet. To set boundaries on the side of the road before the player hits any buildings, I have coded in artificial barriers.

    This works perfectly fine if my player is grounded and is translating against the invisible wall, nothing catches or jitters. However, if my character is currently jumping through the air using physics and is either actively pushing against the wall or is simply jumping straight up and down along the xBound point, the player appears to occasionally catch on the invisible wall or jerk ever so slightly as it moves up and down. I thought perhaps the resetting of the player's y position in the SetBoundaries function was not being ordered properly with the physics calculation, somehow jerking/stuttering the y position from time to time, but when I tried to reorganize things, it didn't seem to help. It's a relatively small problem, but I want this to work as smoothly as possible. Any help would be greatly appreciated. Thanks!

    Code (CSharp):
    1. void Update()
    2.         {
    3.             JumpPlayer();
    4.             MovePlayer();
    5.             SetBoundaries();
    6.         }
    7.  
    8.         void JumpPlayer()
    9.         {
    10.             if (grounded)
    11.             {
    12.                 //If player is grounded, halt forces applied while gliding/jumping
    13.                 playerRb.velocity = Vector3.zero;
    14.  
    15.                 if (Input.GetKeyDown(KeyCode.Space))
    16.                 {
    17.                     //Apply Impulse force to player equal to constant vertical force and variable horizontal force relative to the last ground movement command
    18.                     playerRb.AddForce((Vector3.up * jumpUpForce) + (lastMotion * jumpSideForce), ForceMode.Impulse);
    19.                     grounded = false;
    20.  
    21.                     //Setting glide boools to control whether air-control should be allowed in a given direction until grounded
    22.                     if (lastMotion.x == 0)
    23.                     {
    24.                         glideNeutral = true;
    25.                     }
    26.  
    27.                     else if (lastMotion.x > 0)
    28.                     {
    29.                         glideNeutral = false;
    30.                         glidePositive = true;
    31.                     }
    32.  
    33.                     else
    34.                     {
    35.                         glideNeutral = false;
    36.                         glidePositive = false;
    37.                     }
    38.                 }
    39.             }
    40.  
    41.             else
    42.             {
    43.                 //If player collides with xBound, halt the effects of initialized horizontal jumpForce on glide ability and reset glide control to neutral
    44.                 if (transform.position.x <= -xBound || transform.position.x >= xBound)
    45.                 {
    46.                     glideNeutral = true;
    47.                 }
    48.  
    49.                 //If player attempts to move left, and player is currently further right than -xBound, apply appropriate force relative to initial jump force
    50.                 if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
    51.                 {
    52.                     if (transform.position.x > (-xBound + 0.1f))
    53.                     {
    54.                         if (glideNeutral)
    55.                         {
    56.                             glideVector.x = (steeringSpeed * horizontalInput * glideForce * neutralGlideForce);
    57.                             playerRb.AddForce(glideVector * Time.deltaTime);
    58.                         }
    59.  
    60.                         else if (glidePositive)
    61.                         {
    62.                             glideVector.x = (steeringSpeed * horizontalInput * glideForce * lastHorizontalInput);
    63.                             playerRb.AddForce(glideVector * Time.deltaTime);
    64.                         }
    65.                     }
    66.                 }
    67.  
    68.                 //If player attempts to move right, and player is currently further left than xBound, apply appropriate force relative to initial jump force
    69.                 if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
    70.                 {
    71.                     if (transform.position.x < (xBound - 0.1f))
    72.                     {
    73.                         if (glideNeutral)
    74.                         {
    75.                             glideVector.x = (steeringSpeed * horizontalInput * glideForce * neutralGlideForce);
    76.                             playerRb.AddForce(glideVector * Time.deltaTime);
    77.                         }
    78.  
    79.                         else if (!glidePositive)
    80.                         {
    81.                             glideVector.x = (steeringSpeed * horizontalInput * glideForce * -lastHorizontalInput);
    82.                             playerRb.AddForce(glideVector * Time.deltaTime);
    83.                         }
    84.                     }
    85.                 }
    86.             }
    87.         }
    88.  
    89.         void MovePlayer()
    90.         {
    91.             //Assign input variables
    92.             horizontalInput = Input.GetAxis("Horizontal");
    93.  
    94.             //Move player via transform.translate if grounded, save last movement commands for future jump/glide calculations
    95.             if (grounded)
    96.             {
    97.                 moveVector.x = (steeringSpeed * horizontalInput);
    98.                 transform.Translate(moveVector * Time.deltaTime);
    99.                 lastMotion = moveVector;
    100.                 lastHorizontalInput = horizontalInput;
    101.             }
    102.         }
    103.  
    104.         void SetBoundaries()
    105.         {
    106.             //Halt glide/jump velocity if player position reaches xBounds
    107.             if (transform.position.x >= xBound || transform.position.x <= -xBound)
    108.             {
    109.                 playerRb.velocity = new Vector3(0, playerRb.velocity.y, 0);
    110.             }
    111.  
    112.             //Halt transform.translate commands if player position reaches xBounds
    113.             if (transform.position.x >= xBound)
    114.             {
    115.                 transform.position = new Vector3(xBound, transform.position.y, transform.position.z);
    116.  
    117.             }
    118.  
    119.             if (transform.position.x <= -xBound)
    120.             {
    121.                 transform.position = new Vector3(-xBound, transform.position.y, transform.position.z);
    122.             }
    123.         }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,336
    Welcome!

    Traditionally in Unity when the camera follows a Rigidbody object, you want the camera to update in FixedUpdate()

    Same goes for doing Physics stuff, like adding forces and whatnot: do it in FixedUpdate()

    The reason is, if you do physics stuff in Update() then you become beholden to the framerate, and your game will play differently on different machines.

    You can google more about the details of this, but it all stems from this timing diagram here:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    Update() gets run "as much as possible"-ish, whereas Physics (FixedUpdate()) attempts to follow the prescribed physics update rate, so they're not ever necessarily the same.

    All that said, there could be some other mechanism making it jitter anyway, but at least you should try and get the FixedUpdate vs Update working properly, just so to eliminate that known issue.
     
  3. Sengarden

    Sengarden

    Joined:
    Oct 8, 2020
    Posts:
    9
    Thanks! And thank you for all the help and information.

    I did try calling the JumpPlayer function from FixedUpdate instead, as well as moving the playerRb.velocity neutralizer for boundary setting to the end of that function, but to be honest, it actually made the issue worse, which I don’t understand. I’m wondering if my issue is in fact camera related. It’s currently just floating behind the player as a child object but I’m thinking I should just write a Follow script instead and have it update position in FixedUpdate. Will report back later.
     
  4. Sengarden

    Sengarden

    Joined:
    Oct 8, 2020
    Posts:
    9
    Okay, so I tried making a new followPlayer script for my camera:

    Code (CSharp):
    1. public class FollowPlayer : MonoBehaviour
    2. {
    3.     GameObject player;
    4.     Vector3 cameraOffset;
    5.  
    6.     // Start is called before the first frame update
    7.     void Start()
    8.     {
    9.         transform.eulerAngles = new Vector3(20, 0, 0);
    10.         player = GameObject.Find("Player");
    11.         cameraOffset = new Vector3(0, 2, -2.75f);
    12.     }
    13.  
    14.     // Update is called once per frame
    15.     void Update()
    16.     {
    17.         transform.position = player.transform.position + cameraOffset;
    18.     }
    I first tried running the follow command in Update, this resulted in the player now appearing to constantly vibrate slightly whenever it is grounded and translating left or right. This effect stops whenever the player jumps and begins moving via physics, or whenever the player is pressed up against the boundary marker and is held in place. Unfortunately, it did not seem to fix the much more slight, occasional stuttering when jumping along/against the boundary either. When I put both the follow command from the FollowPlayer script as well as the same physics commands from the playercontroller script into Fixed Update that I tried earlier, this results in extreme player object stuttering while moving. Starting to wonder whether it's worth the trouble! But I feel like if it's something I notice, it's something the player would notice too, and perhaps more so once the primitives are replaced with real assets. Just still feeling a bit lost on what would be causing such an isolated issue when my code seems to prevent the addition of any force interactions whenever the player is on or past the barrier. No idea what the barrier would be interacting with to produce such an occasional, just barely noticeable effect.
     
  5. ZDTTUTA

    ZDTTUTA

    Joined:
    Mar 20, 2021
    Posts:
    7
    I've had Issues with modifying too many different GameObjects linked to 1 parent. I.E. Empty Parent --> Character --> Camera --> Gun.

    I was trying to add weapon sway to the gun, but since there are scripts already acting on the parents of "Gun" it was glitchy, and it oscillated like crazy. I deleted the part of the script that modified the Z rotation, and since have tried to run scripts on the fewest possible children of a given GameObject.

    Especially when modifying position. The same happened with my Camera Movements becoming jittery, that's when I started to reduce the number of GameObjects with Scripts attached to them.

    TLDR; condense your scripts to the fewest possible GameObjects. Try to move only with Physics- moving with transform, or a combo of physics and transform can lead to jittering I've noticed.

    I'm not saying you can't modify with transform, I'm making a game relying solely on transform and no physics.
     
    Last edited: Mar 28, 2021
    alexeu likes this.
  6. Sengarden

    Sengarden

    Joined:
    Oct 8, 2020
    Posts:
    9
    Thanks for the tips! I did originally have the camera as a child object, but before performing the experiment with the follow script, I removed it from the player object, so I don't think that's the problem. But I'll definitely keep that in mind for the future!

    I think you're right about interactions between physics and translation though. To be honest, it was working well when the only functionality was having constant access to left and right translation and uniform y axis jumping via physics. It all started to get a bit funny when I started implementing the glideForce mechanics - calculating initial jump velocity and direction from my most recent translation instead of always jumping straight ahead, then adding more force on top of that at a particular rate to control glide speed and direction, thereby hosting multiple ways of moving along the same axis depending on the player's position on the y axis. Might just be too much for the system to handle. I think I might try setting up grounded movement with physics instead and tweak ground and player physic materials until it feels similar to my translation coding.
     
  7. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    How did you configure the rigidbody? What's its interpolation mode?

    It might very well be that you mess up the physics movement when you manipulate the transform directly.

    I beg to differ.

    If a camera is updated in FixedUpdate, its transform values are calculated with the physics steps which makes everything that's based on the various frame-update routines (Update, LateUpdate, most of a coroutine's, etc) appear to jitter, whereas it'll be the camera itself that's actually experiencing and causing this effect.

    The camera should always be updated in any of the frame-based update routines, more specifically after all values which the camera's transform calculations depend on hold their "final" values for that particular frame. Otherwise, it might appear to have delayed/wrong motion.

    It's generally best to place it inside LateUpdate, which happens to run at the very end of the frame-based game logic. Update, most of the coroutines resumption points etc come first, i.e. unless you move relevant game logic into LateUpdate, it'll be safe to just place your camera logic there, otherwise, make sure it's the last that updates even in LateUpdate.
     
    alexeu likes this.
  8. Sengarden

    Sengarden

    Joined:
    Oct 8, 2020
    Posts:
    9
    I've tried implementing this. I can't tell the difference personally, but your logic sounds clear, so I'll take your word for it. As for the effects of implementing all physics-based movement, it worked! Finally have everything working the way I wanted it to. Added wall objects on the sides of the road for barriers instead of using artificial ones in my script. Still use the collision detector to affect glideForces and such. Just need to tweak variables as I add in art assets to suit my tastes. Thanks again for everyone's help!

    Here's the finished script if anyone's interested:

    Code (CSharp):
    1. void Start()
    2.     {
    3.         //Assign component variables and set bools
    4.         playerRb = GetComponent<Rigidbody>();
    5.         grounded = true;
    6.         vertJumpVector = Vector3.up * jumpUpForce;
    7.     }
    8.  
    9.     void Update()
    10.     {
    11.         JumpPlayer();
    12.     }
    13.  
    14.     private void FixedUpdate()
    15.     {
    16.         MovePlayer();
    17.     }
    18.  
    19.     void JumpPlayer()
    20.     {
    21.  
    22.         if (grounded)
    23.         {
    24.             if (Input.GetKeyDown(KeyCode.Space))
    25.             {
    26.                 //Apply Impulse force to player equal to constant vertical force and variable horizontal force relative to the last ground movement command
    27.                 playerRb.AddForce(vertJumpVector, ForceMode.Impulse);
    28.                 grounded = false;
    29.  
    30.                 //Setting glide boools to control whether air-control should be allowed in a given direction until grounded
    31.                 if (lastGroundVelocity.x == 0)
    32.                 {
    33.                     glideNeutral = true;
    34.                 }
    35.  
    36.                 else if (lastGroundVelocity.x > 0)
    37.                 {
    38.                     glideNeutral = false;
    39.                     glidePositive = true;
    40.                 }
    41.  
    42.                 else
    43.                 {
    44.                     glideNeutral = false;
    45.                     glidePositive = false;
    46.                 }
    47.             }
    48.         }
    49.  
    50.     }
    51.  
    52.     void MovePlayer()
    53.     {
    54.         //Assign input variables
    55.         horizontalInput = Input.GetAxis("Horizontal");
    56.  
    57.         //Speed limit controls
    58.         if (playerRb.velocity.x > maxSpeed)
    59.         {
    60.             playerRb.velocity += new Vector3(-(playerRb.velocity.x - maxSpeed), 0, 0);
    61.         }
    62.  
    63.         if (playerRb.velocity.x < -maxSpeed)
    64.         {
    65.             playerRb.velocity += new Vector3(-(playerRb.velocity.x + maxSpeed), 0, 0);
    66.         }
    67.  
    68.         if (wallCollision)
    69.         {
    70.             glideForce *= wallCollisionGlideMod;
    71.         }
    72.  
    73.         //Move player via transform.translate if grounded, save last movement commands for future jump/glide calculations
    74.         if (grounded)
    75.         {
    76.             moveVector.x = (steeringSpeed * horizontalInput);
    77.             playerRb.AddForce(moveVector * Time.deltaTime);
    78.             lastGroundVelocity = playerRb.velocity;
    79.         }
    80.  
    81.         else
    82.         {
    83.             //If player attempts to move left, and player is currently further right than -xBound, apply appropriate force relative to initial jump force
    84.             if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
    85.             {
    86.                 if (glidePositive || glideNeutral)
    87.                 {
    88.                     glideVector.x = (steeringSpeed * horizontalInput * glideForce * lastGroundVelocity.x) + -minGlide;
    89.                     playerRb.AddForce(glideVector * Time.deltaTime);
    90.                 }
    91.             }
    92.  
    93.             //If player attempts to move right, and player is currently further left than xBound, apply appropriate force relative to initial jump force
    94.             if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
    95.             {
    96.                 if (!glidePositive || glideNeutral)
    97.                 {
    98.                     glideVector.x = (steeringSpeed * horizontalInput * glideForce * -lastGroundVelocity.x) + minGlide;
    99.                     playerRb.AddForce(glideVector * Time.deltaTime);
    100.                 }
    101.             }
    102.         }
    103.     }
    104.     private void OnCollisionEnter(Collision collision)
    105.     {
    106.         //grounded bool control via tagged collision
    107.         if (collision.gameObject.CompareTag("Ground"))
    108.         {
    109.             grounded = true;
    110.             wallCollision = false;
    111.         }
    112.  
    113.         if (!grounded && collision.gameObject.CompareTag("Wall"))
    114.         {
    115.             wallCollision = true;
    116.         }
    117.     }
     
  9. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    You won't be able to tell the difference between camera movement in physics vs camera movement in update until you have other update-based objects as a visual reference.

    Once you add other objects that are visible while the camera updates in FixedUpdate, it'll look as if those update-based objects have minor-major jitter problems - even though they don't have it.

    The inserv happens when the camera updates in Update/Coroutine/LateUpdate and a physics-based objects is in sight. If it doesn't interpolate/extrapolate its position, sooner or later it'll either be rendered at the same position multiple times (no FixedUpdate before the upcoming Update) or it appears to make an irregular leap forward (multiple FixedUpdates before the upcoming Update).

    That's compensated by the interpolation mode. The rigidbody keeps its physics position, but it allows the transform (and hence everything that's going to be rendered) to adjust its position based on the selected mode in order to have it rendered at the position you'd expect for that particular update.

    Without interpolation, the transform's position is simply always going to be the rigidbody's position.

    As mentioned earlier, the reason is that the ratio of Update and FixedUpdate is usually not guaranteed to be 1:1. It can vary based on how many physic-cycles are required in order to catch up with Update.

    That's great, however, now you've got a new problem.
    It's better and even highly recommended to read input in Update.

    I could imagine that your code would easier to understand and easier to implement if you separated input and character logic. For example, your character would only expose his abilities like jumping and moving forward etc, and something else instructs your character to Jump when it needs to - let it be a a player input component, a replay-component, an AI component or the an alien from a far-away planet.
     
  10. Sengarden

    Sengarden

    Joined:
    Oct 8, 2020
    Posts:
    9
    Thank you for the warning, but I'm afraid I don't quite understand exactly what you're suggesting. Are you saying my Character Controller script should be used only for FixedUpdate movement commands, then I should have an entirely different script for recording and logging player input through normal Update that accesses and runs the FixedUpdate functions on the first script?