概要
- 過去に ClickGame というゲームを作った
- リポジトリ: https://github.com/ryota37/ClickGame
- 1回クリックすれば消える複数のボールが画面内を跳ね回っており、ボールを全てクリックして消したらクリアというシンプルなゲーム
- ゲーム制作の練習のための習作という位置づけ。ストア等に公開してマネタイズすることは考えていないし、そもそもそのようなクオリティには達していない
- このゲームにおいてとあるバグが発生していたが、作成当時は解決方法が分からずそのまま放置していた
- 壁に当たったボールが反射せず、壁に張り付いたままになるという事象が時折発生していた
- 『ゲームプログラミングC++』 を読んでいたら、その解決方法が載っていた
バグ詳細
- 修正前
- 左端と下端にて、ボールが反射せず張り付くような挙動になってしまっている
- 修正前(拡大)
- 張り付いているボールを拡大した動画が以下。
- 上とは別の試行にて撮影した動画だが、同様に壁に張り付く挙動をしている
- 修正後
- 修正後は、きちんとボールが壁で反射し、張り付く動作は見られなくなった
- 詳細は後述するが、修正後は複数回の試行において一度も「張り付き」が見られなかった
修正内容
- 修正前は、左端or右端の壁にボールが振れた場合は速度のxベクトルの符号を反転し、上端or下端の壁にボールが振れた場合は速度のyベクトルの符号を反転するという実装にしていた。
if (m_circle.left().x < Coordinate::LEFT || m_circle.right().x > Coordinate::RIGHT) { m_velocity.x *= -1; } if (m_circle.top().y < Coordinate::TOP || m_circle.bottom().y > Coordinate::BOTTOM) { m_velocity.y *= -1; }
- しかしこの実装では、壁にぶつかって速度ベクトルが反転した直後のフレームでまだボールが壁に触れている判定になっていたら、また速度ベクトルが反転して壁にぶつかる方向にボールが進んでしまう
- 上記の条件を満たすと、壁の近傍で速度ベクトルが反転し続けて「張り付き」が起きる
- 『ゲームプログラミングC++』には、この事象に対する解決法が記載されていた
- 本書では、導入の1章にてPONG(ポン)を実装するということをしている
- PONGは、パドルとボールだけで構成されたシンプルなゲーム。プレイヤーはパドルを操作して、ボールが自分のゴールに入らないようにする
- 自分が作成したClickGameではパドルがないが、ボールが画面内を反射するという部分は共通している
- 「1.6.4 ボールの位置を更新する」 の p.30 に、まさに今回の「張り付き」現象がその解決法と共に紹介されていた
- 解決法は、ボールが壁で反射する際にボールの向きも考慮するという至ってシンプルなもの。
- 例えば、上の壁に触れたボールはy方向の速度ベクトルが正(下向き)の時のみ反射する、といった具合
- ※ y方向の速度ベクトルが正の時に下向きなのは、Siv3Dが採用している座標系の都合
- 本書では更に「張り付き」防止のための発展的な実装があるが、今回はその実装をしなくてもバグを修正できたため採用しなかった
- 本書では、導入の1章にてPONG(ポン)を実装するということをしている
- 以上を踏まえて、「張り付き」を修正した後のコードが以下
if (m_circle.left().x < Coordinate::LEFT && m_velocity.x < 0) m_velocity.x *= -1; if (m_circle.right().x > Coordinate::RIGHT && m_velocity.x > 0) m_velocity.x *= -1; if (m_circle.top().y < Coordinate::TOP && m_velocity.y < 0) m_velocity.y *= -1; if (m_circle.bottom().y > Coordinate::BOTTOM && m_velocity.y > 0) m_velocity.y *= -1;
修正する際に意識したこと
今回修正したバグは、発生頻度は比較的高いものの常時発生する現象ではなかった。そのため、明確に基準を設けないとバグが修正できたかを判断できないと考えた。
よって、バグが修正できたかどうかを評価するため以下のような基準を設けたり、工夫をしたリした。
- バグを検出しやすくするために、1回のゲームで登場するボールの数を3個から10個に増やしてバグの発生頻度を意図的に上げた
- バグが発生するかどうかの試行はボール1個ごとに起きるため、ゲームを1回起動した時の抽選回数を増やした方が効率的だと思った
- ただあまり増やし過ぎると「張り付き」の観察が難しくなりそうなので、10個程度にとどめた
- 発生頻度が比較的高く容易に確認できるため、バグ発生の判定は目視で行うことにした
- 速度ベクトルの符号が反転する頻度を各ボールごとに測定し、明らかに頻度が高いボールは「張り付き」が起きているという風に機械的に判定する方法も無くはなかった
- しかし、上述の通り確認が容易であるためそこまでする必要はないと判断した
- バグを修正する前に、どのくらいの確率で本事象が発生するかを確認した
- 確認方法:
- ゲームを起動して、ボールが壁に張り付いているか確認→再起動を10回繰り返す
- 1回のゲーム起動で壁に張り付いているボールの個数は問わず、壁に張り付いているボールが1個以上存在するかどうかをチェックした
- 10回の内、壁に張り付いているボールが最低でも1個以上あった回数は7回もあった
- 確認方法:
- そして、バグ修正後は10回試行を繰り返して壁に張り付くボールが1個も出ないことを目標とした
- 1回の試行(=ゲーム起動)で10個のボールについて抽選が行われるため、全体で100回の抽選が行われたことになる。
- バグの発生頻度が1%を切っていたらOKとするという方針
- 結果: 上記のバグ修正を行ったところ、10回ゲームを起動しても張り付くボールは1個もなかった
以上のように判定基準を明確に定めることにより、「バグを修正できた」と自信をもって判断することができた。
(バグが絶対に発生しないことを保証するわけではないが、バグ発生率が1%未満程度だと概算でき、実用上問題ないという感触が得られた)