動く浮遊要塞(名称未定)

2017/1/27 | 投稿者: ghost

今回のゼビモドキ開発でもっともやりたかったことの1つは、地上背景の上に浮かぶ浮遊要塞、本家ゼビウスで言うところのアンドアジェネシス……そのモドキ、を実現することだ。この部分のプログラムが面倒臭いことは事前にわかっていたので、敢えて早目に取り組むことにした。んで、意外にもあっさりと出来てしまった。


デザインは後日変更する可能性もある……本当はこの後四隅のプラットホーム部にスプライトパターンで赤い開口部が表示され、これが攻撃標的になるのだが、背景に過ぎない絵の中にも赤色の部分があるのはちょっと筋が悪い……が、とりあえず背景スクロールと独立して浮かんでいるように……見えるよな?見えると言ってくれ!

まぁ、別に何か特別なことをやっているワケではない。一旦メモリ上のバッファ領域に作った背景の上に、浮遊要塞の図柄を重ねてからVRAMに送っている、という話は既に書いた。ちなみに今回の実装では背景スクロールのフレームレートは2.5回/秒(24/60秒毎)になっている。で、何が面倒臭いか(いや、やってみたらそうでもなかったんだけども)というと、画面上の一地点に停止しているのであれば話は簡単なのだが、演出上、コイツは画面の上下から出入りしなければならない。

VRAM送出用のバッファ領域はきっかりゲーム画面分(384バイト)しか確保していないので、何も考えずにコイツの図柄をそこへ重ね合わせようとすると、画面から出入りする際にはみ出した部分がメモリ上で隣接するデータを壊してしまう。その部分がプログラムだったら即暴走だ。実際、テスト中に一度やらかして、そのときは幸いに画面下方向に隣接していたのが自機のカーソル入力に対応する移動量テーブルだったため暴走こそしなかったが、そこにあらぬ値が書き込まれたため、画面じゅうを自機がワープして飛び回る、というトンデモ現象が発生して一瞬焦った。

そういうことが起こらないように、浮遊要塞のゲーム画面に対する相対位置を勘案して、バッファ領域と重なる部分についてのみ書き込みをおこない重ならない部分は無視する、というロジックを組まなければならないのだが、アセンブラでこれを書くのは(まぁ、案ずるより産むが易しではあったのだが)存外面倒臭いのである。誰も興味はないと思うが、ボクが結果的に楽しかったので今日はその話を書く。

クドいが繰り返し言っておこうと思うが、本稿を書いている理由は、それを説明するため、ではなく、ある意図とそれを実現すること、の間にある数々の論理をプログラムとして組み上げていく思考過程の楽しさ、を書き残したいと思ったからである。このような行為を通すことによってのみ、普段何気なく無意識のうちにやってしまっている多くのことが、意図と実現をつなぐ何段階もの緻密な論理によって成り立っていることを実感できる。まぁ、実感したくない人はしなくてもいいのだが。


クリックすると元のサイズで表示します左図は、バッファ領域の模式化したものだ。便宜上面積を有しているかのように描かれているが、実際のメモリは連続する一直線の数字の列に過ぎない。ゼビモドキのゲーム画面は8×8ピクセルからなるパターンを16×24個敷き詰めた長方形になっていて、これが左図の水色部分に相当する。メモリを16バイト毎に折り返して書くと丁度こんな感じで、この中を9×8個のパターンからなる浮遊要塞が上から下へ通過していく様は、まさに左図の通りとなる。

前述したテスト中の異常動作は、左図(3)の状態で、本来はメモリへの書き込みを回避しなければならないバッファからはみ出した赤い部分についても、プログラムの考慮漏れから書き込みをおこなってしまったことに起因する。逆に言えば、(3)に加えて(1)の状態においても、赤い部分については書き込みをおこなわず、青い部分のみ書き込みをおこなう、という判断をプログラムの中でおこなわなければならない。

日常的な思考から考えると、メモリ側に「ここはバッファ領域、ここはそうじゃない」という目印があって、これを逐一確認して書くか否かを判断すればいいように思うかも知れないし、実際のところ現代のCPU+マルチタスクOSにおけるメモリ保護機能は(本質的にはこの話題とは関係がないが)それをやっているのだが、残念ながら最もプリミティブな領域におけるZ80コンピューティングはそういう世界ではない。CPUから見ると、RAM上のすべてのアドレスは無差別・等価なのであり、あくまでもプログラム側のロジックのみでこれを判断しなければならないのだ。

面白いことに(……ボクは面白いのだが、あなたは違うだろうか?)前掲図の(1)と(3)のケースは異なる方法で対処する必要がある。図上の視覚としてはどちらも「はみ出し」だが、前述したように、実際のメモリ空間は面積ではなく一直線に並んだ数字の列であり、これを操作するCPUの取り得る手段は、極めて限定的ななのだ。その実際を、アセンブラコードを通して示してみたい。見易さのため、ロジックのまとまり毎に背景の色を変えている。

クリックすると元のサイズで表示します

基本的な考え方は、上掲コード冒頭でdeおよびhlレジスタにそれぞれバッファ領域、浮遊要塞の図柄の先頭アドレス(メモリ上の位置)を得て、これをうまいこと増やして後者から前者へと内容のコピーをしていく、という話になる。

クリックすると元のサイズで表示しますここで、考え方を整理するために前掲図に2つの要素を加えたい。第一には画面上の行で、バッファ領域の先頭が0行目の左上、末尾が23行目の右下に当たる。24行目、というのは、実際には画面には存在しないものになるが、ここまで行ってしまうと前述した異常動作の原因になる行数、ということになろう。

対して、浮遊要塞をどこに表示するか、を意味する「相対位置」を考える。画面上の行と揃えたいところだが、画面上端に浮遊要塞の一部が隠れているときが考えにくくなるので、浮遊要塞が完全に画面上方向に隠れている状態を-1と定義することにする。つまり、相対位置0は浮遊要塞の一番下の一行が画面上に現れる状態であり、8は全体が姿を現した状態に当たる。

ここからわかるのは、相対位置が8未満の場合、浮遊要塞全体を書き込むということはないのであって、実際に書き込む行数は相対位置に1を加えた行数を、画面上の行=バッファ領域の先頭から書き込めば良い、ということである。これを前提に上掲のアセンブラコードおよび付したコメントを読んでもらうと、今ここで説明した通りのことをしているのがおわかりいただけるだろう。わかるか、おい?

Z80には掛け算命令が存在しないので、足し算と引き算だけですべてをまかなわなければならない。コメント中に掛け算が書かれている部分の末尾にdjnzという命令が共通して見えると思うが、これはDo Jump Not Zero、をいささか強引に略記した命令で、bレジスタから1を引いて0になるまで繰り返せ、の意味になる。掛け算というのは要するに足し算の繰り返しであるから、平たく言えば×bレジスタがここでおこなわれていることになる。

ややこしいのは、このbレジスタ自身がアドレス計算を含む16ビット演算に使われるため、push/pop 命令で退避する行為が著しく可読性を損ねる点だろうか。これはレジスタの内容をスタックと呼ばれる一時域にしまったり取り出したりする命令で、bレジスタを他事に使う直前にpushし、使い終わった後でpopして元に戻している。

以下、同じ考え方をケース(2)、画面内に浮遊要塞全体が現れている場合に当てはめてやる。相対位置-8行だけ、浮遊要塞の上端は画面上端から離れているはずだから、この離れている行数×16バイトをバッファ領域の先頭アドレスに加えてやれば、浮遊要塞を書き込むべき位置を得ることができる。では、続きを見ていこう。

クリックすると元のサイズで表示します

実際の重ね合わせをやっているのが上掲コードになる。重ね合わせ、というと何だか特別なことをやっているように聞こえるかも知れないが、実際にやっていることはとてもつまらないことだ。

クリックすると元のサイズで表示します

上に示したのは、浮遊要塞の姿を定義した部分である。ボクにはこの数字の羅列がしっかりと浮遊要塞の姿に見えるのだが、あなたもそうだろうか。それはともかくとして、この数字中、0の部分は要塞の構造がなく、背景が透けて見えて欲しい部分になる。これをわかった上で先のコードに目を移すと、1バイトずつこのデータを舐めながら、値が0のときだけバッファ領域への書き込みをスキップしていることがおわかりいただけるだろう……いただけるかな?

浮遊要塞の図柄を書き込まない、ということは、バッファ領域に事前に用意されていた背景の図柄がそのまま残る、ということであり、つまり、透けて見えるという意図が実現されることになる。ほら、わかってしまえばつまんないことでしょ。

最後に残ったケース(3)、ゲーム画面下端へ浮遊要塞がはみ出している場合への対処は、拍子抜けするほど簡単である。実際の浮遊要塞の書き込みはバッファ領域と要塞絵柄のアドレスのみで管理されているので、相対位置はもう使う場面がないのだが、敢えてこれを維持して、浮遊要塞を1行書き込む都度に1加算している。コード中はこの値をcレジスタに保持している。したがって、この値が加算直後に24になったら、これ以上書いたら危険領域に書き込んでしまうぞ、といううことであり、ここでこの処理を終了すればいい。

察しの良いひとはもうおわかりかと思うが、先に書いた異常動作は、ここで言うcレジスタの値の保持に、push/pop命令の位置のマズさから失敗していて、cレジスタがずっと0のまま、その他の部分は正常動作する、という憂き目に陥ったからである。流石にこれは我ながら笑った……アレ、笑えない?

蛇足ながら念のために補足しておくと、上に示したコードはこの課題を解くための最適手ではない。もっと短く書こうと思えば書けると思う。今回は、まだこの部分に後日なんらかのロジックを追加する可能性があるので、直感的に読み易い書き方を意識した結果こうなったものである。というか、必ずしも最適手を追う必要がなく、自由気ままにコードを書けるのは、趣味プログラマの特権だ、とも言える。じゃぁ、仕事では自由きままに書いてないのか、と問われると、それはまた別の話なのだが。
タグ: MSX Z80



コメントを書く

名前
メールアドレス
コメント本文(1000文字まで)
URL





AutoPage最新お知らせ