Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
182 views
in Technique[技术] by (71.8m points)

c# - Making a ball balancing game with the gyroscope of mobile devices

as the title already says, i'm currently working on a small ball balancing game using the gyroscope rotation to rotate a floating platform with a ball on it. The problem i always encounter is: i can't lock the y axis. I used euler angles and it worked perfectly, but then i have to deal with gimbal lock. Because i don't want to deal with this issue of euler, i tried using quaternions instead and it does work in some kind of way. I can set the rotation and can define a calibration offset for the gyroscope. If i wouldn't calculate this offset, the rotation would be absolute and not relative to the ingame world space. But i always have some sort of y rotation going on, even if i set it to 0 for the quaternion.

For example: i start the game, calibrate the gyro to my rotation but after that rotate around the y axis in real world space. The platform keeps facing the camera, but so doesn't the rotation axes.

The normal behaviour and how it always should be (no rotation on y axis in real world space): https://www.nani-games.net/share/umSc6XH1vE.gif

The actual behaviour and how it shouldn't be (rotation on y axis in real world space): https://www.nani-games.net/share/AVicmV4fWe.gif

A list of what i already tried:

  • Using euler angles
  • Using a combination of euler angles and quaternions in order to make up a new quaternion with desired parameters / rotations
  • Using Quaternion.AngleAxis in order to change the order of the euler axes
  • Checking the box "Freeze Y Rotation" of the platform's rigidbody under constraints (this doesn't work because i'm not applying force to rotate but rather set the rotation directly)

How my scene is build up: https://www.nani-games.net/share/Unity_fAQKW7cfPK.png

I have two scripts which are used to get the gyroscope rotation, make it useful in unity and use it to rotate other objects.

A static script called DeviceRotation. It activates the gyro and gets the rotation from it:

using UnityEngine;

static class DeviceRotation
{
    public static Quaternion offsetRotation;

    private static bool gyroInitialized = false;

    public static bool HasGyroscope
    {
        get
        {
            return SystemInfo.supportsGyroscope;
        }
    }

    public static Quaternion Get()
    {
        if (!gyroInitialized)
        {
            InitGyro();
        }

        return HasGyroscope
            ? ReadGyroscopeRotation()
            : Quaternion.identity;
    }

    private static void InitGyro()
    {
        if (HasGyroscope)
        {
            Input.gyro.enabled = true;
            Input.gyro.updateInterval = 0.0167f; // 60Hz
        }
        gyroInitialized = true;
    }

    private static Quaternion ReadGyroscopeRotation()
    {
        return new Quaternion(0.5f, 0.5f, -0.5f, 0.5f) * Input.gyro.attitude * new Quaternion(0, 0, 1, 0);
    }

    public static void calibrateCoords()
    {
        Quaternion deviceRotation = Get();
        offsetRotation = deviceRotation;
    }
}

The second script which is appended to the platform object. It gets a calibration quaternion, calculates a new quaternion out of the calibration quaternion and a constantly updating quaternion (both taken from the gyroscopes rotation) and then sets the platforms transform to this new rotation:

using UnityEngine;
using UnityEngine.UI;

public class Gyroscope : MonoBehaviour
{
    public Button resetSphere;
    public Button calibrate;
    public GameObject sphere;

    public Text platformCoords;
    public Text gyroOffsetCoords;

    void Start()
    {
        DeviceRotation.calibrateCoords();
        calibrate.onClick.AddListener(DeviceRotation.calibrateCoords);

        setGyroRotation();
        resetSphere.onClick.AddListener(ResetSphere);
    }

    void Update()
    {
        setGyroRotation();

        Vector3 offsetRotationEuler = DeviceRotation.offsetRotation.eulerAngles;
        gyroOffsetCoords.text = "GyroOffset:
" +
                                "X: " + offsetRotationEuler.x + "
" +
                                "Y: " + offsetRotationEuler.y + "
" +
                                "Z: " + offsetRotationEuler.z;
    }

    private void ResetSphere()
    {
        GameObject temp_sphere = Instantiate(sphere);
        temp_sphere.transform.position = new Vector3(0, 3, 0);
    }

    private void setGyroRotation()
    {
        Quaternion deviceRotation = DeviceRotation.Get();
        Quaternion newRotation = Quaternion.Inverse(DeviceRotation.offsetRotation) * deviceRotation;
        transform.localRotation = newRotation;

        Vector3 platformAngles = transform.localRotation.eulerAngles;
        platformCoords.text = "Platform:" + "
" +
                              "X: " + platformAngles.x + "
" +
                              "Y: " + platformAngles.y + "
" +
                              "Z: " + platformAngles.z;
    }
}

So, to conclude everything: my problem is that i cannot lock one axis (the y axis) of a quaternion. I also tried a combination of using euler to make the desired rotation i want and then converting it to a quaternion (that's why those two functions QuaternionToEuler and EulerToQuaternion exist in the script OffsetGyro), but unfortunately this caused gimbal lock (what a surprise i guess).

I really need help at this quaternion problem, i'm stuck with it since several days.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Explanation in comments:

// FIELDS
// device rotation from last frame
// init with DeviceRotation.Get() where appropriate
private Quaternion prevDeviceRotation; 

//...

// UPDATE LOGIC
// get the new device rotation
Quaternion newDeviceRotation = DeviceRotation.Get();

// Find difference between previous and current device rotation along previous axes
Quaternion relativeDelta = Quaternion.Inverse(prevDeviceRotation) * newDeviceRotation;

// Apply that difference to the transform's rotation to get an unfixed rotation 
// for the transform
Quaternion unfixedTransformRotation = transform.rotation * relativeDelta ;

// determine the unfixed up direction after that rotation is applied.
Vector3 unfixedUp = unfixedTransformRotation  * Vector3.up;

// find angle of up from world up
float angle = Vector3.Angle(Vector3.up, unfixedUp); 
Vector3 axis = Vector3.Cross(Vector3.up, unfixedUp); 

// if up is colinear with world up, use identity or flipped rotation
if (axis == Vector3.zero) 
{ 
    transform.rotation = Quaternion.AngleAxis(unfixedUp.y >= 0f ? 0f: 180f,
            Vector3.right); 
} 
else 
{ 
    transform.rotation = Quaternion.AngleAxis(angle, axis);
}

// update prevDeviceRotation
prevDeviceRotation = newDeviceRotation;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...