【问题标题】:Obtain absolute rotation using CMDeviceMotion?使用CMDeviceMotion获得绝对旋转?
【发布时间】:2026-02-04 18:30:02
【问题描述】:

我正在使用 Sprite Kit 构建一个简单的游戏,屏幕没有旋转,但我想知道用户握住手机的角度以获取游戏机制。

使用加速度计 (x, y) 可以轻松检索我想要获取的值,但我发现这不可靠,因此我尝试使用 CMDeviceMotion 存档更好的结果。我可以得到与data.acceleration.y 等效的值,但我不知道如何得到与data.acceleration.x 等效的值。

if let data = motionManager.accelerometerData? {
    let y = CGFloat(data.acceleration.y)
    let x = CGFloat(data.acceleration.x)
}

// Device Motion
if let attitude = motionManager.deviceMotion?.attitude? {
    let y = CGFloat(-attitude.pitch * 2 / M_PI) // This matches closely with data.acceleration.y
    let x = ??????????
}

如何使用CMDeviceMotion 计算与data.acceleration.x 的等效值?

【问题讨论】:

标签: ios swift accelerometer gyroscope cmmotionmanager


【解决方案1】:

听起来您需要运动管理器的device motion,如果您希望旋转值考虑陀螺仪和加速度计输入。根据文档:

CMDeviceMotion 的一个实例封装了设备的姿态、旋转速率和加速度的测量值。

因此,与其直接监控陀螺仪数据,不如监控设备运动。下面的示例演示了如何逐帧获取设备运动。我刚刚决定直接打印CMAttitude 对象,但是从这个对象中,您可以直接访问设备的俯仰、滚动和偏航 (and more),如果我没记错的话,这正是您的正在寻找。

import SpriteKit
import CoreMotion

class GameScene: SKScene {
    let motionManager = CMMotionManager()

    override func didMoveToView(view: SKView) {
        motionManager.deviceMotionUpdateInterval = 1.0 / 30.0
        motionManager.startDeviceMotionUpdates()
    }

    override func willMoveFromView(view: SKView!) {
        motionManager.stopDeviceMotionUpdates()
    }

    override func update(currentTime: CFTimeInterval) {
        if let attitude = motionManager.deviceMotion?.attitude? {
            println(attitude)
            let y = CGFloat(-attitude.pitch * 2 / M_PI)
            let x = CGFloat(-attitude.roll * 2 / M_PI)
        }
    }
}

如果您使用的是 Swift 2,则需要进行一些小的更改,如下所示。

class GameScene: SKScene {
    let motionManager = CMMotionManager()

    override func didMoveToView(view: SKView) {
        motionManager.deviceMotionUpdateInterval = 1.0 / 30.0
        motionManager.startDeviceMotionUpdates()
    }

    override func willMoveFromView(view: SKView) {
        motionManager.stopDeviceMotionUpdates()
    }

    override func update(currentTime: CFTimeInterval) {
        if let attitude = motionManager.deviceMotion?.attitude {
            print(attitude)
            let y = CGFloat(-attitude.pitch * 2 / M_PI)
            let x = CGFloat(-attitude.roll * 2 / M_PI)
        }
    }
}

有关这方面的更多信息,请咨询this image

【讨论】:

  • 嗨@0x7fffffff 我正在尝试按照您的建议使用 CMAttitude 获得绝对旋转,我可以使用 -attitude.pitch * 2 / M_PI 检索 Y 但我不明白如何检索 X。我正在添加额外的赏金。
  • @lisovaccaro 我更新了我的代码。如果我正确理解问题应该会有所帮助。
  • 嗨@0x7fffffff 我一直在阅读有关俯仰、滚动和偏航的信息,但我无法将其转换为绝对旋转。我想我想出了一个计算 x 的算法(见我的答案),但我不确定它是否是最可靠的。
【解决方案2】:

您可以通过读取CoreMotion提供的陀螺仪数据来获得角度。像这样初始化一个 CM 对象:

class GameScene: SKScene {
    var motionManager = CMMotionManager()
    override func didMoveToView(view: SKView) {
        motionManager.startGyroUpdates()
    }
}

然后在更新函数中,就可以读取了:

override func update(currentTime: CFTimeInterval) {
    if let data = motionManager.gyroData {
        let x = data.rotationRate.x
        let y = data.rotationRate.y
        let z = data.rotationRate.z
    }
}

您现在可以为您的游戏机制使用 x、y 和 z 值。但是,请注意,它给出的是当前旋转 rate 而不是绝对旋转。如果你需要的话,你可以自己跟踪它。或者,您可以使用加速度计数据:

motionManager.startAccelerometerUpdates()
...
if let data = motionManager.accelerometerData {
    let x = data.acceleration.x
}

【讨论】:

  • 嗨,teshel,我添加了一个赏金,您能否添加代码以获得加速度计和陀螺仪的绝对旋转? (相当于 data.acceleration.x 和 data.acceleration.y)
【解决方案3】:

这就是我现在正在尝试的。我是通过实验得到的,所以我无法判断它是否正确,可能有更好的解决方案。

            if let attitude = motionManager.deviceMotion?.attitude? {
                 // Convert pitch and roll to [-1, 1] range
                let pitch = CGFloat(-attitude.pitch * 2 / M_PI)
                let roll = CGFloat(abs(attitude.roll) > M_PI / 2 ? (2 - abs(attitude.roll) * 2 / M_PI) * (attitude.roll > 0 ? 1 : -1) : attitude.roll * 2 / M_PI)

                // Calculate x and y
                let y = pitch
                let x = roll*(1.0-abs(pitch))
            }

【讨论】: