cocos2d-x v3 ピクセルパーフェクトなタッチ判定をする(スプライトの透過部分を無視する)
スプライトのタッチ判定を、見た目どおりに行いたい。
透明部分とそうでない部分を判別したい、ということであります。
ググると、大量のStackOverFlow記事が出てくるので、サクッと行きそうに思えたのですが……。
激ハマり!(泣) しましたので備忘録を残しておきます。
参考にしたのはこちら。discuss.cocos2d-x.org
途中の、ピクセルを読むクラスを作るアプローチでいきたいと思います。
早速その、ピクセルを読むクラスのコードです。
コピペでいけるはず。まずはヘッダーから。
//ReadNode.h class ReadNode : public cocos2d::Node { public: static ReadNode* getInstance(); virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags); GLubyte* getPixelColor() {return _pixelColorRead; }; void setReadPoint(cocos2d::Vec2 point); private: static ReadNode* _ReadNode; CREATE_FUNC(ReadNode); protected: void onDraw(); cocos2d::CustomCommand _readPixelsCommand; cocos2d::Vec2 _readPoint; GLubyte _pixelColorRead[4]; };
privateとprotectedの分け方とかテキトーだったりします。
private部は自前のシングルトンクラスからのコピペ+書き換えだったりするので……。
しかしながら一応動くは動くはず。
続いてソース。
//ReadNode.cpp //シングルトン ReadNode* ReadNode::_ReadNode = NULL; ReadNode* ReadNode::getInstance() { if(_ReadNode == NULL) { _ReadNode = new ReadNode; } return _ReadNode; } //改悪セッターメソッド void ReadNode::setReadPoint(cocos2d::Vec2 point) { _readPoint = point; } // draw()はオーバーライドできないので、draw(Renderer *renderer, const Mat4& transform, uint32_t flags)をいじくる void ReadNode::draw(Renderer *renderer, const Mat4& transform, uint32_t flags) { _readPixelsCommand.init(_globalZOrder); _readPixelsCommand.func = CC_CALLBACK_0(ReadNode::onDraw, this); renderer->addCommand(&_readPixelsCommand); } void ReadNode::onDraw() { // ここでピクセルのRGBAを取得 glReadPixels(_readPoint.x, // タッチしたポイントのx,y _readPoint.y, 1, // width 1, // height GL_RGBA, //定数 GL_UNSIGNED_BYTE,//定数 &_pixelColorRead); //なんで&かよくわかっていない }
何をやっているかというと。
RenderTextureで対象のスプライトを描画すると、そのタイミングでglReadPixelsが呼べるので、
glReadPixelsで取得できるRGBAの値を読んで、透明か否かを判定しよう、という話です。
使い方はこんな風。
//HelloWorld.cpp bool HelloWorld::init() { if (!Layer::init()) { return false; } Size winSize = Director::getInstance()->getWinSize(); auto sp = Sprite::create("pp_test.png"); sp->setPosition(Vec2(winSize.width/2, winSize.height/2)); addChild(sp); auto sp_listener = EventListenerTouchOneByOne::create(); sp_listener->onTouchBegan = [this, sp](Touch *touch, Event *event){ auto location = touch->getLocation(); // convertToNodeSpaceしない this->IsTouchingSprite(sp, location); return true; }; Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(sp_listener, sp); return true; }
こっちはただ対象スプライトとタッチポイントを特定して、渡してるだけであります。
spとlocationを透過判定メソッドに渡してやります。
bool HelloWorld::IsTouchingSprite(cocos2d::Sprite *sp, cocos2d::Point touch_point) { bool isTouchingExactly = false; Size winSize = Director::getInstance()->getWinSize(); RenderTexture *r_texture = RenderTexture::create(winSize.width, winSize.height, Texture2D::PixelFormat::RGBA8888); //フルサイズで r_texture->setPosition(Vec2(winSize.width/2, winSize.height/2)); this->addChild(r_texture); auto readNode = ReadNode::getInstance(); readNode->setReadPoint(touch_point); //改悪セッターメソッド……。 r_texture->beginWithClear(0, 0, 0, 0); sp->visit(); readNode->visit(); //作ったクラスにもvisitする r_texture->end(); Director::getInstance()->getRenderer()->render(); // Renderer::render()をコール GLubyte *color = readNode->getPixelColor(); // color[0] = r, color[1] = g, color[2] = b, color[3] = a, if (color[3] == 0) { isTouchingExactly = false; log("透明部分"); } else { isTouchingExactly = true; log("絵がある部分"); } return isTouchingExactly; }
StackOverFlowで中の人が何やら説明しているのが、下の部分。
r_texture->beginWithClear(0, 0, 0, 0); sp->visit(); readNode->visit(); //作ったクラスにもvisitする r_texture->end(); Director::getInstance()->getRenderer()->render(); // Renderer::render()をコール
nodeを作ってvisitせよとか、Renderer::render()せよとか、うんぬんかんぬん。
なんか非同期処理になったので、glReadPixels()呼ぶのに、ひと工夫必要になった感じですかね。
とりあえずこの指示通りに作った人「の真似」をしております。
ちなみにセッターメソッド作ってるのは、上記の指示通りに作った人のコードが理解できなかったためです(汗
オリジナルはもっとスマートでしたが、泥臭く改悪しております。
auto readNode = ReadNode::getInstance(); readNode->setReadPoint(touch_point); //改悪セッターメソッド……。
何気に最後に時間を浪費してしまったのがこれ。
日本語の記事、例えば↓なんかだと、wonderpla.net
convertToNodeSpaceせよ
と仰ってるのですが、しなくてOK、
というかするとオカシくなってしまうので、touch->getLocation()した値を直渡しすべし。
最終的にcolorの第4要素、color[3]の値で透明か否かを判定しております。
color[0] = r // 赤
color[1] = g // 緑
color[2] = b // 青
color[3] = a // アルファ
ですな。
複数判定とかはもうチョイいじくり回さないとですが、
とりあえず動いたので、一安心です。