Search Unity

How to Get the Difference Between Two Quaternions in 2d?

Discussion in 'Scripting' started by Mashimaro7, Aug 26, 2021.

  1. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    I'm not sure why, but Quaternion.Angle does not seem to be working in my game... Does it only work in 3D or something? I want my turret to rotate towards the enemy, and shoot towards the enemy. I had this working just fine before, but I wanted to make the turret rotate even when the gun was not reloaded and for some reason this broke everything lol.

    So originally it was two coroutines, one waits for the other... It calls the first coroutine constantly if shooting is false, the end of the routine sets shooting to false. It rotates in the fixed update, the rotation is working fine. But it's not waiting for the target... I have a while loop running, while Quaternion.angle is greater than 0.1f, but for some reason it's shooting long before it even reaches the target. I tried printing off said angle, and it's producing some strange results.. Sometimes it seems proper, other times it prints of exactly 0 and other times 180?!

    Anyway, here's my coroutines
    Code (CSharp):
    1.     IEnumerator WaitForTarget()
    2.     {
    3.  
    4.         if (nextTarget == null)
    5.         {
    6.             shooting = false;
    7.             yield break;
    8.         }
    9.  
    10.  
    11.         while (Mathf.Abs(Quaternion.Angle(transform.rotation, rotGoal))>0.01f || rateOfFire > 0)
    12.         {
    13.  
    14.             if (nextTarget == null || AngleBetweenVector2(nextTarget.transform.position, transform.position) > angleToFire)
    15.             {
    16.                 shooting = false;
    17.                 yield break;
    18.  
    19.             }
    20.  
    21.             yield return null;
    22.         }
    23.         turning = false;
    24.  
    25.     }
    26.  
    27.     IEnumerator ShootBullet()
    28.     {
    29.         shooting = true;
    30.         nextTarget = CheckChangeTarget();
    31.         if (nextTarget == null || nextTarget.GetComponent<Zombie>().dead)
    32.         {
    33.             shooting = false;
    34.             yield break;
    35.  
    36.         }
    37.         yield return WaitForTarget();
    38.  
    39.         if (nextTarget == null || nextTarget.GetComponent<Zombie>().dead)
    40.         {
    41.             shooting = false;
    42.             yield break;
    43.         }
    44.         SoundManager.sm.PlaySound("Shot");
    45.         lightAnim.Play("MuzzleFlash", -1, 0);
    46.         shellEffect.Play();
    47.  
    48.         GameObject b = Pool.pool.GetObject(bulletsPool, bullet);
    49.         b.GetComponent<Bullet>().SetVelocity(directionToEnemy);
    50.         b.transform.position = bulletSpawn.position;
    51.         b.SetActive(true);
    52.  
    53.         shooting = false;
    54.  
    55.         rateOfFire = rateOfFireSet;
    56.  
    57.         nextTarget = null;
    58.  
    59.     }
    I know it's kinda sloppy, i tried fixing everything up...So, what's happening is it seems to be firing instantly when it acquires a target, so more likely than not, the while loop is just ending immediately... It doesn't fire if there's no target, however, it doesn't wait until it's rotated properly... If it's relevant, here's my rotation and target acquisition method as well,
    Code (CSharp):
    1.     private void FixedUpdate()
    2.     {
    3.         if (deactivated) return;
    4.         if (!baseTurret && !GameManager.gm.gameStarted) return;
    5.         if (nextTarget != null)
    6.         {
    7.             if (nextTarget.GetComponent<Zombie>().dead) nextTarget = CheckChangeTarget();
    8.  
    9.             if (nextTarget != null)
    10.             {
    11.                 float angle = Mathf.Atan2(directionToEnemy.y, directionToEnemy.x) * Mathf.Rad2Deg;
    12.                 directionToEnemy = (nextTarget.position - transform.position).normalized;
    13.                 angle = Mathf.Atan2(directionToEnemy.y, directionToEnemy.x) * Mathf.Rad2Deg;
    14.  
    15.                 rotGoal = Quaternion.AngleAxis(angle, Vector3.forward);
    16.                 turretAxis.rotation = Quaternion.RotateTowards(turretAxis.rotation, rotGoal, rotationSpeed * Time.deltaTime);
    17.  
    18.  
    19.                 if (transform.rotation != rotGoal)
    20.                 {
    21.                     if (nextTarget == null || AngleBetweenVector2(nextTarget.transform.position, transform.position) > angleToFire)
    22.                     {
    23.                         nextTarget = CheckChangeTarget();
    24.                         if (nextTarget == null || nextTarget.GetComponent<Zombie>().dead)
    25.                         {
    26.                             shooting = false;
    27.                             return;
    28.                         }
    29.                     }
    30.  
    31.                 }
    32.                 else
    33.                 {
    34.                     nextTarget = CheckChangeTarget();
    35.                 }
    36.             }
    37.         }
    38.         else
    39.         {
    40.             nextTarget = CheckChangeTarget();
    41.         }
    42.     }
    43.  
    44.     Transform CheckChangeTarget()
    45.     {
    46.         Zombie[] zombies = FindObjectsOfType<Zombie>();
    47.  
    48.         Zombie closestZombie = null;
    49.         float previousDistance = shootRange;
    50.  
    51.         if (zombies.Length == 0) return null;
    52.         for (int i = 0; i < zombies.Length; i++)
    53.         {
    54.             float angle = AngleBetweenVector2(zombies[i].transform.position, transform.position);
    55.             if (angle > angleToFire) continue;
    56.             if (Vector2.Distance(zombies[i].transform.position, transform.position) < previousDistance && !zombies[i].dead)
    57.             {
    58.                 previousDistance = Vector2.Distance(zombies[i].transform.position, transform.position);
    59.                 closestZombie = zombies[i];
    60.             }
    61.  
    62.         }
    63.  
    64.         if (closestZombie != null)
    65.         {
    66.             return closestZombie.transform;
    67.         }
    68.         shooting = false;
    69.         StopAllCoroutines();
    70.         return null;
    71.     }
    Can someone please help me figure out what I'm doing wrong? Previously I had it only calling the routine if the rate of fire was less than or equal to 0, but now I changed it to just call it when it's shooting...

    PS, 99% sure the Quaternion.Angle is not functioning as intended(or I just dont understand it lol) i just did a print in the while loop, it printed 180 4 times, and other times it didn't even call it once...
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    The solution is simple, if you care about the angles, work with angles and do not rely on Quaternions, compute them for Unity only. Quaternions use internal representation and have nothing to do with (Euler) angles. There is no guarantee the angles you get back with eulerAngles will be consistent (they will numerically, but they do not care about your frame by frame logic and will twist and turn however they see fit as long as the angle technically remains the same).

    Now let me see what actually happened.
     
    MD_Reptile and Mashimaro7 like this.
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    What does AngleBetweenVector2 do btw? (not conceptually but literally)
     
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    I wish you didn't unload all of this code, but simply asked for a contrived example of what you actually need.
    I can't see anything particularly wrong, other than to intuitively guess that you made a wrong assumption somewhere. Too much going on and plenty of room for error.

    To answer your question, no, Quaternions work the same in both 2D and 3D. 2D is just a plane in 3D remember?
    And yes I know Quaternions are hard and angles are easy, but the problems usually arise when people try to force Quaternions to behave like angles.

    Simply keep your angles, work with angles, debug your angles, subtract your angles, do what you want, and when you're finished, make a Quaternion out of it, the functions are there.
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    So you've tried debugging
    Mathf.Abs(Quaternion.Angle(transform.rotation, rotGoal))
    right?
    Have you tried debugging
    transform.rotation.eulerAngles
    and
    rotGoal.eulerAngles
    ?
     
  6. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    I just figured it out, sorry I didn't see this post. The issue was the rotation goal was being assigned after the coroutine was called, so at the exact frame the coroutine was called. it would be exactly equal to the rotGoal.

    Um, sorry if I wasn't super clear, maybe you didn't see cause it was between the two codes, the issue was the coroutine that was waiting for it to rotate was pretty much being skipped over, it was firing a bullet the second it was called, and then rotating, and firing again. So it would properly wait until a target was in range, but then it would fire regardless of whether or not the target was actually in range.

    Also, I wasn't asking if Quaternions are different in 2D and 3D haha, I was asking if Quaternion.Angle() <<< the method, worked properly in 2D as well or if that's why it wasn't functioning

    But thanks for the help, I wish I'd seen these replies beforehand haha.
     
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    Yeah, ok, coroutines are terrible for debugging anyway. And when we look at the code, we can't possibly know the order of execution, it's something only you can know, because you wrote it. You definitely shouldn't be relying on them for core logic and behavior, it's not their purpose. Don't worry, you're not the first or the last who fell into that trap, I'm not criticizing you, but I'd wish you'd see some proper code and learn from that. Care to tell me where did you learn to use them (and abuse them), so I can have a word with that Youtuber?

    Nah I got you regarding 2D/3D, that's exactly what I meant. All of the Quaternion functions work in 2D.
     
    Mashimaro7 likes this.
  8. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    It was awhile ago that I learned this, but I learned a lot of what I know about coroutines from Sebastian Lague's introduction to game design series. But to be fair, he did write it 5 years ago haha. In the final section of the series where you make a stealth game, he used coroutines to wait for the guard to turn.
     
    orionsyndrome likes this.
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    Goddamnit, find another youtuber :D
     
    Mashimaro7 likes this.
  10. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    Hey, he's better than Brackeys... lol
     
    orionsyndrome likes this.
  11. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    No no, find another Youtuber I can have a word with. I couldn't raise my figurative hand on that one in particular. (Man is he better than Brackeys...)
     
    Mashimaro7 likes this.
  12. Mashimaro7

    Mashimaro7

    Joined:
    Apr 10, 2020
    Posts:
    727
    Ahh, I gotcha, thought you weren't a fan haha

    He's my idol, I wish he still made tutorials lol