简体   繁体   English

将当前设备位置识别为平坦

[英]Recognize current device position as flat

So I have this app Im working on where you can roll the ball around the screen by tilting the device around(accelerometer). 所以我有这个应用程序我正在努力你可以通过倾斜设备(加速度计)在屏幕上滚动球。 How can I alter the code below so that I don't have to hold the phone flat and have that as my neutral balance point. 我怎样才能改变下面的代码,这样我就不必将手机保持平衡,并将其作为我的中立平衡点。 What I want is that whatever tilt you have with the device at the moment when the app loads, that will be the neural balance point. 我想要的是,无论你在应用程序加载时对设备的任何倾斜,这都是神经平衡点。 From that current angle your holding the device that is the neutral point. 从当前角度来看,您握住设备即中性点。 Neutral balance point meaning the point where the ball is pretty much still. 中立平衡点意味着球几乎仍然存在的点。 Hope thats clear as to what I would like. 希望这清楚我想要什么。 Also the app is landscapeRight only. 该应用程序也只是landscapeRight。

note The code below works 100 percent well just like it need it to work for my app.Just I need to hold the phone flat to roll the ball around... 注意下面的代码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);

}

First, Larme's comment gives the correct answer for determining the starting point. 首先,Larme的评论给出了确定起点的正确答案。

However, if you are trying to determine device tilt (attitude), you want to use the gyroscope, not the accelerometer. 但是,如果您要确定设备倾斜(姿态),则需要使用陀螺仪,而不是加速度计。 The accelerometer tells how fast the device is moving in each direction. 加速度计可以显示设备在每个方向上的移动速度。 That's useful for determining if the user is quickly moving or shaking the device but doesn't help you at all determine whether the device is being tilted. 这对于确定用户是否正在快速移动或摇动设备非常有用,但却无法帮助您确定设备是否正在倾斜。 The gyroscope provides the device's current attitude and the rate of rotation. 陀螺仪提供设备的当前姿态和旋转速率。

Since it sounds like you are trying to implement a ball that will "roll" around a table as the user tilts the device, you probably want to get the attitude. 因为听起来你正试图在用户倾斜设备时实现一个会在桌子周围“滚动”的球,你可能想要获得态度。 To get the attitude, use startDeviceMotionUpdatesToQueue:withHandler:. 要获得态度,请使用startDeviceMotionUpdatesToQueue:withHandler:。 Then you can use the attitude property of the CMDeviceMotion object to find out how the device is oriented on each axis. 然后,您可以使用CMDeviceMotion对象的attitude属性来了解设备在每个轴上的定向方式。

As it was mentioned, we need to catch an initial device position (accelerometer value) and use it as zero reference. 如上所述,我们需要捕获初始设备位置(加速度计值)并将其用作零参考。 We catch reference value once when game starts and subtract this value from every next accelerometer update. 我们在游戏开始时捕获参考值,并从每个下一个加速度计更新中减去该值。

static const double kSensivity = 1000;

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

Initially, ball is motionless (velocities = 0). 最初,球是不动的(速度= 0)。 Zero reference is invalid. 零参考无效。 I set significant value in CMAcceleration to mark it as invalid: 我在CMAcceleration设置了重要值以将其标记为无效:

_referenceAcc.x = DBL_MAX;

Accelerometer updates. 加速度计更新。 As the app uses landscape right mode only we map y-acceleration to x-velocity, and x-acceleration to y-velocity. 由于应用程序仅使用横向右模式,我们将y加速度映射到x速度,将x加速度映射到y速度。 accelerometerUpdateInterval factor is required to make velocity values independent of update rate. 需要使用accelerometerUpdateInterval因子来使速度值与更新速率无关。 We use negative sensitivity value for x-acceleration, because direction of accelerometer X axis is opposite to landscape right orientation. 我们对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;
}

Your update: method does not respect currentTime value. 您的update:方法不尊重currentTime值。 Intervals between update calls can be different. 更新呼叫之间的间隔可以不同。 It would be better to update distance according to time interval. 最好根据时间间隔更新距离。

- (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: alternative way 编辑:替代方式

By the way, I found an alternative way solve it. 顺便说一下,我找到了另一种解决方法。 If you use SpriteKit , it is possible to configure gravity of physics world in response to accelerometer changes. 如果您使用SpriteKit ,则可以根据加速度计的变化配置物理世界的重力。 In that case there's no need to move a ball in update: method. 在这种情况下,不需要在update:方法中移动球。

We need to add physics body to a ball sprite and make it dynamic: 我们需要将物理体添加到球精灵中并使其动态化:

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

And set updated gravity in accelerometer handler: 并在加速度计处理程序中设置更新的重力:

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

Also we need to set physical bounds of the screen in order to limit ball movement. 我们还需要设置屏幕的物理边界以限制球的移动。

Why don't you just take the numbers, at the point of start up, as a baseline and save them as a class property. 为什么不在启动时将数字作为基线并将它们保存为类属性。 Any further readings you have you can simply add/subtract the current numbers with your baseline. 您拥有的任何其他读数可以简单地使用您的基线添加/减去当前数字。 Unless I am wrong, that should give you the desired results. 除非我错了,否则应该给你想要的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM