jarinosuke blog

about software engineering, mostly about iOS

cocos2dでスワイプ処理を実装し、チューニングする

はじめに

f:id:jarinosuke0808:20100902232628p:image:right
ある程度、iPhone SDKを通してアプリケーション開発をされている方ならiPhone SDKにはスワイプ処理を取得する関数群などが用意されていないことは知っているかと思います。(iOS 3.2からUIGestureRecognizerというクラスが用意され、各オブジェクトにこれを付けることによってオブジェクト毎にジェスチャ認識とそれに対するアクションが設定できるようになりました。)
n本指に対してのタッチ開始、タッチ移動、タッチ終了、という比較的簡単なものしか用意されていないのです。なので、もし使いたい場合は自分で用意しないといけませんでした。
まぁ、スワイプ処理というのはiPhoneのUIでとても優れたものだと思うので、様々な方々が様々な方法で実装しているのでドキュメントはそこら中にあります。

しかし、いざcocos2d内でスワイプ処理を完結させて実装させている資料を探してみるとあまり見つかりませんでした。なので、自分で実装したものを紹介し、四方向のスワイプ判定のパラメータを簡単に目で見て調整できるような簡単なアプリケーションを作りましたので、ブログに公開したいと思います。

実装

ではアプリケーションを作っていきましょう。例によってアプリケーション名を「SwipeTuning」新規作成して下さい。
作成できたらHelloWorldScene.hを開いて、まず以下をクラス変数として定義します。

     /*Touch Start Point*/
     CGPoint startPt;
    
     /*Touch Continuing Time*/
     NSDate *startTime;
    
     /*Number Label*/
     CCLabel *directionLabel;
     CCLabel *startPointLabel;
     CCLabel *movePointLabel;
     CCLabel *speedLabel;
     CCLabel *distanceLabel;
    
     /*Explain Label*/
     CCLabel *directionExp;
     CCLabel *startPointExp;
     CCLabel *movePointExp;
     CCLabel *speedExp;
     CCLabel *distanceExp;

同様にして、getter/setterを使うために以下のプロパティ宣言も加えます。

@property(nonatomic, retain)NSDate *startTime;
@property(nonatomic, retain)CCLabel *directionLabel;
@property(nonatomic, retain)CCLabel *startPointLabel;
@property(nonatomic, retain)CCLabel *movePointLabel;
@property(nonatomic, retain)CCLabel *speedLabel;
@property(nonatomic, retain)CCLabel *distanceLabel;

これで表示するための各ラベルの準備は整いました。
簡単に説明すると、Number Labelはタッチの度に動的に変更されるラベルで、Explain Labelはただの説明です。
タッチの情報を格納するために、クラス変数に時間を保存するための変数と、タッチ開始場所を保存する変数も用意しています。

では次にimplementationを見ていきます。HelloWorldScene.mを開いて下さい。
.hファイルでプロパティ宣言をしたので、synthesizeを付け加えましょう。

@synthesize startTime;
@synthesize directionLabel;
@synthesize startPointLabel;
@synthesize movePointLabel;
@synthesize speedLabel;
@synthesize distanceLabel;

そして後でチューニングをするために必要となるマクロを定義しないといけません。
とりあえず以下の値をimplementation外に定義しておきましょう。

/* Tuning Parameters */
#define HORIZ_SWIPE_MIN 12
#define HORIZ_SWIPE_MAX 8
#define VERT_SWIPE_MAX 8
#define VERT_SWIPE_MIN 12
#define SWIPE_NON 0
#define SWIPE_LEFT 1
#define SWIPE_RIGHT 2
#define SWIPE_UP 3
#define SWIPE_DOWN 4

簡単に説明すると、スワイプ操作の際に行きたい方向に対してx, y軸でどれだけのブレや距離を許容または無視するか、という値になります。
ここをうまく調整することで、素早くシュッとやらないとスワイプできなかったり、綺麗に平行にスワイプしないと反応してくれなかったりと、自分好みのカスタマイズができるようになります。

次にinitメソッドを以下のものと置き換えて下さい。

-(id) init
{
     if( (self=[super init] )) {
         
          self.isTouchEnabled = YES;
         
          /*create and  itialize a Explain Label*/
          directionExp = [CCLabel labelWithString:@"SWIPE DIRECTION" fontName:@"Marker Felt" fontSize:20];
          [directionExp setColor:ccYELLOW];
          startPointExp = [CCLabel labelWithString:@"START POINT" fontName:@"Marker Felt" fontSize:14];
          [startPointExp setColor:ccYELLOW];
          movePointExp = [CCLabel labelWithString:@"MOVE POINT" fontName:@"Marker Felt" fontSize:14];
          [movePointExp setColor:ccYELLOW];
          speedExp = [CCLabel labelWithString:@"SPEED" fontName:@"Marker Felt" fontSize:14];
          [speedExp setColor:ccYELLOW];
          distanceExp = [CCLabel labelWithString:@"DISTANCE" fontName:@"Marker Felt" fontSize:14];
          [distanceExp setColor:ccYELLOW];
         
          /*create and initialize a Number Label*/
          directionLabel = [CCLabel labelWithString:@"WHERE" fontName:@"Marker Felt" fontSize:24];
          startPointLabel = [CCLabel labelWithString:@"(x , y)" fontName:@"Marker Felt" fontSize:18];
          movePointLabel = [CCLabel labelWithString:@"(x , y)" fontName:@"Marker Felt" fontSize:18];
          speedLabel = [CCLabel labelWithString:@"0.0" fontName:@"Marker Felt" fontSize:18];
          distanceLabel = [CCLabel labelWithString:@"0.0" fontName:@"Marker Felt" fontSize:18];
         
        /*position the Explain label on the center of the screen*/
          directionExp.position =  ccp( 135.0 , 160.0 + 30.0);
          startPointExp.position = ccp( 340.0 ,250.0 + 30.0);
          movePointExp.position = ccp( 340.0 , 190.0 + 30.0);
          speedExp.position =  ccp( 340.0 , 90.0 + 30.0);
          distanceExp.position =  ccp( 340.0 , 30.0 + 30.0);
         
         
         
          /*position the Number label on the center of the screen*/
          directionLabel.position =  ccp( 135.0 , 160.0);
          startPointLabel.position = ccp( 340.0 ,250.0 );
          movePointLabel.position = ccp( 340.0 , 190.0 );
          speedLabel.position =  ccp( 340.0 , 90.0);
          distanceLabel.position =  ccp( 340.0 , 30.0);
         
          /*add the Number label as a child to this Layer*/
          [self addChild:directionExp];
          [self addChild:startPointExp];
          [self addChild:movePointExp];
          [self addChild:speedExp];
          [self addChild:distanceExp];
         
          /*add the Number label as a child to this Layer*/
          [self addChild:directionLabel];
          [self addChild:startPointLabel];
          [self addChild:movePointLabel];
          [self addChild:speedLabel];
          [self addChild:distanceLabel];
     }
     return self;
}

まぁ、ここは表示するためのラベルを適材適所に貼り付けているだけなので特に説明は要らないと思います。

では最後に、肝心要のタッチ操作の取得と、それの処理について見ていきましょう。
以下を貼り付けて下さい。

-(void) registerWithTouchDispatcher
{
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0
                                                         swallowsTouches:YES];
}


/*Touch start*/
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{

     startPt = [touch locationInView: [touch view]];      
     startPt = [[CCDirector sharedDirector] convertToGL: startPt];
     startPt = [self convertToNodeSpace:startPt];
    
     startTime = [[NSDate date] retain];    
     swipe_direction = SWIPE_NON;
     [startPointLabel setString:[NSString stringWithFormat:@"(%f, %f)",startPt.x,startPt.y]];
     [directionLabel setString:[NSString stringWithFormat:@"TAP"]];
     return YES;
}

/*Touch move*/
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
    
     CGPoint curPt = [touch locationInView: [touch view]];      
     curPt = [[CCDirector sharedDirector] convertToGL: curPt];
     curPt = [self convertToNodeSpace:curPt];
     [movePointLabel setString:[NSString stringWithFormat:@"(%f, %f)",curPt.x, curPt.y]];
     double distanceFromStartToCur = sqrt( pow(fabsf(startPt.x - curPt.x), 2.0) + pow(fabsf(startPt.y - curPt.y), 2.0));
     [distanceLabel setString:[NSString stringWithFormat:@"%f", distanceFromStartToCur]];
    
     /*Check Out Horizontal Swipe*/
     if(fabsf(startPt.x - curPt.x) >= HORIZ_SWIPE_MIN && fabsf(startPt.y - curPt.y) <= VERT_SWIPE_MAX){
          if(startPt.x < curPt.x){
               swipe_direction = SWIPE_LEFT;
               [directionLabel setString:[NSString stringWithFormat:@"LEFT"]];
          }else{
               swipe_direction = SWIPE_RIGHT;
               [directionLabel setString:[NSString stringWithFormat:@"RIGHT"]];
          }
     }
    
     /*Check Out Vertical Swipe*/
     if(fabsf(startPt.x - curPt.x) <= HORIZ_SWIPE_MAX && fabsf(startPt.y - curPt.y) >= VERT_SWIPE_MIN){
          if(startPt.y < curPt.y){
               swipe_direction = SWIPE_UP;
               [directionLabel setString:[NSString stringWithFormat:@"UP"]];
          }else{
               swipe_direction = SWIPE_DOWN;
               [directionLabel setString:[NSString stringWithFormat:@"DOWN"]];
          }
     }
}

/*Touch end*/
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{

     CGPoint curPt = [touch locationInView: [touch view]];      
     curPt = [[CCDirector sharedDirector] convertToGL: curPt];
     curPt = [self convertToNodeSpace:curPt];
    
     if(swipe_direction != SWIPE_NON){
          /*Check out swipe speed*/
          NSTimeInterval dt = -[startTime timeIntervalSinceNow];
          int dx = fabsf(startPt.x - curPt.x);
          double speed = dx / dt;         
          [speedLabel setString:[NSString stringWithFormat:@"%f",speed]];

     }
}

少し長くなってしまいましたが、中で行っている処理は単純です。
ccTouchMoved内での水平または垂直方向のスワイプ判定ですが、先ほど設定したパラメータを用いて判定を行っています。
水平方向のスワイプ判定を例にとると、x軸での移動距離がHORIZ_SWIPE_MINより長くて、y軸での移動距離がVERT_SWIPE_MAXより短ければ水平方向のスワイプがあったと判断します。
そして、その中で距離に対して正負の判定を行い、左右どちらのスワイプなのかを処理しています。
簡単ですね!!!