Search Unity

  1. Click here to see what's on sale for the "Best of Super Sale" on the Asset Store
    Dismiss Notice
  2. We are looking for feedback on the naming of a new user research platform that we are working on.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Gyro rotation but in local space?

Discussion in 'Scripting' started by dennik, Oct 16, 2020.

  1. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    I'm trying to get some basic gyro functionality for my game that allows the phone to be used for local rotation on the xy plane (always oriented to the phone screen). I managed to get rid of the z axis rotation, unfortunately the Gyro function returns real world rotations so the interpretation of a tilt on x or y for example, changes depending on the real world orientation of the phone.
    I'm no math wiz so I'm not sure what would be the solution to this. Any ideas?
    Here is the code so far.

    Code (CSharp):
    1.    
    2. public GameObject rot; //a null object directly controlled by Gyro
    3.     Vector3 alignedAngles;
    4.     void Start()
    5.     {
    6.     }
    7.     void Update()
    8.     {    
    9.         alignedAngles = rot.transform.localEulerAngles;        
    10.         gameObject.transform.eulerAngles = new Vector3(alignedAngles.x, 0.0f, alignedAngles.y);//replace  z
    11.  
    12.     }
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,366
    I don't understand at all why returning "real-world" gyro values would be a problem, or what you want instead, or what your current code is trying to do. But a couple of possible problems:

    1. You started with local Euler angles, but then assigned them to another transform's global Euler angles.

    2. Euler angles are not linearly independent. For example, the angles (180, 0, 0) and (0, 180, 180) are really the same orientation. Therefore, replacing just one of them with a different value gives unpredictable results. (For instance, the two groups of angles listed above start out equivalent, but if you ran them through your code they'd give non-equivalent output.)

    One strategy for eliminating one axis of a rotation (while sidestepping issues of how it is represented) is to take Transform.forward, project it onto a plane, and then use the result to create a LookRotation.
     
  3. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    424
    Are you saying that you want to be able to hold the phone in some position other than with the screen facing straight up at the sky, and still have it return some kind of X/Y tilt information when you change its orientation? I think to do that, you'd need a starting orientation that you defined as "flat." Just thinking off the top of my head, I'd guess that would be something you'd define at the start of a game. So, maybe the player is holding the phone in landscape orientation, with the screen facing up at a 45-degree angle from facing straight out horizontally (more or less how I'd hold my phone if I were sitting in my couch with my elbows on my knees). You could read the rotation of the phone then from Gyroscope.attitude. Get the inverse of that rotation with the Quaternion.Inverse method. By applying the inverse rotation to whatever you then read from Gyroscope.attitude, you'll get a Quaternion that is the rotation from the original position you recorded when the game started.

    Does that help?
     
  4. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    424
    Codewise, you could try this:

    Code (CSharp):
    1. public class TiltReader : MonoBehaviour
    2. {
    3.     public Quaternion tilt;
    4.  
    5.     Quaternion inverseFlat;
    6.  
    7.     void Start()
    8.     {
    9.         inverseFlat = Quaternion.Inverse(Gyroscope.attitude);
    10.     }
    11.  
    12.     void Update()
    13.     {
    14.         tilt = Gyroscope.attitude * inverseFlat;
    15.     }
    16. }
    Then you could read the
    tilt
    member of a
    TiltReader
    object and get the attitude same as if the player always held the phone flatly with the screen facing straight up at the sky, but really the phone is in the orientation it had when the game started.

    Mind you, I have never programmed a phone or anything else with a gyro in it, so this could all be malarkey. But I'd try it if I were working on your problem.
     
  5. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    Thanks for giving it a shot.
    This works for x axis only, but only if the phone doesn't turn on the z at all. Also the y axis rotation is fed to the z on my object.
     
  6. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    I want to create the type of control you see on ball balancing maze games. So the phone screen always faces up, and only rotations on x and y axis are taken into account (z facing towards the sky)
     
  7. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    424
    Ah, that is quite different than what I thought you were doing.

    Unity applies rotations in ZXY order. Seems to me that you want to ignore the Y rotation. What happens if you simply use the Z and X Euler angles from Gyroscope.attitude.eulerAngles?
     
  8. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    Nope. Unfortunately this doesn't work either. It works as I need in one specific orientation of the phone (laid flat, pointing east-west) but if I start rotating, the axis rotations get mixed. So some of the increase in the x, goes into the y etc.
    You can try it yourself on a cube, if you have an android phone. I'm using unity remote app (developer options on, usb debugging on) to test this.
    Code (CSharp):
    1.        
    2. gyro = Input.gyro;
    3.         gyrEuler = new Vector3(gyro.attitude.eulerAngles.x, -gyro.attitude.eulerAngles.z+180, gyro.attitude.eulerAngles.y);
    4.         gameObject.transform.eulerAngles = gyrEuler;
     
  9. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    424
    I do have an Android tablet. May give this a try.

    From your description, it sounds like you have to undo the Y rotation. What about this?

    Code (CSharp):
    1.     gyro = Input.gyro;
    2.  
    3.     Quaternion orientation = gryo.attitude;
    4.     Vector3 angles = orientation.eulerAngles;
    5.     Quaternion undoY = Quaternion.Euler(0, -angles.y, 0);
    6.     orientation = orientation * undoY;
    7.     gameObject.transform.rotation = orientation;
     
  10. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    I managed to finally make it work by implementing the rotationRate instead.
    It's not perfect though. Even with the rotationRateUnbiased, many times I'm getting unwanted rotation shifts that pile up over time and the controlled plane ends up being rotated more, in relation to the phone orientation. I don't know if that's an inaccuracy flaw with the gyro sensor of my phone, or is it something that can be remedied through code.

    Code (CSharp):
    1. gyrEuler = new Vector3(gameObject.transform.eulerAngles.x - gyro.rotationRateUnbiased.x/10, 0f, gameObject.transform.eulerAngles.z - gyro.rotationRateUnbiased.y /10);
     
  11. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    424
    I tried your suggestion and used a cube with a quad on it and a render texture. I think that's a valid simulation. The mistake I made was post-multiplying by the rotation that undoes the Y rotation. You need to pre-multiply. Here's my code:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ReadGyro : MonoBehaviour
    4. {
    5.     public Transform pieceTransform;
    6.  
    7.     void Update()
    8.     {
    9.         Quaternion rot = transform.rotation;
    10.         Vector3 angles = rot.eulerAngles;
    11.  
    12.         Quaternion undoY = Quaternion.Euler(0, -angles.y, 0);
    13.         pieceTransform.rotation = undoY * transform.rotation;
    14.     }
    15. }
    You'll need to change Line 9 to this:
    Code (CSharp):
    1.         Quaternion rot = Input.gyro.attitude;
     
  12. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    424
    Does this look like what you're trying to do?

     
  13. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    992
    This (rough) script should do what you need.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class RotationScript : MonoBehaviour {
    4.     Quaternion referenceRotation;
    5.  
    6.     private void Start () {
    7.         Input.simulateMouseWithTouches = true;
    8.  
    9.         Input.gyro.enabled = true;
    10.         Screen.orientation = ScreenOrientation.Portrait;
    11.  
    12.         referenceRotation = Quaternion.Inverse( Input.gyro.attitude );
    13.     }
    14.  
    15.     void Update () {
    16.         if( Input.GetMouseButtonDown( 0 ) ) {
    17.             // re-orient to current position on touch to prove that it can always be relative to current screen orientation
    18.             referenceRotation = Quaternion.Inverse( Input.gyro.attitude );
    19.         }
    20.  
    21.         var raw = Input.gyro.attitude;
    22.         // make the rotation relative to the current rotation rather than the world
    23.         var rot = referenceRotation * raw;
    24.  
    25.         var euler = rot.eulerAngles;
    26.  
    27.         var zRot = Quaternion.AngleAxis( euler.z, Vector3.forward );
    28.  
    29.         // reverse the rotation about the z-axis
    30.         rot = Quaternion.Inverse( zRot ) * rot;
    31.  
    32.         // rotate about the x axis to make the y axis point the right direction
    33.         rot = Quaternion.AngleAxis( 90, Vector3.right ) * rot;
    34.  
    35.         var up = rot * Vector3.up;
    36.         var right = rot * Vector3.right;
    37.         var fwd = rot * Vector3.forward;
    38.  
    39.         Debug.DrawRay( transform.position, up, Color.green );
    40.         Debug.DrawRay( transform.position, right, Color.red );
    41.         Debug.DrawRay( transform.position, fwd, Color.blue );
    42.  
    43.         transform.rotation = rot;
    44.     }
    45. }
    46.  
    If you want behavior that is closer to a real labyrinth puzzle, you will want to decompose the rotation further into the x and y axis and apply them separately using euler angles. This will simulate the stacking effect of having nested single-axis rotations that you find in real machines. This solution doesn't do that and so has strange artifacts at large rotations.
     
    Last edited: Oct 17, 2020
  14. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    Thanks! I'll give it a try.
     
  15. dennik

    dennik

    Joined:
    Nov 23, 2011
    Posts:
    75
    This might be getting close. I now realize that I was misleading everyone by mentioning rotation on the y axis. That was wrong on my part. the Y is actually the axis I don't need. What should I change on your script to get it to rotate on the xz plane only?
     
  16. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    992
    The Z axis for gyro is the axis that is facing straight out of the phone. My script translates the X and Y axis from gyro to act as the X and Z axis for the rotating box.

    That's what this line does:

    Code (CSharp):
    1. rot = Quaternion.AngleAxis( 90, Vector3.right ) * rot;
    For the record, in my sample scene I was utilizing a camera pointing straight down.

    If that's not what you mean, then I don't know what kind of ball maze game you're making. :D
     
unityunity