cocos2d-x
cocos2d-x入門

Cocos2d-xでCCSpriteの当たり判定(タッチ判定)

More than 5 years have passed since last update.

CCSpriteにはboundingboxというメソッドがあり、これを使って矩形判定をすることができます。

回転拡縮を行なっても自動的に矩形の大きさを調整してくれます。

ただし注意点もあります。

検索したところ、下記のブログに詳しく書かれていましたので参考になるかと思います。


【cocos2d】CCSpriteのサイズを取得するときに気をつけること

http://obc-fight.blogspot.jp/2013/02/cocos2dccsprite.html


上記の点にさえ気をつければ、boundingboxで返されるCCRect型には、containsPointというCCPointがその矩形に内包されているかをチェックするメソッドもあるので、単純な矩形の当たり判定はすぐにできるでしょう。

しかしまだ問題があります。

CCSpriteのboundingboxに限った話ではないのですが、矩形判定を行うときの一般的な問題として、回転させたときに見た目よりも大きな範囲でboundingboxの矩形がとれてしまうことです。

スクリーンショット 2014-03-08 6.11.48.png

このスプライトをユーザーがタッチする場合、傾きによって生まれた空白の部分もタッチ判定されてしまうため、操作に違和感を覚えることがあります。

そのため、もっと厳密にスプライトの当たり(タッチ)判定が取りたい場合は下記のような方法で判定することができます。


  1. スプライトの幅と高さの矩形を作る

  2. 上記で作成した矩形をスプライトのnodeToWorldTransformで取得できる変換行列で変換

  3. タッチ座標をワールド座標(OpenGL座標)に変換

  4. 外積を使ってタッチ座標が変換後の矩形領域にあるか判定する

外積を知らない方の為にすごい簡単に説明しますと、ベクトル同士が直交するベクトルのことです。3Dではライティングの計算のための法線計算などにも使われたりしますが、今回のように内外判定にも使えます。

また、今回は四角形での判定に使用していますが、凸ポリゴンの判定にも使えますし、凹ポリゴンでも三角形に分割して判定すれば、ほぼ同様に判定をすることができます。

サンプルコードは下記の様になります。

{

CCTouch* pTouch = (CCTouch*)pTouches->anyObject();
CCPoint locationPoint = pTouch->getLocationInView();
CCPoint glPos = CCDirector::sharedDirector()->convertToGL(locationPoint);

if( isHitCCSprite(sprite, plPos) ) {
CCLOG("HIT");
}
}

bool isHitCCSprite(CCSprite* sprite, CCPoint glPoint)
{
// 変換前のスプライトの矩形を求める
CCSize size = sprite->getContentSize();

CCPoint v[4] = {
ccp(0,0),
ccp(0, size.height),
ccp(size.width, size.height),
ccp(size.width, 0),
};

// 矩形をワールド座標に変換する
CCAffineTransform affine = sprite->nodeToWorldTransform();

for( int i=0; i<4; i++ ) {
v[i] = CCPointApplyAffineTransform(v[i], affine);
}

// 外積判定用のベクトルを求める
CCPoint vec[4] = {
v[1] - v[0],
v[2] - v[1],
v[3] - v[2],
v[0] - v[3],
};

CCPoint vecP[4] = {
v[0] - glPos,
v[1] - glPos,
v[2] - glPos,
v[3] - glPos,
};

// 外積計算 求めた外積の向きが揃っていれば点は内包されている
// (この例の場合は正方向に揃っていれば内包されている)
for( int i=0; i<4; i++ ) {
if( (vec[i].x * vecP[i].y - vec[i].y * vecP[i].x) < 0 ) {
return false;
}
}

return true;
}


Appendix1

CCSpriteにはgetQuad()というメソッドもあり、これでも変換前の頂点座標を取得できますので、上記のgetContentSizeの部分は下記のように置き換えることも可能です。

    ccV3F_C4B_T2F_Quad quad = sprite->getQuad();

CCPoint v[4] = {
ccp( quad.bl.vertices.x, quad.bl.vertices.y ),
ccp( quad.tl.vertices.x, quad.tl.vertices.y ),
ccp( quad.tr.vertices.x, quad.tr.vertices.y ),
ccp( quad.br.vertices.x, quad.br.vertices.y ),
};

ただし、冒頭で紹介したboundingboxの計算などには、getContentSize()で取得できる、m_obContentSizeの値が使われています。getContentSizeが使えない場合は代用出来る気がします、が試してはいません。


Appendix2

Cocos2d-xにはCC_SPRITE_DEBUG_DRAWというdefineがあり、これを有効にすることでスプライトの描画矩形のラインを描画してくれます。boundingboxで取得できる矩形をこのCC_SPRITE_DEBUG_DRAWで描画される矩形に変換して判定しようとすると結構手間がかかりますが、上記の方法を使うとCC_SPRITE_DEBUG_DRAWで描画される矩形と同じ領域でタッチ判定が可能です。


Appendix3

上記のサンプルでは外積計算のみを判定に使用していますが、スプライトが大量にある場合は処理を高速にするため、外積計算の前にもっと単純な円や矩形での判定をすると良いです。