我的四元数旋转协程在接近结束时变慢?

huangapple go评论62阅读模式
英文:

My Quaternion rotation coroutine slows down towards the end?

问题

我有一个协程,当用户输入一个触发器然后按下特定的键时启动。整体上旋转是有效的,但在旋转末尾开始变得非常慢。我在使用Vector3.Lerp时遇到过这个问题,但我通过使用speed * Time.deltaTime和另一个变量来使其平滑化来解决了这个问题。我尝试根据Unity文档中的Quaternion.RotateTowards使用一步,但它根本不会旋转。

void Update()
{
    if (Input.GetKey(KeyCode.Z))    
    {
        Debug.Log("Raycast.");
        if (Physics.Raycast(transform.position, Vector3.down, 1.5f))
        {
            StartCoroutine(Turn(cubeObject));
        }
    }
}

IEnumerator Turn(Transform cubeObject)
{
    Quaternion targetRotation = Quaternion.identity;
    do
    {
        Debug.Log("Begin rotating.");
        Vector3 targetDirection = cubeObject.position - transform.position;
        targetRotation = Quaternion.LookRotation(targetDirection);
        Quaternion nextRotation = Quaternion.RotateTowards(
                transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        transform.localEulerAngles = new Vector3(0, nextRotation.eulerAngles.y, 0);
        yield return null;

    } while (Quaternion.Angle(transform.rotation, targetRotation) == 0f);     
}

如果我将最后一行的最后一个值(Quaternion.Angle)更改为大于0的任何值,旋转永远不会结束。我将被锁定在旋转状态,直到退出运行时。我只想在按下适当的键时旋转一次完全朝向目标。

英文:

I have a coroutine that is initiated when the user enters a trigger and then presses a specific key. The rotating as a whole works, but towards the end of the rotation it begins to rotate much, much slower. I have had this issue when using Vector3.Lerp but I fixed that by using speed * Time.deltaTime and another variable to make it smooth. I tried to use a step as per the Quaternion.RotateTowards unity docs but it would just not rotate at all.

void Update()
{
    if (Input.GetKey(KeyCode.Z))    
    {
        Debug.Log("Raycast.");
        if (Physics.Raycast(transform.position, Vector3.down, 1.5f))
        {
            StartCoroutine(Turn(cubeObject));
        }
    }
}

IEnumerator Turn(Transform cubeObject)
{
    Quaternion targetRotation = Quaternion.identity;
    do
    {
        Debug.Log("Begin rotating.");
        Vector3 targetDirection = cubeObject.position - transform.position;
        targetRotation = Quaternion.LookRotation(targetDirection);
        Quaternion nextRotation = Quaternion.RotateTowards(
                transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        transform.localEulerAngles = new Vector3(0, nextRotation.eulerAngles.y, 0);
        yield return null;

    } while (Quaternion.Angle(transform.rotation, targetRotation) == 0f);     
}

And if I change the very last value on the last line (Quaternion.Angle) to anything greater than 0, the rotation never ends. I will be locked in rotation until I exit runtime. I'm only trying to rotate fully towards the target just once when the appropriate key is pressed.

答案1

得分: 1

以下是您要翻译的内容:

"As said there is a combination of a couple of issues here

  • First you potentially start a lot of concurrent ("simultaneously" happening) Coroutines! You should only start ONE and either make sure you don't start a second one or interrupt an already running one:

      private Coroutine currentRoutine;
    
      void Update()
      {
          // Use GetkeyDown to check of the first frame the key went down
          // rather than a continuous holding down of the button
          if (Input.GetKeyDown(KeyCode.Z))    
          {
              Debug.Log("Raycast.");
              if (Physics.Raycast(transform.position, Vector3.down, 1.5f))
              {
                  if (currentRoutine != null) StopCoroutine(currentRoutine);
                  currentRoutine = StartCoroutine(Turn(cubeObject));
              }
          }
      }
    
      IEnumerator Turn(Transform cubeObject)
      {
          ...
    
          currentRoutine = null;
      }
    
  • Then you are mixing the global world space direction targetDirection and also world space transform.rotation with a local space transform.localEulerAngles. You want to either do all calculation in local or global space - don't mix!

  • Then I think the slowing down can be explained by the fact that your targetDirection might have an offset in the Y axis.

    You do RotateTowards which rotates into the target rotation on all three axes - but then throw away X/Z and only apply the Y component.

    => The closer your rotation comes to the final targetRotation (potentially never fully reaching it btw) the more you see the effect since the Y component of the rotation is already almost "correct" so it now tries to rotate more and more on the X/Z components - the ones you ignore.

    In order t solve this (and in general) I would recommend to stick to Quaternion as much as possible and rather already fix the target direction

      targetDirction.y = 0;
      targetRotation = Quaternion.LookRotation(targetDirection);
      transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    
      yield return null;
    
  • And finally never use == for comparing float! You could rather use Quaternion == which uses a reduced precision of 1e-5

      while(transform.rotation != targetRotation);
    
      // and then make sure to end up with clean values
      transform.rotation = targetRotation;
    

    or alternatively if you want to stick to the angle an be more precise use Mathf.Approximately

      while(!Mathf.Apprximately(Quaternion.Angle(transform.rotation, targetRotation), 0f));
    

    which basically equals doing

      while(Quaternion.Angle(transform.rotation, targetRotation) > Mathf.Epsilon);"
    
英文:

As said there is a combination of a couple of issues here

  • First you potentially start a lot of concurrent ("simultaneously" happening) Coroutines! You should only start ONE and either make sure you don't start a second one or interrupt an already running one:

     private Coroutine currentRoutine;
    
     void Update()
     {
         // Use GetkeyDown to check of the first frame the key went down
         // rather than a continuous holding down of the button
         if (Input.GetKeyDown(KeyCode.Z))    
         {
             Debug.Log("Raycast.");
             if (Physics.Raycast(transform.position, Vector3.down, 1.5f))
             {
                 if (currentRoutine != null) StopCoroutine(currentRoutine);
                 currentRoutine = StartCoroutine(Turn(cubeObject));
             }
         }
     }
    
     IEnumerator Turn(Transform cubeObject)
     {
         ...
    
         currentRoutine = null;
     }
    
  • Then you are mixing the global world space direction targetDirection and also world space transform.rotation with a local space transform.localEulerAngles. You want to either do all calculation in local or global space - don't mix!

  • Then I think the slowing down can be explained by the fact that your targetDirection might have an offset in the Y axis.

    You do RotateTowards which rotates into the target rotation on all three axes - but then throw away X/Z and only apply the Y component.

    => The closer your rotation comes to the final targetRotation (potentially never fully reaching it btw) the more you see the effect since the Y component of the rotation is already almost "correct" so it now tries to rotate more and more on the X/Z components - the ones you ignore.

    In order t solve this (and in general) I would recommend to stick to Quaternion as much as possible and rather already fix the target direction

     targetDirction.y = 0;
     targetRotation = Quaternion.LookRotation(targetDirection);
     transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    
     yield return null;
    
  • And finally never use == for comparing float! You could rather use Quaternion == which uses a reduced precision of 1e-5

     while(transform.rotation != targetRotation);
    
     // and then make sure to end up with clean values
     transform.rotation = targetRotation;
    

    or alternatively if you want to stick to the angle an be more precise use Mathf.Approximately

     while(!Mathf.Apprximately(Quaternion.Angle(transform.rotation, targetRotation), 0f));
    

    which basically equals doing

     while(Quaternion.Angle(transform.rotation, targetRotation) > Mathf.Epsilon);
    

huangapple
  • 本文由 发表于 2023年7月13日 20:06:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76679193.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定