Search code examples
unity-game-enginerotationpositionmovephysics

The best navigation code to using rigidbody in unity3d?


I want to press the key once

My cube moves to the right and rotates 90 degrees

The rotation is well done

But it does not move well

using UnityEngine;

using System.Collections;

public class Player : MonoBehaviour
{

    public float speed;

    public float time;

    public GameObject contactPoint;

    private Rigidbody rig;

    private void Start()
    {
        rig = GetComponent<Rigidbody>();
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.D))
        {
            StartCoroutine(RotatePlayer(Vector3.forward * 90, Vector3.right, time));
        }

        if (Input.GetKeyDown(KeyCode.A))
        {
            StartCoroutine(RotatePlayer(Vector3.back * 90, Vector3.left, time));
        }
    }

    private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
    {

        Quaternion fromAngle = contactPoint.transform.rotation;
        Quaternion toAngle = Quaternion.Euler(transform.eulerAngles - byAngle);

        for (float t = 0; t < 1; t += Time.deltaTime / inTime)
        {   
            rig.MovePosition(transform.position + (dir * speed * Time.deltaTime));
            rig.MoveRotation(Quaternion.Slerp(fromAngle, toAngle, t));

            yield return null;
        }
    }
}

Solution

  • Your main issue is: You should do Physics related things like movement of a Rigidbody only in FixedUpdate.

    For Coroutines Unity provides WaitForFixedUpdate which does exactly what the name says, making sure the code after it is executed in a FixedUpdate physics call.

    private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
    {
        yield return new WaitForFixedUpdate();
    
        // here I would also use "rig" instead of "transform"
        Quaternion fromAngle = contactPoint.transform.rotation;
        Quaternion toAngle = Quaternion.Euler(rig.eulerAngles - byAngle);
    
        for (float t = 0; t < 1; t += Time.deltaTime / inTime)
        {   
            yield return new WaitForFixedUpdate();
    
            // here I would also use "rig" instead of "transform"
            rig.MovePosition(rig.position + (dir * speed * Time.deltaTime));
            rig.MoveRotation(Quaternion.Slerp(fromAngle, toAngle, t));
        }
    }
    

    beyond that it is a bit unclear what exactly you define as not move well. You also should somehow make sure that only one routine is running at a time. Either by terminating already running routines like

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.D))
        {
            StopAllCoroutines();
            StartCoroutine(RotatePlayer(Vector3.forward * 90, Vector3.right, time));
        }
    
        if (Input.GetKeyDown(KeyCode.A))
        {
            StopAllCoroutines();
            StartCoroutine(RotatePlayer(Vector3.back * 90, Vector3.left, time));
        }
    }
    

    or preventing new routines from starting until the current one is finished using a flag like

    private bool alreadyRotating;
    
    private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
    {
        if(alreadyRotating) yield break;
    
        alreadyRotating = true;
    
        ......
    
        alreadyRotating = false;
    }