読者です 読者をやめる 読者になる 読者になる

夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【Core Motion】モーションセンサー使用時にだんだん座標がずれていくのを防止する

TravelShooting JP Objective-C

以前、Core Motionを使ってモーションセンサーでゲームを操作する方法についてブログを書きました。

【cocos2d】Core Motionを使ってcocos2dゲームをモーションセンサーで操作 - 夏までにiPhone アプリつくってみっか!

iPhone4Sだとそれほど気にならなかったのですが、iPhone5を使うと時間とともにだんだんキャラクターのX位置がずれて行き、それを修正するには操作する人間が少しずつ姿勢を変えなければならないという、おかしな現象が見つかってしまいました。

いろいろ調べた結果、理由は不明ですが、multiplyByInverseOfAttitude:を使うとずれが発生するという事が判明しました。

代わりに、基準となる位置は「度」に変換した状態で記憶しておき、計算の一番最後にその値を引いて基準位置からの角度の変化量を算出するように修正しました。
これにより、デバイスを最初の位置に戻せば必ずキャラクターが画面の中心に戻るようになりました。

ゲームスタート時に初期角度を「度」で記録

    CMAttitude* referenceAttitude = _motionManager.deviceMotion.attitude;
    _initialRoll = CC_RADIANS_TO_DEGREES(referenceAttitude.roll);
    _initialPitch = CC_RADIANS_TO_DEGREES(referenceAttitude.pitch);

ゲーム中はスタート時の角度を引いた値を移動量として使用

    CMAttitude* attitude = _motionManager.deviceMotion.attitude;
    double rollDegree = CC_RADIANS_TO_DEGREES(attitude.roll);
    double pitchDegree = CC_RADIANS_TO_DEGREES(attitude.pitch);
    CGFloat xMove = (pitchDegree - _initialPitch) * (_winCenter.x / MAX_TILT_ANGLE);
    CGFloat yMove = (rollDegree - _initialRoll) * (_winCenter.y / MAX_TILT_ANGLE);

修正版のタッチ操作クラスのソースです。

#import <CoreMotion/CoreMotion.h>
#import "AttitudeManager.h"

#define MAX_TILT_ANGLE 10.0

@implementation AttitudeManager {
    CMMotionManager* _motionManager;
    CGPoint _winCenter; //画面中央の座標
    UIDeviceOrientation _orientation; // デバイスの向き
    NSInteger _counter;
    double _initialRoll;
    double _initialPitch;
}

- (id)init
{
    if (self = [super init]) {
        _motionManager = [[CMMotionManager alloc] init];
        if (_motionManager.deviceMotionAvailable) {
            _motionManager.deviceMotionUpdateInterval = 1/60.0;
        } else {
            return nil; //モーションセンサーが使えないデバイスの場合はnilを返す
        }
        CGSize winSize = [CCDirector sharedDirector].winSize;
        _winCenter = ccp(winSize.width/2.0, winSize.height/2.0);
        _orientation = UIDeviceOrientationLandscapeLeft; // とりあえずどっちか入れておく
    }
    return self;
}

- (void)startMotionUpdate
{
    [_motionManager startDeviceMotionUpdates];
}

- (void)stopMotionUpdate
{
    [_motionManager stopDeviceMotionUpdates];
}


- (void)startUsing
{
    CMAttitude* referenceAttitude = _motionManager.deviceMotion.attitude;
    _initialRoll = CC_RADIANS_TO_DEGREES(referenceAttitude.roll);
    _initialPitch = CC_RADIANS_TO_DEGREES(referenceAttitude.pitch);
    
  [self scheduleUpdate];
}

- (void)stopUsing
{
    [self unscheduleUpdate];
}

- (void)update:(ccTime)delta
{
    CMAttitude* attitude = _motionManager.deviceMotion.attitude;
    double rollDegree = CC_RADIANS_TO_DEGREES(attitude.roll);
    double pitchDegree = CC_RADIANS_TO_DEGREES(attitude.pitch);
    CGFloat xMove = (pitchDegree - _initialPitch) * (_winCenter.x / MAX_TILT_ANGLE);
    CGFloat yMove = (rollDegree - _initialRoll) * (_winCenter.y / MAX_TILT_ANGLE);

    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    if ((orientation == UIDeviceOrientationLandscapeRight) ||
        (orientation == UIDeviceOrientationLandscapeLeft)) {
        _orientation = orientation;
    }

    if (_orientation == UIDeviceOrientationLandscapeRight) {
        xMove *= -1;
        yMove *= -1;
    }
    xMove = MIN(_winCenter.x, MAX(-_winCenter.x, xMove));
    yMove = MIN(_winCenter.y, MAX(-_winCenter.y, yMove));
    self.location = ccpAdd(ccp(xMove, yMove), _winCenter);
}


@end