将当前设备位置识别为平放状态。

11
我正在开发一个应用程序,可以通过倾斜设备(加速计)来使球在屏幕上滚动。如何修改下面的代码,以便我不必将手机平放,并将其作为中性平衡点。我想要的是无论你现在使用设备的倾斜角度为何,都将其作为应用程序加载时的中性平衡点。从设备当前的角度开始,那就是中性点。中性平衡点意味着球基本保持静止的点。希望我的意思很清楚。此外,该应用程序仅支持横向模式。注意下面的代码已经完美地按照我的应用程序需求工作了。只是我需要将手机平放才能使球滚动...
CGRect screenRect;
CGFloat screenHeight;
CGFloat screenWidth;
double currentMaxAccelX;
double currentMaxAccelY;
@property (strong, nonatomic) CMMotionManager *motionManager;

-(id)initWithSize:(CGSize)size {

     //init several sizes used in all scene
        screenRect = [[UIScreen mainScreen] bounds];
        screenHeight = screenRect.size.height;
        screenWidth = screenRect.size.width;

    if (self = [super initWithSize:size]) {

        self.motionManager = [[CMMotionManager alloc] init];
        self.motionManager.accelerometerUpdateInterval = .2;

       [self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
                                                 withHandler:^(CMAccelerometerData  *accelerometerData, NSError *error) {
                                                    [self outputAccelertionData:accelerometerData.acceleration];
                                                     if(error)
                                                     {
                                                         NSLog(@"%@", error);
                                                     }
                                                 }];
    }

    return self;

}

-(void)outputAccelertionData:(CMAcceleration)acceleration{

    currentMaxAccelX = 0;
    currentMaxAccelY = 0;

    if(fabs(acceleration.x) > fabs(currentMaxAccelX))
    {
        currentMaxAccelY = acceleration.x;
    }
    if(fabs(acceleration.y) > fabs(currentMaxAccelY))
    {
        currentMaxAccelX = acceleration.y;
    }
}

-(void)update:(CFTimeInterval)currentTime {

    /* Called before each frame is rendered */

    //set min and max bounderies
    float maxY = screenHeight - (self.ball.size.width/2);
    float minY = 0 + (self.ball.size.width/2);

    float maxX = screenWidth - (self.ball.size.height/2);
    float minX = 0 + (self.ball.size.height/2);

    float newY = 0;
    float newX = 0;
    //left and right tilt
    if(currentMaxAccelX > 0.05){
        newX = currentMaxAccelX * -10;
    }
    else if(currentMaxAccelX < -0.05){
        newX = currentMaxAccelX*-10;
    }
    else{
        newX = currentMaxAccelX*-10;
    }
    //up and down tilt
    newY = currentMaxAccelY *10;

    newX = MIN(MAX(newX+self.ball.position.x,minY),maxY);
    newY = MIN(MAX(newY+self.ball.position.y,minX),maxX);

    self.ball.position = CGPointMake(newX, newY);

}

4
如果我理解正确的话:在applaunch中注册x/y值,那就是你的新“零点”。然后使用这个新零点作为偏移量来进行想要做的事情。 - Larme
@Larme 这应该是一个答案,因为这是正确的方法。 - Krumelur
是的,但我希望答案与我的代码相关。在我的搜索中很难找到如何使用偏移量的任何代码示例。 - 4GetFullOf
3个回答

6

首先,Larme的评论给出了正确的答案来确定起点。

然而,如果您想确定设备的倾斜(姿态),您需要使用陀螺仪而不是加速度计。加速度计告诉您设备在每个方向上移动的速度。这对于确定用户是否快速移动或摇动设备很有用,但并不能帮助您确定设备是否被倾斜。陀螺仪提供设备的当前姿态和旋转速率。

由于您似乎正在尝试实现一个球,当用户倾斜设备时,它将“滚动”在桌子上,所以您可能想要获取姿态。要获取姿态,请使用startDeviceMotionUpdatesToQueue:withHandler:。然后,您可以使用CMDeviceMotion对象的attitude属性来找出设备在每个轴上的方向。


以上代码对我来说百分之百有效,在手机倾斜的方向上将球在屏幕上“滚动”。我已经询问了如何使用已提供的代码进行偏移的答案,因为这是我已经在我的应用程序中运行的代码。感谢您的建议。 - 4GetFullOf

4
正如之前提到的,我们需要捕捉设备的初始位置(加速度计值)并将其用作零参考点。我们在游戏开始时只需捕捉一次参考值,并从每个后续的加速度计更新中减去此值即可。
static const double kSensivity = 1000;

@interface ViewController ()
{
    CMMotionManager *_motionManager;
    double _vx, _vy;                         // ball velocity
    CMAcceleration _referenceAcc;            // zero reference
    NSTimeInterval _lastUpdateTimeInterval;  // see update: method
}

起初,球是静止的(速度为0)。零参考无效。我在CMAcceleration中设置了显著的值来标记它无效:

_referenceAcc.x = DBL_MAX;

加速度计更新。由于应用程序仅使用横向右侧模式,因此我们将y加速度映射到x速度,将x加速度映射到y速度。accelerometerUpdateInterval系数是必需的,以使速度值与更新率无关。我们对x加速度使用负灵敏度值,因为加速度计X轴的方向与横向右侧方向相反。

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        _vx = 0;
        _vy = 0;
        _referenceAcc.x = DBL_MAX;

        _motionManager = [CMMotionManager new];
        _motionManager.accelerometerUpdateInterval = 0.1;

        [_motionManager
         startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
         withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
             CMAcceleration acc = accelerometerData.acceleration;

             if (_referenceAcc.x == DBL_MAX) {
                 _referenceAcc = acc;
                 _referenceAcc.x *= -1;
                 _referenceAcc.y *= -1;
             }

             _vy += kSensivity * (acc.x+_referenceAcc.x) * _motionManager.accelerometerUpdateInterval;
             _vx += -kSensivity * (acc.y+_referenceAcc.y) * _motionManager.accelerometerUpdateInterval;
         }];

        self.ball = [SKSpriteNode spriteNodeWithImageNamed:@"ball"];
        self.ball.position = CGPointMake(self.size.width/2, self.size.height/2);
        [self addChild:self.ball];
    }
    return self;
}

您的update:方法没有考虑currentTime值。更新调用之间的时间间隔可能不同。最好根据时间间隔更新距离。

- (void)update:(NSTimeInterval)currentTime {
    CFTimeInterval timeSinceLast = currentTime - _lastUpdateTimeInterval;
    _lastUpdateTimeInterval = currentTime;

    CGSize parentSize = self.size;
    CGSize size = self.ball.frame.size;
    CGPoint pos = self.ball.position;

    pos.x += _vx * timeSinceLast;
    pos.y += _vy * timeSinceLast;

    // check bounds, reset velocity if collided
    if (pos.x < size.width/2) {
        pos.x = size.width/2;
        _vx = 0;
    }
    else if (pos.x > parentSize.width-size.width/2) {
        pos.x = parentSize.width-size.width/2;
        _vx = 0;
    }

    if (pos.y < size.height/2) {
        pos.y = size.height/2;
        _vy = 0;
    }
    else if (pos.y > parentSize.height-size.height/2) {
        pos.y = parentSize.height-size.height/2;
        _vy = 0;
    }

    self.ball.position = pos;
}

编辑:备选方法

顺便说一下,我发现了一种解决方法。如果您使用的是SpriteKit,则可以根据加速度计的变化配置物理世界的重力。在这种情况下,就无需在update:方法中移动球。

我们需要向球精灵添加物理体并将其设置为动态:

self.physicsWorld.gravity = CGVectorMake(0, 0);  // initial gravity
self.ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.ball.size.width/2];
self.ball.physicsBody.dynamic = YES;
[self addChild:self.ball];

在加速度计处理程序中设置更新后的重力:

// set zero reference acceleration 
...

_vy = kSensivity * (acc.x+_referenceAcc.x) * _motionManager.accelerometerUpdateInterval;
_vx = -kSensivity * (acc.y+_referenceAcc.y) * _motionManager.accelerometerUpdateInterval;

self.physicsWorld.gravity = CGVectorMake(_vx, _vy);

此外,我们需要设置屏幕的物理边界,以限制球的移动。

不起作用,你知道我的球对象是SKSpriteNode吗? 属性中心和superview在该对象中无法识别。 - 4GetFullOf
你也不知道如何初始化CADDisplayLink吗? - 4GetFullOf
1
@Rookie 是的,我错过了这是一个SpriteKit框架。回答已更新。 - vokilam
这个很棒。谢谢。最后一个问题,我知道我可以通过降低kSensitivity来减少灵敏度,但是如果球向一个方向滚动(例如向左),而我快速向右倾斜,我希望球更快地改变方向。 - 4GetFullOf
基本上是更快的响应方向变化* - 4GetFullOf
1
@Rookie 尝试介绍速度因子:_vy = _vy*0.8 + kSensivity * <公式的其余部分>+= 被更改为 =_vx 同理。 - vokilam

0
为什么不在启动时将数字作为基线保存为类属性呢?您可以简单地使用现有的任何进一步读数来将当前数字与基线相加/减。除非我错了,这应该可以给您所需的结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接