cocos2dの基礎 タッチに反応するオブジェクトを作る.
前回、タッチが検出されるプロセスを大まかに説明し、HelloWorldLayer上でccTouchBeganメソッドを用いたタッチの検出を解説しました(「タッチを検出する」)。
今回はタッチ移動と、タッチ終了の検出について見て行き、タッチに反応するオブジェクトを例として作ってみましょう。
タッチ移動と終了の検出
タッチ中に移動が起こった際に呼び出されるメソッドはccTouchMoved、もしくはccTouchesMovedです。そして、タッチが終わった時に呼び出されるメソッドがccTouchEnded、もしくはccTouchesEndedです。
今回もシングルたちをメインに解説するので、ccTouchMovedとccTouchEndedを用います。
前回のサンプルプログラムに以下のようにccTouchMovedとccTouchEndedメソッドを追加してみてください。
/* 上部省略(init、onEnter、onExit など) */ -(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { NSLog(@"Touch Started"); return YES; } -(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { NSLog(@"on Move"); } -(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event { NSLog(@"Touch Ended"); } /* 下部省略(deallocなど) */
それぞれタッチ検出メソッド内で出力処理を行なっています。これで、各メソッドが呼び出された時に出力が出ます。
実行してみてください。
タッチが始まった瞬間に「Touch Started」が出力され、タッチを移動させてる間は「on Move」が何度も出力されると思います。そして、タッチが終了した瞬間、「Touch Ended」が出力されます。
これで、各タッチメソッドが呼ばれるタイミングがわかったと思います。
ccTouchMovedメソッドはタッチ中ずっと呼び出されているわけではなく、たとえタッチ中でも、タッチ位置が動かなければ呼び出されないことに注意してください。
オブジェクトをタッチに反応させる
今のままでは画面のどこをタッチしてもログが出力されます。画面上のオブジェクトのみに反応させるためには、随時タッチ位置がオブジェクト上かどうかを判定しなくてはなりません。
一見面倒くさい処理のように思いますが、スワイプやピンチなど様々なタッチ動作に対応するためには、このような挙動であった方がいいのです。
早速プログラムを書いていきます。
まずは画面上に以下の画像を使ってスプライトを配置します(前回のサンプルプログラムですでに配置済み)。
-(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:tagCircle]; } return self; }
これで画面の真ん中にスプライトが表示されます。circle1をレイヤーに追加している時につけているタグは、以下のようにenumでヘッダーに定義しています。これで他のメソッド内からもこのスプライトを参照できるようになります。
typedef enum nodeTags { tagCircle } tag;
次にccTouchBeganを変更していきます。
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { /* touchポジションを取得し、cocos2d座標系へ変換する */ CGPoint touchPos = [touch locationInView:[touch view]]; touchPos = [[CCDirector sharedDirector] convertToGL:touchPos]; /* タグを使ってスプライトを取得 */ CCSprite *circle = (CCSprite *)[self getChildByTag:tagCircle]; /* スプライトのバウンディングボックス内にタッチポジションがあればYESが返る */ if(CGRectContainsPoint(circle.boundingBox, touchPos)) { NSLog(@"Touch Started"); } return YES; }
まず、引数で取得しているタッチオブジェクト(UITouch型touch)から、位置情報を取得してやりましょう。
UITouchクラスのlocationInViewメソッドに現在のview上のタッチデータを渡すことでCGPoint型のポジションが得られます。
しかし、Cocoaとcocos2dではy軸の向きが異なります。このままではCocoa座標系のポジションなので、cocos2d座標系に変換してやる必要があります。
sharedDirector、つまりこのcocos2dアプリの監督者を呼び出して、GL座標系にタッチポジションを変換してもらい、touchPosを上書きしています。
これらの処理をまとめて以下のように書くこともできます。
CGPoint touchPos = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
8行目ではタグを使って画像のスプライトを参照しています。CCNode型で返ってくるので、CCSprite型にキャストで変換しています。これによってこのメソッドでもスプライトが使えるようになります。
11行目が「オブジェクト上にタッチがあるかどうか」を判定する記述になります。
”boundingBox”プロパティはCCNodeから継承されているプロパティで、CGRect型を返します。つまりスプライトが含まれる四角形のデータを返してくれます。
タッチポジションがその四角形に含まれているかを”CGRectContaintsPoint”メソッドで判定し、結果をif文に入れています。
ここで、CGRect型について説明すると以下の2つのプロパティから構成されています。
- origin
- size
またそれぞれoriginはCGPoint型ですのでx、y値を持ち、sizeはCGSize型ですのでwidthとheightの値を持ちます。
originはanchor pointがどこであれ左下です。そしてスプライトの場合は画像のサイズで自動的にcontent size値を決定し、そこからsizeが生成されています。
ま、とにかくこれでCGRectとタッチポジションの交差判定ができました。スプライトが円の場合は、角をタッチした場合、画像自体はなくても、反応してしまいますが、大抵の場合、これぐらい大雑把でも大丈夫です。
きちんと円で判定したい場合は、ベクトルの内積計算するメソッドを自分で定義しなくてはなりません。
基本的にはスプライトがタッチされたかどうかはこのように判定します。