0
\$\begingroup\$

I am struggling with an issue in Unity where I am stopping a rigidbody's motion, but after the motion is stopped the rigidbody still moves slightly. It is causing me major issues as I need the object to freeze with the exact position and rotation at the time it was stopped.

I have uploaded a small package where you can see this problem first hand. Any help in identifying the problem and overcoming it would be greatly appreciated.

To see the problem, simply create a new project and import the package. Start the game and press the "spacebar" key to see the issue, pressing the "r" resets the game so you can see it again (without having to restart the game in the editor each time).


As requested here is the code I am using to illustrate this problem:

public class PlayerController : MonoBehaviour{
private Rigidbody rb;


void Start()
{
    rb = GetComponent<Rigidbody>();

}}

public class TrapController : MonoBehaviour {
// Trap data
float trapSpeed = 120;
float trapRadius = 3;


// Player data
private PlayerController player;
private Rigidbody playerRb;


void Start()
{
    player = FindObjectOfType<PlayerController>();
    playerRb = player.GetComponent<Rigidbody>();

    // Start the trap
    StartCoroutine(TrapPlayer());
}

private void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
        HaltTrap();
}


private IEnumerator TrapPlayer()
{
    Vector3 offset;
    float angle = 0;
    while (true)
    {
        // Force player to move in a circular motion
        angle += trapSpeed * Time.fixedDeltaTime;
        offset = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad)) * trapRadius;
        Vector3 position = transform.position + offset;
        playerRb.MovePosition(position);

        // Force player to face tangential
        Vector3 towardsTrap = transform.position - player.transform.position;
        playerRb.MoveRotation(Quaternion.LookRotation(towardsTrap * 90));
        yield return new WaitForFixedUpdate();
    }
}


private void HaltTrap() 
{
    // Stop the motion
    StopAllCoroutines();

    playerRb.velocity = Vector3.zero;
    playerRb.angularVelocity = Vector3.zero;
    Debug.Log("1. Value when trap halted: " + player.transform.rotation.eulerAngles.y);

    // Start reporting the angle
    StartCoroutine(ReportAngle());

}

private IEnumerator ReportAngle()
{

    float value = player.transform.rotation.eulerAngles.y;  
    Debug.Log("2. Value at start of Report: " + value);
    while (true)
    {
        float newValue = player.transform.rotation.eulerAngles.y;
        if (newValue != value)
        {
            Debug.Log("---> Value has changed to: " + newValue);
            value = newValue;
        }
        yield return null;
    }
} 

This is an illustration of what happens when running the code:

enter image description here

It might be hard for you to see but if you look at the console you can see that the angle has changed after stopping it. I have tried toggling isKinematic on and off in the HaltTrap() function. This stops the rotational change from taking place whilst the object is stationary, but as soon as I try to move the position, the rotation changes. Thus, toggling this only seems to delay the change, not prevent it.

\$\endgroup\$
13
  • 1
    \$\begingroup\$ Please include the instructions to create a Minimal Complete Verifiable Example in the body of your question itself, rather than relying on an external link that can rot, and requires trusting the downloaded content. \$\endgroup\$
    – DMGregory
    Commented Feb 26, 2023 at 19:47
  • \$\begingroup\$ I tried to do that DMGregory but I really struggled to summarise it with code in the message body. I would have to write paragraphs of text to adequately explain it. The problem involves multiple classes, rigid bodies and moving objects, and as such, it really is far easier to just download the package and see it working. I hope that is okay. \$\endgroup\$
    – Cato
    Commented Feb 26, 2023 at 19:56
  • 1
    \$\begingroup\$ It sounds like you have more work to do then in paring this down to a minimal example. Can you create a new project, and reproduce the problem with just one or two bodies and one script? Are you using constraints to freeze the position, or just zeroing its velocity? This is the kind of information a reader should be able to glean without committing to downloading and running untrusted code. \$\endgroup\$
    – DMGregory
    Commented Feb 26, 2023 at 20:49
  • 2
    \$\begingroup\$ "it really is far easier to just download the package" is true from your perspective, but it shifts the work of onto the reader. From the reader's point of view, it's much easier to answer a question that is self contained & has the relevant info all in one place and doesn't contain extraneous code to sift through. The easier you make it for people to answer your question, the more likely you are to receive answers. \$\endgroup\$
    – Pikalek
    Commented Feb 26, 2023 at 21:05
  • \$\begingroup\$ @Pikalek I think that's a bit unfair! I spent quite a bit of time trying to recreate the problem in a simplified scene, where the reader can easily see the issue without having to do any investigation. But if there is an insistence for me to use code in the message body, I will try to edit the post to show that. \$\endgroup\$
    – Cato
    Commented Feb 26, 2023 at 21:23

1 Answer 1

0
\$\begingroup\$

Okay so I think I have finally found a way to solve this problem by updating the function that stops the rigidbody:

private void HaltTrap()
{
    // Stop the motion  
    StopAllCoroutines();

    Vector3 playerPos = player.transform.position;
    Quaternion playerRot = player.transform.rotation;

    playerRb.velocity = Vector3.zero;
    playerRb.angularVelocity = Vector3.zero;

    playerRb.isKinematic = false;
    playerRb.position = playerPos;
    playerRb.rotation = playerRot;
    playerRb.isKinematic = true;


    Debug.Log("1. Value when trap halted: " + player.transform.rotation.eulerAngles.y);

    // Start reporting the angle
    StartCoroutine(ReportAngle());
  
}

Some people already suggested toggling isKinematic off and on as a solution. This did not solve the problem, but setting rb.position and rb.rotation in between the toggling has solved the problem. I guess this breaks the queue of positions that rb.MovePosition() has accrued, thereby preventing the drift? I wrote an automated test to run this function 1000 times, and not once did I see a rotation change after halting the rigidbody. So it looks to me like this is a reliable solution.

\$\endgroup\$
2
  • \$\begingroup\$ It's not exactly the case that there's a "queue" of move positions, but the position of the body in the physics system is separated from the position in Unity's transform hierarchy. They're synched once per frame unless you force a synch in code. I think your problem was that you were taking the RB position pre-synch, then comparing that to the transform position. Here, by setting the RB position to the transform position, you're forcing them into agreement at the time of the stop. \$\endgroup\$
    – DMGregory
    Commented Feb 27, 2023 at 20:28
  • \$\begingroup\$ That makes sense. The good thing is that there is no longer drifting at the point of halting the rigidbody. It was causing me major issues in my game so a big relief to see it resolved. \$\endgroup\$
    – Cato
    Commented Feb 27, 2023 at 20:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .