Gyro rotation but in local space?

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.

public GameObject rot; //a null object directly controlled by Gyro
    Vector3 alignedAngles;
    void Start()
    {
    }
    void Update()
    {     
        alignedAngles = rot.transform.localEulerAngles;        
        gameObject.transform.eulerAngles = new Vector3(alignedAngles.x, 0.0f, alignedAngles.y);//replace  z

    }

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.

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?

Codewise, you could try this:

public class TiltReader : MonoBehaviour
{
    public Quaternion tilt;

    Quaternion inverseFlat;

    void Start()
    {
        inverseFlat = Quaternion.Inverse(Gyroscope.attitude);
    }

    void Update()
    {
        tilt = Gyroscope.attitude * inverseFlat;
    }
}

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.

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.

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)

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?

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.

gyro = Input.gyro;
        gyrEuler = new Vector3(gyro.attitude.eulerAngles.x, -gyro.attitude.eulerAngles.z+180, gyro.attitude.eulerAngles.y);
        gameObject.transform.eulerAngles = gyrEuler;

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?

    gyro = Input.gyro;

    Quaternion orientation = gryo.attitude;
    Vector3 angles = orientation.eulerAngles;
    Quaternion undoY = Quaternion.Euler(0, -angles.y, 0);
    orientation = orientation * undoY;
    gameObject.transform.rotation = orientation;

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.

gyrEuler = new Vector3(gameObject.transform.eulerAngles.x - gyro.rotationRateUnbiased.x/10, 0f, gameObject.transform.eulerAngles.z - gyro.rotationRateUnbiased.y /10);

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:

using UnityEngine;

public class ReadGyro : MonoBehaviour
{
    public Transform pieceTransform;

    void Update()
    {
        Quaternion rot = transform.rotation;
        Vector3 angles = rot.eulerAngles;

        Quaternion undoY = Quaternion.Euler(0, -angles.y, 0);
        pieceTransform.rotation = undoY * transform.rotation;
    }
}

You’ll need to change Line 9 to this:

        Quaternion rot = Input.gyro.attitude;

Does this look like what you’re trying to do?

This (rough) script should do what you need.

using UnityEngine;

public class RotationScript : MonoBehaviour {
    Quaternion referenceRotation;

    private void Start () {
        Input.simulateMouseWithTouches = true;

        Input.gyro.enabled = true;
        Screen.orientation = ScreenOrientation.Portrait;

        referenceRotation = Quaternion.Inverse( Input.gyro.attitude );
    }

    void Update () {
        if( Input.GetMouseButtonDown( 0 ) ) {
            // re-orient to current position on touch to prove that it can always be relative to current screen orientation
            referenceRotation = Quaternion.Inverse( Input.gyro.attitude );
        }

        var raw = Input.gyro.attitude;
        // make the rotation relative to the current rotation rather than the world
        var rot = referenceRotation * raw;

        var euler = rot.eulerAngles;

        var zRot = Quaternion.AngleAxis( euler.z, Vector3.forward );

        // reverse the rotation about the z-axis
        rot = Quaternion.Inverse( zRot ) * rot;

        // rotate about the x axis to make the y axis point the right direction
        rot = Quaternion.AngleAxis( 90, Vector3.right ) * rot;

        var up = rot * Vector3.up;
        var right = rot * Vector3.right;
        var fwd = rot * Vector3.forward;

        Debug.DrawRay( transform.position, up, Color.green );
        Debug.DrawRay( transform.position, right, Color.red );
        Debug.DrawRay( transform.position, fwd, Color.blue );

        transform.rotation = rot;
    }
}

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.

Thanks! I’ll give it a try.

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?

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:

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. :smile: