Ответ 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);
Также нам нужно установить физические границы экрана, чтобы ограничить движение шара.