1セクタゲーム作成に挑戦-8
デバッグ
すり抜けバグの原因究明です。
当たり判定はキャラクタの座標を総当りでチェックして、重なっている部分を見つけるのが通常の手法だと思いますが、非常にコード制限がキツイのでその方法は採用できませんでした。
MSXのVDPにはハードウェアの機能としてスプライトが重なると教えてくれる機能があり、今回はそれを利用しています。
これはどのスプライトが重なったのかは教えてくれないので、スプライトのどれかが重なったとしか分からない大雑把な機能になります。
多くの場合は、どのスプライトが重なったのかが重要なので余り使われないと思われる機能ですが、このゲームの場合は障害物は重なることがなく、スプライトが重なる=プレイキャラクタと障害物の重なり=ゲームオーバーという状況なので利用することができました。
コードで言えば
この部分で重なったかどうかを取得しています。
ちょっと手順を省いてダイレクトに99hを指定していますが、MSXでは(殆どの場合)I/Oポートの99hを読み込むことでVDPのステータスレジスタを読むことができます。
このステータスレジスタに衝突フラグがあり、MSXのルールでこのステータスレジスタが常に選択されている状態なので、このポートから読み込むだけでスプライトの重なりをチェックできるはずでした。
しかし、すり抜けてしまうわけです。ややこしいのは、すり抜けないこともあるということ。
実際スプライトが重なった時にエミュレータを止めてフラグを見ましたが、フラグが立っていません。
もしやと思い0038hにブレークポイントを仕掛けてみたところ予想通りの結果でした。
原因
このゲームはMSXのシステムが動作している上で実行されているので、ゲームに関係なく割り込み処理が行われています。
垂直帰線割り込みの処理で、キー入力などの処理が行われていますがVDPのステータスレジスタの読み込みも行われています。
このステータスレジスタには、垂直帰線割り込みフラグというビットがあり、これは読みだすとリセットされるとDatapackやテクニカルハンドブックには書かれています。
一方、衝突フラグもステータスレジスタにあり、スプライトが衝突するとセットされるとのみ書かれているのですが、実はこのビットも読み出されるとリセットされていたのでした。
ネットで見つかるTMS9918のマニュアルには読み出しでリセットされると書かれているので、表記が抜けたのだろうと推察します。
よくあることですが、こういうちょっとしたことがバグの原因になるので困りものです。
つまり、今回の現象は
- ゲーム上で、スプライトが衝突する
- 垂直帰線割り込みが掛かる
- ステータスレジスタを読み、フラグが0になる
- ゲーム内でステータスレジスタを読むが、フラグが0なので衝突していないと判定される
- タイミングにより、割り込みより先にステータスレジスタを読み込むと、衝突が判定される
と、こういうことのようです。
対策
対策はこうなります。
MSXはレジスタの値をワークエリアに保存して参照できるようにしてくれるので、そっちを読みます。ステータスレジスタの保存はアドレス0f3e7hでした。
ただそれだけでは不十分で、障害物のy座標を209にするときはy座標、x座標の順。それ以外のときはx座標、y座標の順で書き出す必要があります。
自前のルーチンだと座標書き換え中は割り込み禁止などの手が使えるのですが、BIOSを利用すると勝手に割り込み許可されてしまうので順序を変えることで対処しました。
座標チェックは座標書き換え中にして欲しくないのですが、BIOSを利用するとそういう細かい制御ができないので対処に悩みます。
ゲーム調整
ゲーム調整した結果、以下の動画のようになりました。
- 障害物をジャンプで避け続けるゲーム
- ジャンプ回数は無制限だがジャンプ中は前へ進んでしまい難しくなる
- 逆に地上の場合、後ろへゆっくり下がる。また地上では得点が倍入る
ジャンプ中の前進速度や地上の後退速度、障害物の配置等の調整は少し残りますが、ゲーム内容としてはこれで決定です。
ジャンプ回数制限の撤廃などでコード長は減りましたがそれでも減ったり増えたりで644バイト。いかにして132バイトも削るのか。
ちょっと不可能な気もしますが、もう少し粘ってみます。