当たり判定を導入していく
前回までで、Rect型とRect型(軸に平行なバウンディングボックス:英語でなんて言うんだっけ?)を2つ比べて接触判定ができるようになった。
だけれども、今気づいてしまったのだけど、なんか途中で座標を実数にした気がするんだよねぇ(RectF型で作った気がする。。。)
こんな時に対応できるように、C++では、いろいろ便利な機能が提供されている。
主にこのような時に使えるのは、
- オーバーロード
- コンストラクタの時に習った、同じ名前の関数を引数の型、数を変えて呼び出すテクニック
- 戻り値のみ違うものはオーバーロードできない
- 継承をまたいだオーバーロードはできない(これやるとどうなるんだっけ?)
- テンプレート関数
- 関数の処理を一般化することで、型をパラメータとして取得して、さまざま型で同じ動きをする関数を作るテクニック
- コンパイル時には型が確定していなければならない
- 実行時エラーが発見しづらく、デバッグがちょっとややこしくなる
今回は、Rect型とRectF型両方で同じ名前、同じ動きをする関数を作るためにオーバーロードを使ってみる(テンプレートのほうがしっくりくるけど、授業の進度の都合で。。。)
接触判定関数IsRectIntersectsOtherRectをオーバーロードする
まずオーバーロード関数の、プロトタイプ宣言を書いていく。
今回は、中身はほぼ一緒で、引数が違い、名前を同じにして呼びたいので、こんな感じになる。
(せっかく作ったのでRect版を残しておきたいけど、別にRectF版だけあれば事足りる、ともいう)
- "gameSequence.h"
bool IsRectIntersectsOtherRect(Rect _rectA, Rect _rectB); bool IsRectIntersectsOtherRect(RectF _rectA, RectF _rectB);
これで、後は実装を書いていけば、この関数をオーバーロードができる。
この関係は、どっちが親でどっちが子でとかいう関係ではなく、同じ名前の関数が並列に並んでいて、引数が違うと別の機能が呼び出される。
というものである。(ので、混乱しないで使ってね)
次は実装
- "gameSequendce.cpp"
/// @brief _rectAとほかのRectが接触しているかどうか判定する関数 /// @param _rectA 1個目の矩形 /// @param _rectB 2個目の矩形 /// @return 接触状態 true:接触 false:非接触 bool IsRectIntersectsOtherRect(Rect _rectA, Rect _rectB) { int wABは_rectA.w / 2と_rectB.w / 2の和(x軸方向の判定用) int hABは_rectA.h / 2と_rectB.h / 2の和(y軸方向の判定用) int distABx = _rectAと_rectBのx軸方向の中心座標の差の絶対値 int distABy = _rectAと_rectBのy軸方向の中心座標の差の絶対値 if (上の説明の2つの条件を同時に満たすとき(複合条件で書くよ)) return true; else return false; } bool IsRectIntersectsOtherRect(RectF _rectA, RectF _rectB) { //RectFになったら、型とか処理をどう変更したらうまくいくかな? //考えてみよう!!(型変えるだけともいう) }
これで、特に何の配慮もなくRectF型の当たり判定をすることができるようになった。
プレイヤー弾と敵の当たり判定を書いてみる
プレイヤーと敵の情報はGameDatにまとめてドーンって入っているので、それからプレイヤー弾とエネミーの情報を取り出して、
IsRectIntersectsOtherRectに渡して当たり判定をする。
今は敵1匹、弾丸不定(最大何発だっけ?)の当たり判定となるので、やるべきことは、すべての生きている弾丸VS敵1匹の戦いである。
これを、void PlayerBulletVSEnemy関数として書いてみようz
多分、このヒントだけ出かける人はもうかけると思うんだよねぇ。。。
一応、解説
まずプロトタイプ宣言
- "gameSequence.h"
//どっか、まとまりのよさそうなところに追加 void PlayerBulletVSEnemy(gameData& _dat);
次、実装
- "gameSequence.cpp"
void PlayerBulletVSEnemy(gameData& _dat) { for (_datのなかのPlayerGUNのなかの、すべての弾丸) { if (弾丸と敵が当たっていたらをIsRectIntersectsOtherRectで) { Print << U"どっかーん"; } } }
あとは、
UpdatePlayの中で、playerの更新後あたりで呼び出す。(プレイヤーが動いてから判定)
実行したときに、敵に弾丸が当たる瞬間に“どっかーん”と表示が出れば当たり判定はできている。
できた人は、
- 当たったら弾丸を消す
- 当たったら、敵を消す
- 当たったら、バウンディングボックスを赤く光らせる
- 当たったら、爆発のエフェクトをつける
をどうしたらできるか考えてみよう。
ちなみに、どっかーんが、連続でズダダダダって出てきてしまうのは、今弾丸が貫通弾状態になっていて、敵に当たった後も、敵の中をえぐって進んでいる間ずっと当たり判定を持っているからである。
これも、弾丸を消したり、敵を消したりで解消できる。
(敵のグラフィックだけ消したりすると、存在自体は残っていて、透明な敵と透明な弾の当たり判定とかになっちゃうから、ちゃんと考えないとややこしいことになるよ)
敵に当たった弾丸を消す!
現在は、図のようにプレイヤーの弾丸が敵を通過している間毎フレームずっと弾と敵の当たり判定がtrueになっている。
敵を一発の弾丸で倒せる仕様のゲームなら、敵に弾が当たった時点で弾と敵に消滅判定ややられ判定を出したいものである。
現在の弾の仕様は、
- isAlive:true
- 発射されている
- isAlive:false
- 発射されたいない
の2種類である。
ちょっと乱暴な気もするが、とりあえずこれを使って、当たり判定が出た瞬間に弾のほうは発射されていない判定に戻してしまおう。
- "gameSequence.cpp
void PlayerBulletVSEnemy(gameData& _dat) { for (auto& elm : _dat.playerGUN.PlayerBullet) //習いたての拡張for文、慣れてない人は0~PLAYER_MAX_BULLET_NUM未満までのfor文でいいよ { if (もし発射された弾なら) { if (敵と当たり判定) { Print << U"どっかーん"; 当ったら、isAliveをfalseにして、 弾の位置を画面外{-10,-10}とか?にしてしまう。 (UpdatePlayerBulletでisAliveがtrueの時だけ処理してるので特に問題ないけど一応見えない場所によけておく) } } } }
ついでに、敵も消すにはどうしたらいい考えてみてね。
PlayerやEnemyの管理を各Update内でisAliveの変更などによりちゃんとしているかどうかチェックだね!