背景が動くようになった

2017/6/7 | 投稿者: ghost

今回はモックアップではなく、ROMフォーマットでアセンブルした現物である。

それはさておき。ボクはこう見えて結構繊細なので、前作ゼビモドキの開発に際し、YouTubeのギャラリーから「8ピクセル単位のスクロールなんて!」と好き勝手な罵声を浴びせられて、ちょっと傷ついていたのである。で、今回の開発では「そこまで言うならやってやろーじゃねーか」な気分になって、ピクセル単位でスクロールする背景を仕込むことにした。

……こういうのは繊細、じゃなくて、根に持つ、というべきか。しかも微妙にかみ合ってないし。ま、そんなことはどうでもいい。


<背景をスクロールさせてみた>

これは純粋に背景であって、有り体に言えばゲーム進行そのものとはまったく関係のない飾りの部分、になる。強いて言えば、前景(画面下部1/3)の8ピクセルスクロールしている部分は、これがまるまま30×8パターンのフレームバッファとしてPCGキャラクタの表示処理を兼ねているのでまったくゲームと無関係ではないのではあるが、これとて、キャラクタの背後にこんな申し訳程度の背景を描く積極的な理由はないのだ。

以下、つらつらと技術的な備忘を少々。

本作のフレームレート(1秒当たりの画面更新回数)はNTSCで15fps.、PALで16.6fps、と、アクションゲームとしては許されるぎりぎり下限あたりを、敢えて採用している。もっとも、上掲動画で一番細かい動きを見せる遠景の森のスクロールですらこのフレームレートで動かすといくらなんでも速過ぎる(敵キャラクタは基本的に徒歩なので、速度感はそれを基本にしている)ので、4フレームにつき1ピクセルスクロールするようウェイトを入れているので、今のところこのフレームレートを実感させるものは画面上には存在していない。

前景部分は前述したようにPCG敵キャラクタ表示用のフレームバッファでもあるので、内部的にはフレームレート毎に描き直されている。が、今回実装したその背景部分は遠景の森の倍の速度で左に向かって流れるよう、8フレームにつき1パターンずらされる。つまり、今のところ8フレームのうち7フレームは、虚しく同じ絵柄を画面下1/3の部分に展開している、ということになる。

一見してわかるように、背景のスクロールは三層構造をなしていて、うち遠景の2つは速度が異なるだけで原理はおなじだから、実質的に仕掛けは二種類、ということになる。いずれもMSX(というかVDP TMS9918を採用するすべてのビンテージ機)では極々当たり前の手法に準じたものだが、気分転換に原理を解説しておくことにしよう。


・前景の8ピクセル単位スクロール

話の単純な方から始めよう。

画面下1/3の表示用に横32×縦8パターン=256バイトのフレームバッファが2つ、計512バイトがメモリ上に確保されている。第1のフレームバッファが冒頭のデモで表示されている背景用だ。

先に全体の仕掛けに触れておくと、フレームレート毎に第1のフレームバッファが第2のフレームバッファに単純にコピーされる。これは背景の準備と、前回のフレームで描画されたPCGキャラクタの消去を兼ねている。ここにPCG敵キャラクタが部分上書きされる。結果、背景の上にPCGキャラクタが展開される。最後に第2のフレームバッファがVRAMに転送される。この部分は、基本的にはゼビモドキの浮遊要塞でやっているのと同じものになる。ちなみに、フレームバッファが32×8パターンであるのに対し、実際に画面に表示されているのが30×8パターンで左右が1行ずつ欠けているのは、横幅が2パターンのPCGキャラクタの見切れの処理を簡略化するためだ。

この設計下での画面左方向へ向かってのパターン(8ピクセル)単位のスクロールは、実は拍子抜けするほど簡単である。Z80的には1命令(厳密には準備のためのレジスタ代入を含め4命令)で実現されてしまう。具体的には、ブロック転送命令ldirを使って、第1のフレームバッファの先頭から末尾まで255バイトに渡って、各アドレスのデータを1つ前のアドレスにコピーしろ、と命じるだけでいい。イメージが湧かない人のために模式図を以下に示す。

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

フレームバッファを対応する画面構成に合わせて横16列×縦8行の表(上図では冗長な中央部を省略している)にし、先頭から順に0〜255の値が入っているとする。前述したブロック転送命令を実行すると、表中の値の状態は模式図の上から下の状態に遷移することになる。赤や青の着色で例示したように、一部の例外を除きどの部分を見ても、値は左へ向かって1マス動いたように見える。この値こそがVRAM上では表示される図柄のパターン番号なのであり、従って図柄も左に向かって動いたように見える、という寸法だ。

例外になるのは黄色で示した表の右端の部分になるが、ここはようするに観念上の画面の外から新たに現れる部分、ということになる。ブロック転送をした後に、この8バイトにだけ演出に応じた新たな値を書き込む。これを繰り返せば、実際には存在しない画面右端以降の世界から、次々に何かが現れては左に向かって流れていく様子を表現することができる。

・遠景の1ピクセル単位スクロール

対するこちらは、パターン番号の位置は固定したまま、その番号に対応する図柄自体を直接書き換えて、ピクセル単位のスクロール(もどき)を実現している。まず、ここで利用されているMSX(厳密にはTMS9918)のGRAPHIC2モードで、PCGの図柄がVRAM上でどのように管理しているか、に触れておくことにする。

クリックすると元のサイズで表示します左図は、遠景の岩山の1パターン分を抽出したものである。

GRAPHIC2モードの図柄は、左図で灰色で示した色のついている部分を1、色がついていない部分を0として、8×8ピクセルの横1ピクセル行ずつを8ビットの数値として管理される。たとえば、左図で黄色で示した最下段の1ピクセル行は二進数 11111101 として読める。十進数で書けば 253、十六進数ならば FD になるが、これが8つ、つまり8バイトあることで、1パターンの図柄を表現している。

厳密を期すと、これとは別に、図柄で1になっている部分は何色か、0のところは何色か、を、やはり1ピクセル行毎に示す情報8バイトが対になって、計16バイトでGRAPHIC2モードのパターン毎のPCGの見た目は決定される。この色情報の構造は、横方向ピクセル単位のスクロールに馴染むものではないので、今回は無視している。より厳密を期すならば、スクロールの範囲内で共有されていて変化しなくてよいデザインに落とし込んでいる。山の頂上付近は常に白だし森の木の幹は常に茶色だ、ということ。

さて、この条件下でピクセル単位に左へと図柄が流れていくように見せるためにはどうすればよいだろうか。つきつめれば、たとえば上模式図の黄色で示したピクセル行について言えば、

(1) 11111101 が 1111101? に変化する演算があればいい
(2) (1)の?部分は右隣のピクセル行の最左ビットを反映したい
(3) 逆に自分自身の最左ビットは左隣に反映したい

が実現できれば良いことになる。これも原理がわかってしまえば拍子抜けするほど簡単なのであるが、Z80にはまさにこの作業のために用意されたかの如き(もちろんそういうワケではなく、本来の目的が別途存在するが本稿とは直接関係しない)ローテーション命令 rla がある。

クリックすると元のサイズで表示します
<rla命令の動き>

上掲模式図はrla命令の結果、Z80のAレジスタに起こる出来事を示したものである。まず、前述の黄色着色部の値、11111101がAレジスタに入っている、とする。ここでrla命令を実行すると、この1と0の列が数字1つ分左にずれるのである。まさにピクセル(ビット)単位の左スクロールだ。

Aレジスタは当然8ビット枠なので、最左のビットは行き場がない。これは図で緑色で示した“キャリーフラグ”に格納される。この場合のキャリーとは、我々の日常の言葉でいうところの「繰り上がり」のことであり、キャリーフラグに1が入る=rla実行時にAレジスタの最左ビットが1だった、ということは、これを1ビット左にずらした結果、8ビットの位に収まらなかった1が桁あふれして飛び出したことを意味している。

逆に右端、つまり、1ビット左にずらした結果空いてしまう隙間(模式図の青の部分)には、rla命令実行直前のキャリーフラグの値が入ってくる。これは、この処理の直前に向かって右側に隣接するパターンの同じ1ピクセル行に対し同じことをしていれば、キャリーフラグには右隣のパターンの最左ビットが入っているはずなので、rlaさえしてやれば、まるで地続きにつながっているかのように0/1の列がそのまま流れ込んでくる、ということである。まさに、この演出のために用意された命令のようではないか(だから違うってば)。

今回のデモでは……というか、多分この部分は完成版に至るまで不変だとは思うが……ピクセル単位スクロールする部分はパターン数で2行あり、それぞれ一続きの16個のパターンを2回繰り返す形で画面上に並べてある。この図柄を左方向へ1ピクセルずらすには、16パターン×2行×8バイト=256バイトに対し、rla命令を適切な順序(1ピクセル行毎に左へ向わせる)で実行してやればよい。

もっとも、単純にこれをやると128回繰り返した時点ですべてのビットが画面左端へ流れてなくなってしまうから、左端と右端を神秘の力で輪のようにつないでやると話が単純でよろしい。そこで、各ピクセル行の処理の最初には、左端の1バイトにrla命令を実行しつつ、キャリーフラグだけ確保してメモリに書き戻さない、ということをしてやる。この後右端から件の処理を実行すれば、晴れて左端のさらに左端の1ビットを、右端のさらに右端に運ぶことが叶う。

なお、言うまでもなく以上の処理はRAM上のフレームバッファ上でおこない、次フレーム用のデータ一式が揃った時点でVRAMに一気に流し込んでいる。TMS9918を利用するどのアーキテクチャにも共通する話かとは思うが、(1)連続した一連のデータをVRAMに渡す、のと、(2)1バイト毎にアドレス指定してデータを渡す、のとでは処理所要時間が大きく異なり、後者の方が圧倒的に時間を要するからだ。一般的にBASICを含む高級言語は、実装の物理層(ここでは連続するRAM→VRAMのブロック転送)に直接アクセスする手段を提供しない。つまり、あらゆるVRAMアクセスは実質的に前述の方式(2)になってしまう。MSXでアセンブラでゲームプログラミングする速度メリットの大半は、ここに帰着すると言っても過言ではないだろう。

*     *     *

繰り返しになるが、以上の処理は、本作のゲームの本質にはまったく関係がなく、これらがなくともゲームとしてはおそらく何の支障もなく遊べるだろう、という要素なのであるが、ビンテージPCにおけるゲーム開発のどこに楽しみがあるか、ということを読者諸兄に伝えるのにうってつけの事例だ、と思ったので、必要以上に詳しく書き出してみた。

え、伝わらない?
タグ: MSX Z80



コメントを書く

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





AutoPage最新お知らせ