今回はボールとドロップとの衝突検知を実装してきます。
前回はボールと画面端との衝突判定と跳ね返りのプログラムを行いました。
画面端であれば上下左右の4通りの検知を行えばよかったのですが、画面上に存在するドロップの数が変わり得る場合、ベタにプログラムを書くことは難しいですね。
リストを使おう!
リストの用意
そこで便利なのがリストです。リストを使えばプログラムを比較的簡易に記述することができます。以下のプログラムを記述してください。
HelloWorldScene.h
std::list<cocos2d::Sprite*> _dropList; // フィールド上のドロップを格納する
・std::list
std::listはC++のコンテナです。上記のプログラムはSprite*型の変数を格納できる_dropListを定義した、という意味です。
詳しくはC++プログラミングシリーズで解説予定です。
リストへの要素の格納
実際にプログラム中でリストに要素を格納していきましよう
HelloWorldScene.cpp HelloWorld::init
// ドロップ5種の格納 _dropList.push_back(dropRed); _dropList.push_back(dropYellow); _dropList.push_back(dropBlue); _dropList.push_back(dropGreen); _dropList.push_back(dropPurple);
・push_back
リストの最後尾に要素を追加するメソッドです。
それでは続いて衝突判定の繰り返し処理、跳ね返り処理を実装していきましょう。
衝突判定の繰り返し処理、跳ね返り処理
ドロップとボールとの衝突判定の繰り返し処理、跳ね返り処理を行っていきます。
下記のプログラムをHelloWorldScene.cppのupdateメソッド中に追加してください。
HelloWorldScene.cpp HelloWorld::update
void HelloWorld::update(float dt){ if(_isBallMoving){ float x = _ball->getPosition().x - _speed*cosf(CC_DEGREES_TO_RADIANS(_degree)); float y = _ball->getPosition().y + _speed*sinf(CC_DEGREES_TO_RADIANS(_degree)); _ball->setPosition(Vec2(x, y)); // ドロップに接触した場合の跳ね返りの処理 std::list<Sprite*>::iterator it; for(it = _dropList.begin(); it != _dropList.end(); it++){ if(hitDetectionOval(_ball, (*it))){ // 跳ね返り角度の更新 _degree = getDegree(_ball->getPosition(), (*it)->getPosition()); } } // 画面端に接触した場合の跳ね返りの処理 Size visibleSize = Director::getInstance()->getVisibleSize(); // 画面下端 if(_ball->getPosition().y - _ball->getContentSize().height / 2 <= 0){ _degree = _degree * -1; y = _ball->getContentSize().height / 2; } // 画面上端 if(_ball->getPosition().y + _ball->getContentSize().height / 2 >= visibleSize.height - _enemy->getContentSize().height){ _degree = _degree * -1; y = visibleSize.height - _ball->getContentSize().width / 2 - _enemy->getContentSize().height; } // 画面左端 if(_ball->getPosition().x - _ball->getContentSize().width / 2 <= 0){ _degree = 180 - _degree; x = _ball->getContentSize().width / 2; } // 画面右端 if(_ball->getPosition().x + _ball->getContentSize().width / 2 >= visibleSize.width){ _degree = 180 - _degree; x = visibleSize.width - _ball->getContentSize().width / 2; } _ball->setPosition(Vec2(x, y)); }
※ハイライト部分を追記してください。
std::list<Sprite*>::iterator it;
for(it = _dropList.begin(); it != _dropList.end(); it++){}
先ほどのリストに格納したドロップをを先頭から最後尾まで取得していくのがこの2行です。この書き方はお決まりなのでリストを使うときは覚えておくと良いと思います。
・hitDetectionOval
自作のメソッドです。2つのオブジェクトの円形衝突判定を行います。説明は後述します。
・getDegree
前回も使用した自作のメソッドです。2つのオブジェクトの座標から角度を取得します。ドロップがボールにぶつかった際は、このメソッドで新しい角度を取得し、ボールが移動を続けることになります。
衝突判定の繰り返し処理、跳ね返り処理のプログラム解説
ボールとドロップとの衝突判定の繰り返し処理
リストの先頭からドロップを順番に取り出し、ボールとの円形衝突判定を行っています。衝突している場合には衝突したドロップとボールとの角度を取得して跳ね返りとしています。
複数ドロップとの同時衝突の考慮
同時に2つ以上のドロップと衝突した場合は、リストの末尾に格納されているドロップが優先されます。イメージは下図になります。
この衝突処理は、ゲームとしてどうあるべきかによって実装は変わってきます。例えば以下のようにです。
・リスト末尾に近い要素との衝突を優先する(今回)
・リスト先頭に近い要素との衝突を優先する(衝突を検知した段階でbreak処理を入れるなどして、途中で処理を打ち切る)
・複数の角度を取得し、中間の角度を取得するようにする
どの処理が適切であるかは、ゲームの内容によって変わってくるかと思います。
次に、円形の物体の衝突判定について解説します。
円形の物体の衝突判定を行う
実際に衝突判定を行うメソッドであるhitDetectionOvalメソッドを追加します。
円形での衝突処理のイメージはこんな感じでしょうか。
【衝突の直前】
【衝突の直後】
それでは以下のプログラムを追記してください。
HelloWorldScene.h
bool hitDetectionOval(cocos2d::Sprite*, cocos2d::Sprite*); // 円形の接触判定を行う
HelloWorldScene.cpp HelloWorld::hitDetectionOval
/* 円形の接触判定を行う。接触していればtrue,接触していなければfalseを返却する オブジェクトから算出する円形の縮小率は0.9とする */ bool HelloWorld::hitDetectionOval(Sprite* o, Sprite* t){ if(((o->getPosition().x - t->getPosition().x) * (o->getPosition().x - t->getPosition().x) + (o->getPosition().y - t->getPosition().y) * (o->getPosition().y - t->getPosition().y)) <= ((o->getContentSize().width / 2 * 0.9 + t->getContentSize().width / 2 * 0.9 )) * ((o->getContentSize().width / 2 * 0.9 + t->getContentSize().width / 2 * 0.9))){ return true; }else{ return false; } }
円形衝突判定のプログラム解説
円形衝突判定のアルゴリズム
3平方の定理より、「2点間の(X方向の距離)^2 +(Y方向の距離)^2」が「(それぞれの円の半径の加算)^2」よりも小さければ接触していることになります。
円形の衝突判定はググると詳しく解説しているサイトが出てきますのでそちらをどうぞ。
当たり感の調整
係数として0.9を乗じることで、見せかけの当たり判定よりも小さくして当たり感を自然にしています。
シミュレータの起動
さてシミュレータを起動してみましょう
どうでしょうか、ボールとドロップが接触すると跳ね返るようになったと思います。
ただ、ボールはずっと動き続けていますよね。次はボールの減速処理を追加したいと思います。
次回
んん・・・おお・・・??
衝突判定、自分で実装するとなるとなかなか難しいですよね・・・
ボールに重力を与えて、ただバウンドするだけのゲームを作ってた頃は「なんだ物理演算って簡単じゃん」とか考えてました。
それで実際なにか作ろうとしたら、質量、摩擦、反発係数を考えなきゃいけないし、四角いものがぶつかったら回転しなきゃいけない。そんなこんなで挫折した経験があります。
思えば、当時はボール同士の衝突について解説しているサイトはほとんど無かったように思います。(そのせいで物理の教科書を広げるハメになりました)
物理とプログラムの両方を理解する必要があるので内容はやや難しいですが、きっとこの講座は役に立つと思います!
コメントありがとうございます。
これから活動の内容を増やしていく予定ですので、お手すきの際、たまにチェックしていただければ幸いです!