针对不同的 "flat" 位置校准陀螺仪,然后提取 x 和 y 倾斜

Calibrating a gyroscope for different "flat" positions, then extracting x and y tilt

我有一个自上而下的 2D 球类游戏,就像旧的 Labyrinth 球类游戏一样。我的游戏是在手机上玩的 phone。用户通过倾斜 phone 来控制它。这个想法是,当您将屏幕倾斜设置时,球会真实地滚动到倾斜的“底部”。例如,如果 phone 平放在 table 上,球不应该飞向任何地方。但如果我将设备向上倾斜,就像翻书一样,那么球应该会落到屏幕底部。使用加速度计很容易做到这一点,但是...

我的问题是如何校准陀螺仪,以便可以从设备的任何起始“平面”3d 旋转轻松玩游戏。我希望用户能够“校准”游戏,以便无论他们想如何握住设备,他们都可以正确控制球。我在想:

  1. 如果 phone 平放在 table 上,那么倾斜它会“正常”工作,如第一段所述。

  2. 如果phone是倒过来的,那么应该是#1的反面。这适用于那些想要在床上将 phone 举过头顶之类的人。

  3. 如果 phone 与墙壁平行(如壁挂式电视),那么它应该就像墙壁是平的 table,如果说得通的话。

目前我的代码是:

public void OnHitCalibrate() {
    _offset = Input.gyro.attitude;
}

public void Update() {

    if(!_hasGyro) return;

    Quaternion reading = Input.gyro.attitude;
    reading *= Quaternion.Inverse(_offset); //subtract the offset
    _gyro.rotation = reading;
}

这得到了我想要的输入类型,但我不明白 Input.gyro.attitude 返回的四元数轴如何帮助我找到我的 2d 倾斜值。例如,当设备指向北方,并且姿态与Quaternion.Identity相同时,值似乎发生了合理的变化——当我将attitude变成欧拉角时,x值发生了变化对应于我的 y 倾斜,y 值的变化对应于我的 x 倾斜的变化。但是当我旋转时,一切都变了,当我将 phone 旋转得更平行于墙壁时,数字似乎与欧拉角中的轴没有直接相关,即使我使用 OnHitCalibrate 函数并减去偏移量。有什么方法可以解释 attitude 以便我始终可以知道设备正在转向哪个方向?

我的最终目标是将 attitude 提炼成表示力的 Vector2——其中 x 和 y 都在 -1 和 1 之间,其中 1 表示向上或向右的最大力,-1 是向下或向左的最大力,0 表示球的力没有变化。

更新:已解决!我附上了处理陀螺仪偏移量的 class。希望对您有所帮助!请注意,它是为 Unity C# 编码的,并且已经包含了一些我的自定义 classes。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum TiltModule { GYROSCOPE, ACCELEROMETER, KEYBOARD_ONLY, NONE }


public class CalibratedGyroscope : SingletonKTBehavior<CalibratedGyroscope>
{
    public bool _supported;

    private Quaternion _off;
    private Vector3 _offEuler;
    int _activeSemaphore = 0;
    private float _degreesForFullTilt = 10;

    public Vector2 _lastTilt;

    public void Init() {
        _off = Quaternion.identity;
        _supported = SystemInfo.supportsGyroscope;
    }

    public bool Activate(bool isActivated) {
        if(isActivated) _activeSemaphore++;
        else _activeSemaphore--;

        _activeSemaphore = Mathf.Max(_activeSemaphore, 0);

        if(_activeSemaphore > 0) {
            if(_supported) {
                Input.gyro.enabled = true;
            } else {
                return false; //everything not ok; you requested gyro but can't have it!
            }
        } else {
            if(_supported) {
                Input.gyro.enabled = false;
            }
        }
        return true; //everything ok;

    }

    public void Deactivate() {
        _activeSemaphore = 0;
    }

    public void SetCurrentReadingAsFlat() {
        _off = Input.gyro.attitude;
        _offEuler = _off.eulerAngles;
    }

    public Vector3 GetReading() {
        if(_supported) {
            return (Quaternion.Inverse(_off) * Input.gyro.attitude).eulerAngles;
        } else {
            Debug.LogError("Tried to get gyroscope reading on a device which didn't have one.");
            return Vector3.zero;
        }
    }

    public Vector2 Get2DTilt() {
        Vector3 reading = GetReading();
        
        Vector2 tilt = new Vector2(
            -Mathf.DeltaAngle(reading.y, 0),
            Mathf.DeltaAngle(reading.x, 0)
        );

        //can't go over max
        tilt.x = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.x) * 2 - 1;
        tilt.y = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.y) * 2 - 1;
        
        //get phase
        tilt.x = Mathf.Clamp(tilt.x, -1, 1);
        tilt.y = Mathf.Clamp(tilt.y, -1, 1);

        _lastTilt = tilt;

        return tilt;
    }

    public string GetExplanation() {
        Vector3 reading = GetReading();
        
        string msg = "";
        
        msg += "OFF: " + _offEuler + "\n";

        Vector2 tilt = new Vector2(
            -Mathf.DeltaAngle(reading.y, 0),
            Mathf.DeltaAngle(reading.x, 0)
        );

        msg += "DELTA: " + tilt + "\n";

        //can't go over max
        tilt.x = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.x) * 2 - 1;
        tilt.y = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.y) * 2 - 1;
        
        msg += "LERPED: " + tilt + "\n";

        //get phase
        tilt.x = Mathf.Clamp(tilt.x, -1, 1);
        tilt.y = Mathf.Clamp(tilt.y, -1, 1);

        msg += "CLAMPED: " + tilt + "\n";

        return msg;

    }

    public void SetDegreesForFullTilt(float degrees) {
        _degreesForFullTilt = degrees;
    }

    
}