cocos2dの基礎 タッチを検出する.
Objective-Cの基本的な概念はざっと解説したので、久しぶりにcocos2dについてやりましょう。
iPhoneアプリを作るなら必ず必要になる機能がタッチです。今回は、cocos2d内でタッチがどのように処理されているのか、どのように定義するのか解説したいと思います。
タッチを扱うメソッド
基本的にcocos2dでのタッチ検出は以下の4タイプのメソッドが随時呼ばれることで成り立っています。
- タッチ開始検出(ccTouchBeganなど)
- タッチ移動検出(ccTouchMovedなど)
- タッチ終了検出(ccTouchEndedなど)
- タッチキャンセル検出(ccTouchCancelledなど)
タッチ開始、終了検出メソッドは文字通りタッチが始まった時、もしくはタッチが離れた時に実行されるメソッドです。
タッチ移動検出のメソッドはタッチ位置が動いている時に、ループのように毎フレーム呼ばれるメソッドです。
キャンセル検出は、何か他のイベントの発生によって現在のタッチがキャンセルされた時に呼ばれるメソッドです。
これら4つのタイプを、レイヤーやスプライト、アクションと組み合わせることで、様々なタッチの機能を実現することが可能です。
また、それぞれのタイプには上に挙げているメソッド以外に、もう1つずつメソッドが用意されています。ccTouchesBegan、ccTouchesMoved、ccTouchesEnded、ccTouchesCanceledです。
これらはよく見るとタッチが複数形になっていますね。したがって、マルチタッチにも対応できるメソッド類になります。
これらのメソッドでもシングルタッチを扱うことはできますが、タッチオブジェクトの受け取り方が違い、余分な処理を噛まさないといけないので、シングルタッチしか扱わないことがわかっている場合は、最初に挙げたccTouchBeganなどのシングルタッチ用を使えばいいです。
とりあえず今回はシングルタッチ用のものを用いながら解説し、今後タッチの使い方がわかった所でマルチタッチ用のメソッドの扱いを見て行きたいと思います。
タッチ開始を検出するプログラム
まずは最も単純なものから入って行きましょう。タッチを検出するプログラムを作ってみます。
HelloWorldLayerでタッチを検出してみましょう。以下の3つの処理を定義しなければなりません。
- ”CCTouchDispatcher”にHelloWorldLayerを登録
- ”ccTouchBegan withEvent”メソッドの定義
- ”CCTouchDispatcher”から削除
聞きなれない言葉ですね。CCTouchDispatcherとはなんでしょうか。
これはいわば、タッチに関する見張り番みたいなもんです。タッチがないか見張っていて、タッチを見つけた時は報告してくれるオブジェクトです。 このオブジェクトにHelloWorldLayerを登録しておくことで、HelloWorldLayerにタッチを報告してくれるようになります。
これはまた、タッチ報告がいらなくなったら、もういらないことをきちんと伝えないと、HelloWorldLayerがいなくなってからもずっと報告し続けるので、メモリの無駄遣いにつながります。
CCTouchDespatherへの登録はいつ行えばいいでしょうか。HelloWorldLayerがオブジェクトとして成り立ってから登録する必要があります。initイニシャライザではまだ、イニシャライズできるかどうか確定していませんので、ここでは行わないほうがいいです。
一般的にはonEnterというメソッドで行います。なぜならこのメソッドはそのクラスがインスタンス化され、オブジェクトとして成立した時点で、cocos2dフレームワーク側から呼び出されるメソッドだからです。
CCTouchDespatherからの削除は逆に、オブジェクトが解放される直前に行うのが通例です。このオブジェクトが解放される直前に呼び出されるメソッドとしてonExitというものがあります。したがって、そこに定義しておきましょう。
これらをふまえた上でinitとonEnter、onExitを以下のように定義してみましょう。
-(id) init { if( (self=[super init]) ) { CGSize wSize = [[CCDirector sharedDirector] winSize]; CCSprite* circle1 = [CCSprite spriteWithFile:@"red_circle.png"]; circle1.position = ccp(wSize.width / 2, wSize.height / 2); [self addChild:circle1 z:0 tag:1]; } return self; } -(void)onEnter { [super onEnter]; [[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES]; } -(void)onExit { [super onExit]; [[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self]; }
まず、イニシャライザ内では特に重要なことをしていません。一応HelloWorldLayerが描画されていることを確認するために、スプライトを一個作っています。
そのしたに書いてあるのがonEnterとonExitメソッドです。何が書かれているのか解説していきます。
まず、どちらもスーパークラスのオーバーライドになるので、きちんとスーパークラスのonEnterとonExitを呼び出してやらなければなりません。詳しくは「クラスの継承を理解する.」を参照。
onEnterでは「 [[ CCDirector sharedDirector ] touchDispather ] 」でタッチの見張り番であるCCTouchDispatherを指定しています。
その次からが登録用のメソッドになります。「 addTargetDelegate:self 」はHelloWorldLayer(self)を登録の対象にすることを示します。
「 priority:0 」はタッチが報告される順番の優先度に関する記述で、値が低いものほど優先度は高くなります。
最後の「 swallowsTouches:YES 」は、タッチイベントを飲み込むかどうか。つまり、そのタッチイベントを自分専用にするか、他も検出できるようにするか指定できます。
YESにした場合、自分がタッチ報告を受けると、他はそのタッチ報告に対しての処理は行いません。NOにした場合、自分がタッチ報告を受けたとしても、その下のレイヤーなどにも報告が行き渡るようになります。
onExitでやっていることはCCTouchDispatherからHelloWorldLayerを削除する処理です。
では最後タッチ開始を検出するメソッドを記述してやりましょう。
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { NSLog(@"Touched"); return YES; }
これで実行すると、タッチが開始されるたびに、”Touched”というのがコンソールに出力されます。あとは、タッチが行われた時に実行したい処理をNSLogの代わりに書いてやればいいわけです。
補足ですが、他のタッチ系メソッドと違い、ccTouchBeganメソッドだけ、返り値BOOLが設定されています。したがって、returnで返り値を指定してやる必要があります。YESの場合はタッチの処理を行うことを意味し、NOの場合はタッチ処理が行われません。
NOに変えてみると、いくらタッチしても先ほどの出力がされないはずです。
これを使い分けることでも、タッチを許可するタイミングを操作することができます。
最後にソースコードを載せておきます。
HelloWorldLayer.h
#import <GameKit/GameKit.h> #import "cocos2d.h" @interface HelloWorldLayer : CCLayer <GKAchievementViewControllerDelegate, GKLeaderboardViewControllerDelegate> { } +(CCScene *) scene; @end
HelloWorldLayer.m
#import "HelloWorldLayer.h" #import "AppDelegate.h" @implementation HelloWorldLayer +(CCScene *) scene { CCScene *scene = [CCScene node]; HelloWorldLayer *layer = [HelloWorldLayer node]; [scene addChild: layer]; return scene; } -(id) init { if( (self=[super init]) ) { CGSize wSize = [[CCDirector sharedDirector] winSize]; CCSprite* circle1 = [CCSprite spriteWithFile:@"red_circle.png"]; circle1.position = ccp(wSize.width / 2, wSize.height / 2); [self addChild:circle1 z:0 tag:1]; } return self; } - (void) dealloc { [super dealloc]; } -(void)onEnter { [super onEnter]; [[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES]; } -(void)onExit { [super onExit]; [[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self]; } -(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { NSLog(@"Touched"); return YES; } #pragma mark GameKit delegate -(void) achievementViewControllerDidFinish:(GKAchievementViewController *)viewController { AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; [[app navController] dismissModalViewControllerAnimated:YES]; } -(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController { AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; [[app navController] dismissModalViewControllerAnimated:YES]; } @end
次回はタッチ移動検出や終了検出について見て行きたいと思います。