Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Rigidbody interpolate causes sudden stop of player

Discussion in 'Physics' started by cydonia, Dec 29, 2023.

  1. cydonia

    cydonia

    Joined:
    Apr 7, 2013
    Posts:
    11
    I have a bit of a conundrum.
    I have a player controller which is a top down Newtonian spaceship setup. It's like playing on an air hockey table, all objects including the player retain their momentum and rotation on a single plain. Now when the player is flying along side other ships in motion, it looks like the other ships are jittering. It's the player character actually jittering. Setting the rigidbody to interpolate fixes this issue and everything looks great. However when interpolate is on and the player is coasting at speed, they will suddenly act as if they moved into molasses. They come to a near stop and the thrust inputs for speeding up and slowing down do almost nothing. Whats even stranger about this is the speed readout and the speed of the ridged body in the inspector does not change. It says it's still going the speed it was, but it's physically slower in both the game and scene to basically a slow crawl.

    Ive found moving in one direction along the Z axis seems to prevent this more or less, but moving along the X axis, or turning, or doing basically anything but move in a straight line along the Z axis causes this issue. even with all restraints removed it still happens. I've been trying it out in an empty scene, moved things, played around with the player controller. Nothing seems to help. It also seems to get worse close to world origin, which having worked on a massive open game before, I found that counter intuitive. The camera is just a camera, it follows the player and as far as I can tell plays no role in this issue. It's the players rigidbody that's the issue here.

    I would embed a clip of what I'm talking about but I can't.

    Right now my only choice is to just accept the massive amounts of jittering because interpolate makes it unplayable. which is not ideal.

    My player controller is a mess as it handles a lot of stuff, but I'll include it anyway as a reference. just ignore everything but stuff relevant to this issue.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public class TurretIgnoreColliderPair
    7. {
    8.     public Transform turret;
    9.     public List<Collider> ignoreColliders;
    10. }
    11.  
    12. public class PlayerController : MonoBehaviour
    13. {
    14.     public float rotationSpeed = 5f;
    15.     public float normalThrustSpeed = 10f;
    16.     public float boostedThrustSpeed = 20f; // Speed for the boost
    17.     public float strafeSpeed = 5f;
    18.  
    19.     public List<TurretIgnoreColliderPair> turretIgnoreColliders = new List<TurretIgnoreColliderPair>(); // List of turret-ignore collider pairs
    20.  
    21.     public float turretRotationSpeed = 20f;
    22.     public float turretRotationLimit = 10f;
    23.     public float decelerationFactor = 0.95f; // Adjust this to control the deceleration rate
    24.  
    25.     // Add your particle systems here
    26.     public ParticleSystem[] particleSystems;
    27.  
    28.     private Rigidbody rb;
    29.     private Quaternion initialRotation;
    30.  
    31.     void Start()
    32.     {
    33.         rb = GetComponent<Rigidbody>();
    34.         initialRotation = transform.rotation;
    35.     }
    36.  
    37.     void Update()
    38.     {
    39.         float horizontalInput = Input.GetAxis("Horizontal");
    40.  
    41.         // Apply torque for continuous rotation
    42.         rb.AddTorque(Vector3.up * horizontalInput * rotationSpeed);
    43.  
    44.         RotateTurrets();
    45.  
    46.         // Adjust thrust speed based on space bar input
    47.         float currentThrustSpeed = Input.GetKey(KeyCode.Space) ? boostedThrustSpeed : normalThrustSpeed;
    48.  
    49.         // Apply thrust based on vertical input and current thrust speed
    50.         float verticalInput = Input.GetKey(KeyCode.Space) ? 1f : Input.GetAxis("Vertical");
    51.         Vector3 thrust = transform.forward * verticalInput * currentThrustSpeed * Time.deltaTime;
    52.         rb.AddForce(thrust, ForceMode.Acceleration);
    53.  
    54.         if (Input.GetKey(KeyCode.Q))
    55.         {
    56.             Vector3 strafeLeft = -transform.right * strafeSpeed * Time.deltaTime;
    57.             rb.AddForce(strafeLeft, ForceMode.Acceleration);
    58.         }
    59.  
    60.         if (Input.GetKey(KeyCode.E))
    61.         {
    62.             Vector3 strafeRight = transform.right * strafeSpeed * Time.deltaTime;
    63.             rb.AddForce(strafeRight, ForceMode.Acceleration);
    64.         }
    65.  
    66.         transform.position = new Vector3(transform.position.x, 0f, transform.position.z);
    67.  
    68.         // Reset the rotation when 'Z' key is pressed
    69.         if (Input.GetKeyDown(KeyCode.Z))
    70.         {
    71.             StartCoroutine(ResetAndToggleParticles());
    72.         }
    73.     }
    74.  
    75.     void RotateTurrets()
    76.     {
    77.         foreach (var pair in turretIgnoreColliders)
    78.         {
    79.             Transform turret = pair.turret;
    80.             List<Collider> ignoreColliders = pair.ignoreColliders;
    81.  
    82.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    83.             RaycastHit hit;
    84.  
    85.             // Use Physics.Raycast to ignore specific colliders
    86.             if (Physics.Raycast(ray, out hit) && !ignoreColliders.Contains(hit.collider))
    87.             {
    88.                 Vector3 targetPosition = new Vector3(hit.point.x, turret.position.y, hit.point.z);
    89.  
    90.                 float currentAngle = turret.eulerAngles.y;
    91.                 float targetAngle = Quaternion.LookRotation(targetPosition - turret.position).eulerAngles.y;
    92.  
    93.                 float angleDifference = Mathf.DeltaAngle(currentAngle, targetAngle);
    94.                 float rotationSpeedClamped = Mathf.Clamp(angleDifference, -turretRotationLimit, turretRotationLimit);
    95.  
    96.                 turret.Rotate(Vector3.up, rotationSpeedClamped * Time.deltaTime * turretRotationSpeed);
    97.             }
    98.         }
    99.     }
    100.  
    101.     IEnumerator ResetAndToggleParticles()
    102.     {
    103.         // Decelerate the ship's angular velocity
    104.         while (rb.angularVelocity.magnitude > 0.01f)
    105.         {
    106.             rb.angularVelocity *= decelerationFactor;
    107.  
    108.             // Toggle particle systems randomly during the reset
    109.             ToggleRandomParticles();
    110.  
    111.             yield return null;
    112.         }
    113.  
    114.         // Smoothly interpolate the rotation back to the initial rotation
    115.         yield return StartCoroutine(InterpolateRotation(initialRotation));
    116.  
    117.         // Turn off all particle systems when the reset is complete
    118.         TurnOffAllParticles();
    119.     }
    120.  
    121.     IEnumerator InterpolateRotation(Quaternion targetRotation)
    122.     {
    123.         float duration = 3.0f;
    124.         float elapsed = 0.0f;
    125.         Quaternion startRotation = transform.rotation;
    126.  
    127.         while (elapsed < duration)
    128.         {
    129.             transform.rotation = Quaternion.Slerp(startRotation, targetRotation, elapsed / duration);
    130.  
    131.             // Toggle particle systems randomly during the reset
    132.             ToggleRandomParticles();
    133.  
    134.             elapsed += Time.deltaTime;
    135.             yield return null;
    136.         }
    137.  
    138.         transform.rotation = targetRotation;
    139.  
    140.         // Toggle particle systems randomly during the final frame
    141.         ToggleRandomParticles();
    142.     }
    143.  
    144.     void ToggleRandomParticles()
    145.     {
    146.         // Toggle particle systems randomly during the reset
    147.         foreach (ParticleSystem particleSystem in particleSystems)
    148.         {
    149.             if (particleSystem != null)
    150.             {
    151.                 if (Random.Range(0f, 1f) > 0.5f)
    152.                 {
    153.                     if (!particleSystem.isPlaying)
    154.                     {
    155.                         particleSystem.Play();
    156.                     }
    157.                 }
    158.                 else
    159.                 {
    160.                     if (particleSystem.isPlaying)
    161.                     {
    162.                         particleSystem.Stop();
    163.                     }
    164.                 }
    165.             }
    166.         }
    167.     }
    168.  
    169.     void TurnOffAllParticles()
    170.     {
    171.         // Turn off all particle systems
    172.         foreach (ParticleSystem particleSystem in particleSystems)
    173.         {
    174.             if (particleSystem != null)
    175.                 particleSystem.Stop();
    176.         }
    177.     }
    178. }
     
  2. cydonia

    cydonia

    Joined:
    Apr 7, 2013
    Posts:
    11
    The removing line 66 was a good shout, however changing to FixedUpdate broke the reset function that's kind of a vital game play mechanic. So i'm going to need to go a different rout. There's something that causes interpolate to just break. I need to find out how I can get that smooth interpolate motion without it randomly breaking physics.
     
  3. cydonia

    cydonia

    Joined:
    Apr 7, 2013
    Posts:
    11
    It would seem it's just the rigidbody jittering as it moves. I can't use interpolate as it breaks any time the ship rotates or just randomly after a minute. I can't compensate with the camera as I can only make the ship not jiter and everything else jitter, or everything else jitter and the ship not jitter. I can reduce the effect by modifying fixed timesteps but that causes performance issues.

    I'm at a loss, Ive been working on it all night and I have no idea. I just need to smooth the rigidbodies motion, but that doesn't seem possible with my setup. I even tried to add a compensation to the ship model so even if the rigid body is vibrating the actual rendered parts don't, and that didn't work. It would be so easy if interpolate didn't massively break the physics as it's the only thing I tried that stopped the jittering. I don't know what to do, i'm too far into development to start over, but I can't finish it with this very glaring visual issue of jittering.So what do I do beyond using a combo of chatgpt and professionals on fiver to figure out a work around?
     
  4. codebiscuits

    codebiscuits

    Joined:
    Jun 16, 2020
    Posts:
    101
    I think you really do need to stuff like AddForce, AddTorque, etc. from FixedUpdate, rather than Update.
    Otherwise, I think the behaviour will vary depending on your FPS, & maybe other problems.

    I thiiink reading stuff from Input, like GetKey etc should still happen in Update, and you should cache the values for FixedUpdate to process. I've not experimented a lot, but this is the way I always do it.

    I'm not sure manually setting the rb.angularVelocity in ResetAndToggleParticles is "compatible" with using AddTorque (should you be adding a counter-torque instead to achieve the same result?).
    Same with manually setting transform.rotation in InterpolateRotation. I think the rigidbody would want to be kinematic while you're doing stuff like that to it. (I think MoveRotation will do what you want for a kinematic rigidbody, and is preferable to setting the transform rotation here)?
    I think I'd get everything except reset working smoothly from FixedUpdate, and then fix reset as a separate task.
    I wonder if you can lock out AddForce etc while you're resetting, and make the rigidbody kinematic for the duration of the reset.

    https://forum.unity.com/threads/kinematic-objects-not-colliding.1530868/#post-9550657 << This post I saw earlier today has a handy diagram about kinematic vs non-kinematic/ what's OK and what's not OK to do to a rigidbody/ rigidbody transform etc.

    I know when I'm using cinemachine cameras, visual jitter is affected by the UpdateMethod I specify - someone else is in charge of cameras in my game though, sorry, it's not my area of expertise at all:).
     
  5. cydonia

    cydonia

    Joined:
    Apr 7, 2013
    Posts:
    11
    I don't use cinemachine cameras, they annoy me. In a nother project they kept falling behind the player instead of staying in place when in motion. Why put physics on the camera at all?

    Anyway my biggest issue is the player controller needs to be newtonian. So when the player stops accelerating, the player maintains the momentum. The best way to describe the physics is like asteroids. If I can avoid remaking the controller that would be prefferd as that script controls a number of other things such as turret rotation on the ship.

    I wonder if I can separate the ship model, give it a follow script similar to the camera, and try again to essentially let the rigidody jitter all it wants and just fake it by smoothing the actual player model. Might be dumb but my only other option is rebuild the controller and that's going to set me back.
     
  6. codebiscuits

    codebiscuits

    Joined:
    Jun 16, 2020
    Posts:
    101
    I get the asteroids vibe.
    I think for that you probably want to do all of the movement via RigidBody.AddForce & RigidBody.AddTorque from FixedUpdate (my son made an "Atari tanks" style game with the same feeling using this method (I helped him with the scripting:))
    https://adnats.itch.io/tankx
    (this game has large angular drag, I think, which quickly kills the torque, but they'd spin forever otherwise)

    Code (CSharp):
    1.     private void Update() {
    2.         if ( Input.GetButtonDown( fireButton ) && Time.time > nextFire ) {
    3.             shotSound.Play();
    4.  
    5.             nextFire = Time.time + fireRate;
    6.             takeAShot = true; //I'm caching takeAShot here because otherwise FixedUpdate can miss it, I think
    7.         }
    8.     }
    9.  
    10.     // FixedUpdate is called once per frame
    11.     void FixedUpdate() {
    12.         var vert = Input.GetAxis(verticalAxis); //I think I'm doing GetAxis here in FixedUpdate so "input gravity" etc works correctly, I can't remember. In my pro work, someone else is doing the input stuff:)
    13.         var force = transform.up * vert * Time.fixedDeltaTime * speed; //Time.deltaTime would be better here:)
    14.         rb.AddForce( force );
    15.  
    16.         var horiz = Input.GetAxis(horizontalAxis);
    17.  
    18.         rb.AddTorque( -horiz * Time.fixedDeltaTime * steerSpeed ); //Time-scaling makes it framerate-independent
    19.  
    20.         if ( takeAShot ) {
    21.             var bullet = Instantiate(projectile, shotSpawn.position, shotSpawn.rotation);
    22.             var bulletBody = bullet.GetComponent<Rigidbody2D>();
    23.             bulletBody.AddForce( rb.transform.up * 500 );
    24.  
    25.             takeAShot = false;
    26.         }
    27. }
    I'm not sure cinemachine has to have physics. I've not used it much myself, but Jay/Rhys have used it everywhere in Exo Rally Championship and seems to work well in our 3d vehicle game.
     
    Last edited: Dec 29, 2023
  7. cydonia

    cydonia

    Joined:
    Apr 7, 2013
    Posts:
    11

    Thank you for the suggestions, was actually quite helpful in streamlining a few things.
    I've currently fixed the problem. But I didn't actually fix it I just covered it up. I made the rendered portion of the ships and particle systems a separate object and slapped on a simple follow script with smoothing compensation, the camera follow target is also on this seperate object so both the camera and the rendered part of the ship are separate to the physics of the rigid body. Now the ship doesn't jitter at high speed and otherwise operates normally. The actual rigidbody is still jittering but it's invisible as it's on an object with no mesh renderer so it doesn't matter. For the time being it seems to work even after trying to induce gamebreaking physics and events. It also fixed an issue I was having with the reset function which is nice.