【问题标题】:Making a ball balancing game with the gyroscope of mobile devices用移动设备的陀螺仪做球平衡游戏
【发布时间】:2020-12-30 17:07:10
【问题描述】:

正如标题所说,我目前正在开发一个小型球平衡游戏,使用陀螺仪旋转来旋转上面有球的浮动平台。 我经常遇到的问题是:我无法锁定y轴。我使用了欧拉角,它工作得很好,但是我必须处理万向节锁定。因为我不想处理这个欧拉问题,所以我尝试使用四元数,它确实以某种方式工作。 我可以设置旋转并为陀螺仪定义校准偏移。如果我不计算这个偏移量,旋转将是绝对的,而不是相对于游戏世界空间。但是我总是会进行某种 y 旋转,即使我将四元数设置为 0。

例如:我开始游戏,将陀螺仪校准为我的旋转,但之后在现实世界空间中围绕 y 轴旋转。平台始终面向相机,但旋转轴也不会。

正常的行为以及它应该如何(在现实世界空间中没有在 y 轴上旋转): https://www.nani-games.net/share/umSc6XH1vE.gif

实际行为以及不应该如何(在现实世界空间中沿 y 轴旋转): https://www.nani-games.net/share/AVicmV4fWe.gif

我已经尝试过的列表:

  • 使用欧拉角
  • 使用欧拉角和四元数的组合来组成具有所需参数/旋转的新四元数
  • 使用 Quaternion.AngleAxis 来更改欧拉轴的顺序
  • 在约束条件下选中平台刚体的“冻结 Y 旋转”框(这不起作用,因为我没有施加力来旋转,而是直接设置旋转)

我的场景是如何构建的: https://www.nani-games.net/share/Unity_fAQKW7cfPK.png

我有两个脚本用于获取陀螺仪旋转,使其统一使用并用于旋转其他对象。

名为 DeviceRotation 的静态脚本。它激活陀螺并从中获取旋转:

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;
    }
}

附加到平台对象的第二个脚本。它得到一个校准四元数,从校准四元数和一个不断更新的四元数(都取自陀螺仪旋转)中计算出一个新的四元数,然后将平台变换设置为这个新的旋转:

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:\n" +
                                "X: " + offsetRotationEuler.x + "\n" +
                                "Y: " + offsetRotationEuler.y + "\n" +
                                "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:" + "\n" +
                              "X: " + platformAngles.x + "\n" +
                              "Y: " + platformAngles.y + "\n" +
                              "Z: " + platformAngles.z;
    }
}

所以,总结一下:我的问题是我无法锁定四元数的一个轴(y 轴)。 我还尝试了使用 euler 进行所需旋转然后将其转换为四元数的组合(这就是为什么脚本 OffsetGyro 中存在这两个函数 QuaternionToEuler 和 EulerToQuaternion 的原因),但不幸的是这导致了万向节锁定(我猜这真是令人惊讶)。

在这个四元数问题上我真的需要帮助,我已经坚持了好几天了。

【问题讨论】:

  • 有点像在黑暗中拍摄。在设置局部旋转后尝试添加:Vector3 desiredRelativeUp = transform.localRotation * Vector3.up;Vector3 desiredRelativeForward = Vector3.Cross(Vector3.right, desiredRelativeUp);transform.localRotation = Quaternion.LookRotation(desiredRelativeForward, desiredRelativeUp);这将重新定位transform,使其上轴完全相同,但其右轴尽可能与父级的右轴对齐。跨度>
  • @Ruzihm 像 Vector3 desiredRelativeUp = newRotation * Vector3.up; Vector3 desiredRelativeForward = Vector3.Cross(Vector3.right, desiredRelativeUp); transform.localRotation = Quaternion.LookRotation(desiredRelativeForward, desiredRelativeUp); 这样尝试过,但是当您在 z 轴上旋转太远时,除了导致万向节锁定之外,它什么也没做。但仍然感谢您的想法!
  • Interesting... 请附上一张图片,显示在问题中使用代码时变换的局部轴、父级的局部轴以及您要锁定的 y 轴(是世界向上方向,父级的向上方向,还是别的什么?)
  • 实际上,如果这应该允许在轴上咆哮超过 90 度......在这两种情况下所需的行为是什么:它围绕局部 x 轴翻转 180 度。 2)它围绕z轴翻转180度?这些是否应该导致相同的方向?它们彼此围绕 y 轴正好旋转 180 度,这听起来像是您想要避免的。
  • 平台的父 GyroOffset 显然没有做任何事情。它只包含一个用于获取校准四元数的脚本。它的旋转保持不变,并且不会被我拥有的任何脚本更改。唯一的旋转或变换变化是平台对象之一。我的问题是:玩游戏并校准您的旋转,然后在现实世界空间中围绕 y 轴旋转会改变游戏内的旋转轴(在此 gif 中可见)。这里可以看到最后记录的旋转 => 校准轴。

标签: c# unity3d rotation quaternions gyroscope


【解决方案1】:

cmets 中的解释:

// 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;

【讨论】:

  • 我试过了,它似乎锁定了 y 旋转,但有一个 new issue occuring when rotating too often on x and z axes。当在 y 轴上物理旋转时,它会在 z 轴上稍微倾斜平台。
  • @KoolerNolan 我很好奇如何修复不需要的 z 旋转,但对于即时重新定向,如何将平台围绕 z 旋转 180 度然后围绕 x 旋转 180 度导致平台返回如果你没有让它沿着这两个步骤在某个点重新旋转,它的原始位置?
  • 难道没有另一种方法可以通过使用我在问题中提供的代码完全“锁定”y 旋转吗?我正在使用通过单击按钮更新的偏移四元数。使用这个偏移量已经让我走得很远,我敢肯定完成它所剩无几。
  • @KoolerNolan 目前尚不清楚所需的行为究竟是什么......
  • 平台旋转的行为仍然应该是:始终面向相机,无论物理旋转如何,始终旋转相同并且没有云台锁定。 The normal behaviour and how it always should beThe actual behaviour and how it shouldn't be (rotation on y axis in real world space)。我目前遇到的问题是第二个 gif。如果我在做newRotation.y = 0 时物理旋转会导致“移位”旋转轴。
【解决方案2】:

所以,我尝试了所有东西,并尝试随机创建一个可能的解决方案。这是我当前的函数 setGyroRotation:

private void setGyroRotation()
{
    Quaternion deviceRotation = DeviceRotation.Get();
    Quaternion newRotation = Quaternion.Inverse(DeviceRotation.offsetRotation) * deviceRotation; // Calculating "initial" rotation
    axisObject.transform.rotation = newRotation; // Setting the axis' rotation to our "initial" rotation
    Vector3 unfixedUp = newRotation * Vector3.up;

    float angle = Vector3.Angle(Vector3.up, unfixedUp);
    Vector3 axis = Vector3.Cross(Vector3.up, unfixedUp);

    if (axis == Vector3.zero)
    {
        transform.rotation = Quaternion.AngleAxis(unfixedUp.y >= 0f ? 0f : 180f, Vector3.right);
    }
    else
    {
        transform.rotation = Quaternion.AngleAxis(angle, axis);
    }

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

它几乎可以按预期工作:没有万向节锁定(至少没有一个容易发生的情况。仅当您在 x 或 z 上将手机旋转约 180 度时)并且 y 轴被冻结,但初始/校准旋转轴仍然是一个大问题。 这是一个 gif,希望能比其他的更好地显示问题: https://www.nani-games.net/share/93eHK0Fz1t.gif

平台上方的轴显示陀螺仪的旋转并应用了偏移量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多