Tilting a game object left/right/forward/back with gyroscope, regardless of player's heading

I'm working on a mobile game in Unity and using the gyroscope to rotate an object along the x and y axes.

float x = Input.gyro.attitude.x;
float y = Input.gyro.attitude.y;


This code give me the rotation I want when pointed in a certain direction along the z-axis. I want the rotation to not depend on the z-axis. I want the rotation to act the same no matter what direction I'm pointed in the real-world.

Here is a 12 second clip of what I get using a mobile gyroscope when pointed at 1 or North on the z-axis:

I want the rotation to be the same no matter what direction I am pointed on the z-axis with the gyroscope. Is there some way to rotate the whole coordinate system?

• You're going to have trouble. Rotation in 3D space can only be accurately represented as a Quaternion (W,X,Y,Z). Euler angles (what the Input.gyro gives you) suffer from Gimbal Lock. Restricting yourself down to two axis is just going to not be possible. – Draco18s no longer trusts SE Nov 7 '19 at 16:18
• @Draco18s - the particular interaction they want can definitely be done with two axes. They don't need to express every possible orientation, just a small subset. – DMGregory Nov 7 '19 at 17:39

Input.gyro.attitude gives you a quaternion representing (a best estimate of) the device's absolute orientation in space - including its orientation relative to the north pole.

But that's not what you want here. You want a measure of how the device is tilted relative to the gravity vector - no matter how we're oriented to north/south/east/west.

// Get the vector representing global up (away from gravity)
// within the device's coordinate system.
Vector3 localDown = Quaternion.Inverse(Input.gyro.attitude) * Vector3.down;

// Extract our roll rotation - how much gravity points to our left or right.
float rollDegrees = Mathf.Asin(localDown.x) * Mathf.Rad2Deg;

// Extract our pitch rotation - how much gravity points forward or back.
float pitchDegrees = Mathf.Atan2(localDown.y, localDown.z) * Mathf.Rad2Deg;


This gives 0 pitch when the gravity vector points along the phone's z+ axis. If you want your neutral pitch to sit somewhere else, or adapt based on the player's resting pose when holding the phone, you can shift & wrap the angle accordingly using Mathf.DeltaAngle

• Pitching the phone forwards and backwards in any direction appears to produce the correct results, but rolling from left to right changes both the roll and pitch. How would I fix this? Also wouldn't it be more efficient to use Input.gyro.gravity? @DMGregory – airvine Nov 8 '19 at 3:07
• You can use the same Asin technique for both if you like — it'll just give you a somewhat narrower range of pitch and you may need to manually adjust it when playing mostly-horizontal vs mostly-vertical. Re: gravity, feel free to use it if it works for you. – DMGregory Nov 8 '19 at 3:10
• +1 That worked, thank you so much! I'd upvote your answer, but my reputation is too low. – airvine Nov 8 '19 at 3:24