弾性衝突円盤アプレットの設計メモ

先日公開した弾性衝突円盤アプレットはまた改造して別バージョンを作ろうと思ってるので、自分自身がそのソースコードを読めなくならないよう、設計を書き残す。

●Swing関係
・JAppletを継承するクラスは、盤面クラスのインスタンスを作ってタイマー処理するだけにしている。
 表示画面が複数あっても、盤面のインスタンスを増やすだけで済む。

・タイマーはSwing timerを使っている。汎用のjava.util.Timerを使うのに比べて、タイマーイベントがSwingのイベント発行スレッドにて他のイベントと一緒にシリアライズされるので、タイマーイベントによって処理が割り込まれるということがなく、排他制御がシンプルになる。
 というようなことが、JavaSEのAPI仕様書のjavax.swing.Timerの項に書いてある。
 なお、所々でSwingUtilities.invokeLaterを使ってるのは、同様の効果を期待してのことだが、よく考えると、Swing timerを使ってるので意味が無かった。

・JComponentの盤面の大きさは、JComponent#getPreferredSize()が返すようにしている。レイアウトマネージャはFlowLayoutを使っており、FlowLayoutは余計な調節をしないので、これだけで済むはず。

●描画関係
・円盤の画像は、盤面インスタンスの生成時に全パターン用意するようにしている。(50-58行目)

・盤面の描画は、スプライト的なものを使わず、毎回矩形で塗り潰して全描画、としてるので、せっかくなので、描画の度に色相を少しずつ変えながら塗り潰すことにした。(72-73行目)
 bg_countはそのためだけの変数。
 74行目の青色設定は、状態表示にしか効いていない。

・円盤の座標は中心の座標を保持しているため、描画開始位置は半径を引いた座標になる。

●衝突計算関係
・円盤の移動後に衝突判定と衝突後の速度の計算をしている(127-182行目)が、壁との衝突と他の円盤との衝突が同時に起こった場合、円盤同士の衝突を先に計算すると、壁との衝突の影響が考慮されないので、壁がクッションのような扱いになってしまう。かと言って、壁と他の円盤との衝突を同時に計算しようとすると、複雑な計算になる。
 そもそも、円盤と壁が円盤を挟むように同時に衝突する場合、真ん中の円盤は壁の役割を兼ねるので、円盤同士の衝突の計算は、壁に向かう円盤同士の衝突ではなく、壁に向かう円盤と壁から跳ね返ってきた円盤との衝突とするべきである。
 そのため、壁との衝突を先に処理するようにしている。

・壁との衝突判定は、円盤が壁と深く重なってる場合は衝突状態が続くので、速度ベクトルが壁の方を向いているかどうかを考慮するようにした。(131,138行目)
 円盤同士が深く重なってる場合も衝突状態が続くので、前回の中心間の距離を保存して、距離が縮まっているかどうかを衝突判定の材料にしている。(hit_depth[][]が前回の距離の2乗)

・複数の円盤の衝突が同時に発生した場合の計算は非常に複雑になる。
 1つの円盤が他の2つの円盤に同時に衝突するのはまだいいが、さらに衝突相手がまた別の円盤と同時に衝突する場合は、衝突の影響が全ての繋がる円盤に及ぶので、計算が大変である。
 そのため、1つの円盤は同時に他の1つの円盤としか衝突せず、さらに他の円盤との衝突はその瞬間は無視し、めり込むことを許す、とした。(isHit[]が衝突フラグ)

・衝突による速度変化を一旦別の配列に入れているのは、1つの円盤が同時に複数の円盤と衝突する計算をする場合のことを考えてのものであり、今回の方式では意味が無い。

●円盤の増減関係
・動的に円盤の数を減らせるよう、規定の数の円盤以外は、画面外に出るまで衝突判定の対象外とした。(185-193行目)

・動的に円盤の数を増やす時、既にある円盤と同じ場所から、速度ベクトルを30°ずらしてスタートさせるようにしている。それにより、円盤が分裂する感じになる。
 一気に2つ以上の円盤が増える場合は、分裂する円盤をそれぞれ違う円盤にしている。0番の円盤から分裂させると、いつも同じ円盤から分裂する感じになるので、最後尾の円盤から分裂させるようにしている。(201-207行目)