Распознать текущее положение устройства как плоское

Итак, у меня есть это приложение Im, работающее над тем, где вы можете катить мяч вокруг экрана, наклоняя устройство вокруг (акселерометр). Как я могу изменить приведенный ниже код, так что мне не нужно держать телефон плоским и иметь его в качестве нейтральной точки баланса. Я хочу, чтобы любой наклон, который у вас есть с устройством в момент загрузки приложения, будет точкой нервного равновесия. Из этого текущего угла вы держите устройство, которое является нейтральной точкой. Нейтральная точка баланса означает точку, где мяч в значительной степени неподвижен. Надеюсь, это ясно, что я хотел бы. Кроме того, приложение имеет только ландшафт.

note. Приведенный ниже код работает на 100 процентов хорошо, так как ему нужно, чтобы он работал для моего приложения. Просто мне нужно держать телефон плоским, чтобы катить мяч вокруг...

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

}

Ответ 1

Как уже упоминалось, нам нужно поймать начальное положение устройства (значение акселерометра) и использовать его как нулевую ссылку. Мы улавливаем контрольное значение один раз, когда игра начинается и вычитает это значение из каждого следующего обновления акселерометра.

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;

Акселерометр обновляется. Поскольку приложение использует только ландшафтный правый режим, мы отображаем у-ускорение на скорость 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;
}

EDIT: альтернативный способ

Кстати, я нашел альтернативный способ решить эту проблему. Если вы используете 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);

Также нам нужно установить физические границы экрана, чтобы ограничить движение шара.

Ответ 2

Во-первых, комментарий Ларма дает правильный ответ для определения начальной точки.

Однако, если вы пытаетесь определить наклон устройства (отношение), вы хотите использовать гироскоп, а не акселерометр. Акселерометр сообщает, как быстро устройство движется в каждом направлении. Это полезно для определения того, быстро ли пользователь движется или встряхивает устройство, но не помогает вам вообще определить, наклоняется ли устройство. Гироскоп обеспечивает отношение тока устройства и скорость вращения.

Так как кажется, что вы пытаетесь реализовать мяч, который будет "катиться" вокруг стола, когда пользователь наклоняет устройство, вы, вероятно, захотите получить отношение. Чтобы получить отношение, используйте startDeviceMotionUpdatesToQueue: withHandler:. Затем вы можете использовать свойство отношения объекта CMDeviceMotion, чтобы узнать, как устройство ориентировано на каждой оси.

Ответ 3

Почему бы вам просто не взять цифры в момент запуска в качестве базовой линии и сохранить их как свойство класса. Любые дополнительные чтения, которые у вас есть, вы можете просто добавить/вычесть текущие номера с помощью базовой линии. Если я ошибаюсь, это должно дать вам желаемые результаты.