2
\$\begingroup\$

I am developing a Unity game where I need to tilt an iOS device to steer a ship as it flies in a straight line. The steering should be consistently accurate in any orientation, e.g. if the player holds the device directly in front of their face or if they are slouched over with the phone facing the ground etc. This is shown in the image below.

Phone upright, rolling to the right / Phone flat, yawing to the right

A summary of my needs are:

  • Tilt the device to alter the on-screen ship's rotation (one axis only - in Unity this is the object's y-axis since it is a top-down 3D game). This is why I say "tilt" (think Temple Run), panning around in space to change all axes is not necessary and needs to be prevented.
  • I need to know how quickly the user is tilting their device to control the steering speed
  • I need to know the current tilt angle at any instance
  • I need to enforce a maximum tilt angle (e.g. tilting beyond +/- 45 degree has no further effect on the ship)
  • I need smoothness and no jerkiness

Can someone with knowledge please advise me how to achieve this with the gyroscope in Unity? I have been experimenting with my phone using the Unity Remote app and playing around with the Input.gyro and Input.acceleration functions, but I cannot make sense of the angle data as the pitch and roll of the device changes.


UPDATE:

Okay so I have made progress on this and have managed to achieve the above. Here is what I do:

Step 1: I read the gyroscope attitude and store it as a device rotation. The function I use is this:

private Quaternion GetDeviceRotation()
{
    if (SystemInfo.supportsGyroscope)
        return new Quaternion(0.5f, 0.5f, -0.5f, 0.5f) * Input.gyro.attitude * new Quaternion(0, 0, 1, 0);
}

I found this function here, whilst I am not entirely clear how it works, the author states "it is a wrapper for gyro input which adjusts the forward axis and takes different device orientations into account".

This gives me the current rotation of the device. I take this at the start of the game, or whenever I need to set the reference, and save it as the "ReferenceRotation".

Step 2: I then get the roll angle about the z-axis of the ReferenceRotation using this function:

private float GetRollAngle(Quaternion refRotation, Quaternion currentRotation)
{
    // Eliminate the XY planes whilst maintaining Z
    Quaternion eliminationOfXY = Quaternion.Inverse(Quaternion.FromToRotation(refRotation * Vector3.forward, currentRotation * Vector3.forward));

    // Deduce the z-axis roll angle
    Quaternion rotationZ = eliminationOfXY * currentRotation;
    return rotationZ.eulerAngles.z;
}

This function takes two quaternions and eliminates the XY axes. It then finds the roll about the z-axis. To get this angle for the ReferenceRotation, I pass it as both parameters in the signature.

Step 3: Now that I have a reference z-axis angle from which to measure tilt, I grab the real-time device rotation "CurrentRotation" in the Update() loop using the function in Step 1. I then compare this with the "ReferenceRotation" to get the current roll angle, using the function in Step 2. In this case, I pass ReferenceRotation and CurrentRotation respectively.

This then gives me the 'live' roll angle perfectly, without any issues.

I can point the device in front of me, set the reference, and tilt to see the correct roll angle change. At this referenced orientation, changing the pitch (x-axis) or yaw (y-axis) has no effect on the roll angle, which is exactly how it should be. If I then change the orientation of the device, i.e. point it up to the sky and reset the reference, the same behaviour is observed.

In effect, it works perfectly!


What then is the issue?

The issue is that I need to do the same thing for yaw about the y-axis (I found that rolling about z-axis was a bit awkward in terms of gameplay). By "yaw" I mean this motion:

enter image description here

For some reason, my method does not seem to work as well for yaw as it did for roll. Let's repeat the steps:

Step 1: As above

Step 2: Similar to the above, but instead I adjust the function to use Vector3.up instead, to correspond to the y-axis:

private float GetYawAngle(Quaternion refRotation, Quaternion currentRotation)
{
    // Eliminate the XZ planes whilst maintaining Y
    Quaternion eliminationOfXZ = Quaternion.Inverse(Quaternion.FromToRotation(refRotation * Vector3.up, currentRotation * Vector3.up));

    Quaternion yDifference = eliminationOfXZ * currentRotation;
    return yDifference.eulerAngles.y;

}

Step 3: As above

For some reason, this does not work and the measurement of the yaw angle goes completely haywire. After some experimentation, I found that if instead I pass Quaternion.Identity as the first parameter, during both function calls (i.e. Step 2 and 3), the method does actually work! I can accurately set the reference yaw angle and measure its change.

BUT in this case, changing the roll DOES affect the yaw angle. This is bad and I need to eliminate this problem. I need the same behaviour as the z-axis, i.e. movement in the other axes not to affect my axis of interest. So in this case, I do not want pitch or roll affecting my yaw reading.

My suspicions lead to me believe the issue boils down to the function in the very first step. The author mentioned how this function is "adjusted for the forward" axis, though I have no idea what that means as I struggle with Quaternions. Perhaps I need to re-adjust it for the up axis?

If anyone has any ideas, I would appreciate it tremendously. Apologies for the very long post, I hope it all made sense!

\$\endgroup\$
1
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$ Commented Apr 25, 2022 at 12:44

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.