大胆かつマニアックな本企画。その第二回目は、高速横スクロール処理をはじめとした、弊社ゲーム製品「ビートバイス」に散りばめられた様々な技術を紹介してまいります。 | |||||||||||||||||||||||||
「ビートバイス」は風雅システムが発売した最初のPC9801Vシリーズ用製品です。一般的なカテゴリとしては「シューティングゲーム」になりますが、プレイヤーの進め方によってストーリーが大きく変わってくるシステムになっており、敢えて「RPGシューティング」と銘打ってあります。本題からは若干逸れますが、簡単にビートバイスのゲーム内容の特徴をご紹介いたします。
|
|||||||||||||||||||||||||
高速横スクロール | |||||||||||||||||||||||||
シューティングゲームといえば、欠かせないもののひとつが『スピード感』でしょう。そしてこのスピード感を演出するのがスクロールです。縦スクロールは前作のX1用リボルティー2で使った(?)ので、PC9801版スクロールは横スクロールでいこうということになりました。しかし、前回の講義でも言いましたが、スクロールはとても重い処理です。ましてやPC9801の最高解像度は640×400ピクセルと、X1の倍もあります。16bitCPU搭載とはいえ、当時の主力機のCPUはV30-10MHz、せいぜいで80286-10MHzでしたから、Z80Aの数倍程度の性能です。X1のように画面にプライオリティ(表示の優先順序)が無いので前回のようなテクニックは使用できません。フルカラー(8色)でスクロールとなると大量のデータ転送による大きなCPU負荷が予測されます。 PC9801にはいちおうハードウェアスクロール機能がありました。しかし、全画面スクロールか、部分スクロールでも画面を横に割ることしかできず、使い勝手の良いものではありません。また、縦方向には1ドット単位でスクロールが可能でしたが、横方向には16ドット単位でしかスクロールさせることができませんでした。しかも、横スクロールはハード的な制約を受け、連続してスクロールさせることができるのは6000ドット程度。これではスピード感は出せてもあっという間にスクロールが終わってしまいます。"繋ぎ"の部分に空白を入れて凌ぐという手も考えられますが、スマートではありません。結局、ソフトウェアによる8ドット単位の横スクロールを採用することになったのです。 さて、問題はこれからです。16ドットではなく、8ドット単位。これが大きな速度の壁となります。ご存じの通り、PC9801Vシリーズは16ビットCPUであるV30や80286を採用していましたから、メモリも16ビット単位で接続されています。このため、画面への書き込みも偶数アドレスから2バイト(16ドット)単位で行うと効率が良いわけです。ところがこれを1バイト(8ドット)単位で行うと約2倍の時間がかかってしまうのです。もちろんこれの回避策も検討しました。そう、あるにはあるんです。ところがどうしても処理が煩雑になってしまう、作業用のメモリが必要になってしまう、デザイナに大きな負担を強いてしまう・・・・などの問題がのしかかってきてしまうのです。 そこで採った方法が、「とにかく高速に8ドット単位で描く」という力業でした。それもV30-CPUに特化するのです。80286CPUを搭載したPC9801VX2などは、VRAMもデュアルポートタイプでアクセスが高速でしたし、何よりCPUが高速でした。そのため普通に作っても十分な速度が得られたので、プログラミングでは意識しないことにしたのです。(でも実際にはV30に対する高速化はそのまま80286にも利いたように思います。) 今、ビートバイスのスクロールルーチン見てみると、「まだ何%か高速化できたな・・・」などと思ったりするのですが、そこはノウハウが身に付いているせいなのでしょう。当時のCPUにはキャッシュメモリや分岐予測などの機能がありませんでしたから、消費クロックやパイプラインの状態などを真面目に考慮すれば済みました。V30-CPUの場合は次のような点に注意しながらニモニック(アセンブラプログラム)を書いていくと高速化に寄与します。 |
|||||||||||||||||||||||||
1 V30は実効アドレスの計算をワイヤードロジックで行うため、複雑さに関係なく2クロックで実行してしまいます。このため、LEA命令も4クロックと高速ですし、"ベース+インデックス+ディスプレースメント"といったアドレッシングモードを使用しても速度の低下がありません。(ただし命令のプリフェッチキューが6バイト分しかないので16ビットディスプレースメントを連続して使用したりするとキューが不足してレーテンシが発生する可能性あり。) 何度も使う実効アドレスを事前に計算しておくよりも、直接オペランドに指定して毎回計算させる方が高速になる場合が多いのです。 | |||||||||||||||||||||||||
2 V30の分岐命令は8086に比べれば高速ですが、11〜15クロックを消費します。サブルーチンコール命令ともなると8086にも劣るほどクロックを浪費します(RET命令と合わせて30〜40クロック)。当然、高速化のためには極力使用しないようにコーディングすべきです。最近の命令キャッシュを持つCPUの場合は、ループを構成しているコードが命令キャッシュに収まり切らないと毎回ペナルティーを喰らうため、分岐命令を使ってループを小さくした方が圧倒的に速くなったりしますが、V30や80286の場合はアセンブラマクロなどを使用してループを展開してコーディングすると高速化します。比較的小さなサブルーチンも同様に展開すると高速になります。(もちろん何でもかんでも展開してしまうと今度はメモリを圧迫してしまうので、要所に留めておきます。) | |||||||||||||||||||||||||
3 先にも名前が出ましたが、V30には命令キューが6バイトしかありません。この命令キューの存在を意識することが高速プログラミングのコツにもなります。V30はキュー内に2バイト分以上の空きができると次の命令を2バイト単位でキューにロードしようとします。もし奇数アドレスに次の命令がある場合はまず1バイトロードしてから次の2バイトをロードします。つまり、命令の並びが重要になってきます。小さなループの場合などは、ループの先頭が偶数アドレスになるように指定し、なるべく実行時間の短い単純な命令が続かないようにコーディングすると、キューが有効に機能しやすくなります。逆に命令キューに次の命令をロードする暇を与えないような命令が続くと、キューの存在意味がなくなってしまうわけです。もちろん、分岐命令が多くてもキューはその都度クリアされるので非効率的になります。 | |||||||||||||||||||||||||
4 STOSやLODSなどのストリング命令を有効に活用することも忘れてはいけません。両方とも消費クロックは7と非常に効率的です。"LODSW"は1バイトの命令で、AXレジスタにSIレジスタで示されるメモリの内容をコピーし、SIレジスタに2を加算します。MOVとADDで組むと5バイトで15クロックもかかります。一般的にストリング命令はREPプリフィクスと合わせて使うものと思われがちですが、バラで使用しても効果的です。ビートバイスではスクロールルーチンにも多用されている命令で、8×16ドットの背景パーツを画面に表示する際に例えば次のようにして使用されています。
|
|||||||||||||||||||||||||
これらの他にもコーディング上の細かな高速化テクニックがいくつもありますが、作用の比較的大きなものを紹介しました。 | |||||||||||||||||||||||||
GRCG(グラフィクチャージャ)の活用 | |||||||||||||||||||||||||
PC9801Vシリーズ以降の機種にはGRCG(グラフィックチャージャ)というハードウェアが漏れなく付いています。Vシリーズは4096色中16色同時発色に対応しているため、V-RAM容量が128KBと、V30および80286のセグメントサイズ64KBを大きく超えています。さらに新しく追加された輝度プレーンのアドレスは、従来のRGBプレーンから飛び地のように離れています(右図参照)。このため、画面に16色で表示しようとすると2回もセグメントを切り替えねばなりません。CPUのセグメント変更命令は決して高速ではありませんし、プログラムも煩雑になります。さらに広範囲に渡って何かを表示しようとすると、各プレーンごとにパターンを描画するため、"一瞬の色ムラ"が発生しやすくなってしまいます。 そこでGRCGの登場です。GRCGはCPUから見るとあたかも1プレーンに対する描画あるいは参照で最大4プレーンにアクセスしたようにV-RAMを操作してくれます。つまりセグメント切り替えをハードウェアが行ってくれるようなものです。もちろん実際にはGRCGが4枚のプレーンを順次切り替えて描画を行っているので、1回のアクセス時間はそれなりにかかります。それでも、CPUが4プレーンにアクセスするよりは確実に高速です。 しかし、GRCGは1バイト単位でしかアクセスができません(モードによっては2バイトアクセス可)。そのため有益に利用できる局面は限られてきます。カラーの線や円の描画、画面の塗りつぶし、閉領域のペイント(境界サーチ)などです。 では、ゲームに応用しようとするとどうなるか。これが「キャラクタの合成」に利用できるのです。ビートバイスは8ドットスクロールのため、背景を含む全てのキャラクタを1バイト単位で操作しているためです。ちなみに、これが16ドットスクロールだと、2バイト単位のアクセスで済むため、GRCGの出番はありません。すべてCPUだけで処理した方が実際高速になります。ビートバイスの自機や敵機、弾丸などはすべてGRCGを利用して高速描画しています。そのため、合成するキャラクタのデータ形式はGRCGに合わせた特殊なフォーマットになっています(1バイトごとにB→R→G→マスク)。 また、宇宙ステージの星の流れる背景にも利用されています。流れている星はスクロールエリアの黒い部分のみに表示されるようになっているのですが、このときにもGRCGを利用しているのです。4×4ドット以上の黒い部分があるかどうかをチェックし、あれば星を表示するわけです。あらかじめ「黒色」の色コードをGRCGに設定しておけば、後は1プレーンの参照のみで判断できるため、かなり高速です。 ここでは敢えてGRCGの詳細には触れません。「こういう使い方もあるのか・・・」といったところに注目していただければと思います。 |
|||||||||||||||||||||||||
キャラクタ合成とちらつき防止 | |||||||||||||||||||||||||
リアルタイム系ゲームの天敵ともいえるのが画面のちらつきです。画面を書き換えている最中にその場所をディスプレイの走査線が走ると一瞬キャラクタ等が消えたように見える現象です。これはメモリ上に画面バッファを設けたり、フリップを使ったりして回避するのが一般的です。実際、風雅のソフトでもアマランス2以降はフリップを使ってこれを回避しています。ところがこれらの方法には欠点もあります。それは余分にメモリを喰うということです。PC9801の場合、8色モードで96KB、16色モードで128KB、画面バッファに必要となります。今なら「たったの?」と言われそうですが、メインメモリが最大で640KBしか無かった時代にはとても大きな値です。DOSやドライバ類に必要な容量を引くと、実際には500KB足らずしか自由にならなかった時代でしたから、これは大きな問題です。PC9801Vシリーズはカラー画面を2枚分持っていてフリップを実現し易くなっていましたが、ビートバイスではボスキャラのデータなどでリザーブされていたので、どうしてもフリップや画面バッファを使わないでちらつきを無くす必要に迫られていたのです。 いろいろなアイデアを出し、試行錯誤を行った末にたどり着いた方法が、「縦方向描画」と「バッファ内合成」でした。縦方向描画とは、走査線が走る方向とは垂直方向にマップやキャラクタを描画していくことをいいます。スクロールフィールドを8×16ドットのパーツ(PCGと呼んでいます)で埋めて描画する際に、上から下に向けて行ごとに描くのではなく、左から右に向けて列ごとに描いていくのです。こうすることによって走査線との衝突が連続しにくくなり、ちらつきが低下するのです。これはキャラクタを合成するときにも効果があります。 バッファ内合成はスクロールフィールドに描画するキャラクタを合成表示する際に、直接画面に対して合成するのではなく、PCG1個分のごく小さなバッファ内で背景PCGとキャラクタの一部を合成してから、その結果を画面に描画する方式です。この処理をスクロールフィールドの書き換え時にキャラクタ表示座標と同期させれば、無駄無く、ちらつき無く合成表示が可能となります。このため、ビートバイスの自機を始め全キャラクタは、スクロールフィールドのPCGに合わせて、横8ドット単位で縦方向に分割されたデータ形式になっています。 以上の工夫により、フリップも画面バッファも使用せずに、ちらつきのほとんどないゲーム画面を実現しています。 |
|||||||||||||||||||||||||
テキスト画面同期と漢字PCG | |||||||||||||||||||||||||
これがビートバイスという製品の最も特徴的な部分ではないかと思います。PC9801というパソコンはグラフィック画面の手前にテキスト画面が常に重ねて表示されています。テキスト画面は8/16×16ドット単位で構成されていて、ASCIIコードや漢字コードと文字色や点滅などの属性情報を書き込むことでキャラクタ/漢字ROMに登録された文字や、1ドットが4×4pixelのセミグラフィックを表示することができます。ROMにあらかじめ登録されているパターンしか表示できませんから、シューティングゲームに利用しようとしてもメッセージなどの文字に使う以外には手が無いように感じます。また、セミグラフィックも見た目が非常に荒く、2×4ドット単位でしか色を付けることができませんし、その色も8色のみでパレットもありません。これでゲーム画面を作ってもPC8001の頃のゲームに見えてしまいます。 しかし、PC9801Vシリーズには188文字分の"漢字PCG(=外字機能)"が内蔵されています。これはモノクロながら、16×16ドット単位のテキスト画面上のグラフィック機能に他なりません。ビートバイスにはこれを利用してCPU負荷が非常に少ないながらも迫力のあるオプション兵器等のアニメーションを実現しています。ゲーム開始時に標準装備の「バルカン砲」をはじめ、画面全体を覆う雷系兵器、バリア、ビームなど、グラフィック画面で合成表示すると非常に重くなる効果もほとんどCPUにストレス無く表示させているのです。 また、兵器のアニメーション効果は派手で時間も短い場合が多いこともミソです。動きの激しいものはディテールを認知しにくいので、単色ベタであっても違和感を覚えにくいというわけです。ちなみに、漢字PCGだけでなく、テキストセミグラフィックも併用しています。 でもちょっと待ってください。テキスト画面は横8ドット、縦16ドット単位でしかキャラクタを配置できません。ビートバイスの自機は上下4ドット単位で移動します。そうしないとスムーズな移動に見えないからです。実はビートバイスでは、テキスト画面のスムーススクロール機能を利用して、グラフィック画面と同期をとっているのです。これによって極太のビームを発射しながら上下に移動しても、ビームはスムースにワインディングするようになっています。プログラムは大変でしたが・・・・・(^_^; |
|||||||||||||||||||||||||
|
マルチタイマー割り込みマネジメントドライバ | ||||||||||||||||||||||||
ビートバイスのために開発されたのがこの「マルチタイマー割り込みマネジメントドライバ」です。(X1と違って)PC9801にはいくつもの割り込みソースがありますが、自由に使用できるものは限られています。一般的にはタイマー割り込み1チャネルと垂直同期信号割り込み(V-Blanking)1チャネル程度になります。少し無理をすればマウス用割り込みやFM音源ボード上のOPN内のタイマー割り込みなども使えないことはないのですが、犠牲も出るので思わしくありません。 しかし、上質なゲームを作ろうとするととてもこれだけでは足りません。垂直同期割り込みは周期が16.7msに固定ですので、使い道も制限されます。その点、タイマー割り込みは自由に割り込み周期を設定でき、1ms未満の高周期割り込みも可能です。そこで考え出されたのが一本の割り込みソースからいくつもの割り込みサービスを提供するドライバです。これは常駐プログラム(TRS)として作られ、ゲーム本体のみならず、他の常駐プログラムからも利用可能になっています。ビートバイスに使用されているバージョンでは、最大16チャネルのインターバルタイマまたはワンショットタイマーを提供可能です。これを利用して、スクロール速度の決定、アニメーションの実行、BEEP効果音のモジュレーションなどを実現しています。このドライバは風雅システム最後のPC9801用ソフトであるアマランス4まで、ずっと使用され続けています。 |
|||||||||||||||||||||||||
ビープ効果音ドライバ | |||||||||||||||||||||||||
ところがゲームには効果音がつきものです。この効果音をFM音源やPSGを使って鳴らそうとすると、演奏中の音楽のパートが一時的に抜けてしまうことになります。これでは折角の曲が台無しです。ことシューティングゲームとなると、ビームや弾丸の発射音、爆発音などで効果音はほとんど鳴りっぱなしの状態になりがちです。他社製品を見ると最初から効果音用にパートを空けてある曲をBGMに使ったりして回避しているものもありました。 しかし風雅システムでは曲に対してこのような妥協はしません。音源はFM音源とPGSだけではないのです。標準で必ず装備されている「ビープ音」があるではないですか。PC9801Vシリーズのビープ音は周波数が設定できる、つまり音階をつけることができるので、単音の音源として立派に機能するのです。これを利用しない手はありません。ただし、複数の効果音が重なった場合はどうするのか、迫力ある音は出るのか、そもそも音色が最初から1種類しかないではないか・・・・などなどツッコミどころ満載でしょう。ところがこれらのツッコミにもちゃんと応えることができるのです。 それが「ビープ効果音ドライバ」です。実はこのノウハウは往年の名機と謳われる「MZ-80シリーズ」の頃に培われたものなのです。MZ-80は、Z80CPU-2MHzにRAMが4〜48KBというパソコン(当時の言葉ではマイコン)でした。そしてこれに周波数を自由に設定できる単音の音源(タイマーICを使った方形波)が載っていたのです。これまさにPC9801Vシリーズと同じです。このため、当時のMZユーザは欲を出して“和音”に憧れていました。「何とか標準のハードだけで和音を出せないか・・・・」MZユーザだったプログラマの杉之原名人もそう思っていました。そこでものは試しに、時分割で和音にならないか実験してみたのです。つまり、ドの音を10ms出し、次にミの音を10ms出し、その次にソの音を同様に10ms出すということを繰り返すのです。するとどうでしょう。音色こそノイジーですが、ちゃんと和音に聞こえるではありませんか。いろいろと条件を変えて鳴らしてみると、思ったよりも色々な音を作ることができたのです。 ビートバイスの効果音はすべてビープ音でできています。でも、バルカン砲発射音、ビーム発射音、特殊兵器発射音、爆発音、被弾音など、それぞれ個性的な音色が20種類近く出せるのです。ちなみに時分割用のタイマーは、先の「マルチタイマー割り込みマネジメントドライバ」をゲーム本体プログラムと共用利用しています。 |
|||||||||||||||||||||||||
多段圧縮 | |||||||||||||||||||||||||
容量制限・・・・CD/DVDメディアが標準の現在ではあまり考えられない現実がありました。ビートバイス開発当時は2HDのフロッピーディスクが製品の標準メディアでしたから、一枚当たり約1.2Mバイトの容量しかありませんでした(DOS/Vパソコンのフロッピーは1.44Mですが、フォーマットの違いでPC9801用は容量が少なかったのです)。 当たり前の話ですが、製品のフロッピー枚数は直接製造コストに反映されます。フロッピーの場合はメディア代金の他に、ラベル代・エンベロープ代・デュプリケート(コピー)代がかかりますから、できるだけ枚数を少なくしたいわけです。さもないと製品の価格を上げざるを得なくなってしまうからです。開発部としては当初4〜5枚組を希望していたのですが、天の声は「3枚!」ということでした。
これらの圧縮には開発時間もかかりましたが、容量の低下に伴って読み込み時間の短縮という嬉しいご褒美が付いていました。データを展開するのはほぼ一瞬ですから、ゲーム中の待ち時間も当初に比べるとグッと短くなりました。余談ですが、ビートバイスはハードディスクインストールにも正式対応(おそらくゲーム初)していましたから、当時一般的だった40MB(メガバイト)容量のハードディスクにも、容量圧迫の観点から優しく(?)なりました。 |
|||||||||||||||||||||||||
コピープロテクト | |||||||||||||||||||||||||
リボルティー2での経験を踏まえ、ビートバイスにはより強力なコピープロテクトを用意していました。それはバックアップツールのファイラー(個別対応パッチデータ)でも理論的に不可能なものでした。製品のデュプリケートを担当する会社の技術の人と綿密な話し合いをして、過去に例の無い手法で行うことにしていたのです。 それは俗に言う「回転数プロテクト」の一種でした。フロッピードライブの回転数を若干(数%)落とすことによって、1トラックに記録できるセクタ数を増やしてしまうのです。すると、このフロッピーをバックアップしようとしても、回転数も書き込み周波数も一定である市販ドライブでは、読み込みはできても書き込みができなくなってしまう(1トラックのデータ量が多いために、1回転して最初のデータを潰してしまう)という仕組みです。この手法をさらに進めて、ファイラーを利かなくするのです。バックアップツールのファイラーは、とにかく新しくバックアップした方のフロッピーでもプログラムが動作するように、プログラムのプロテクトチェック部分を無効になるように書き換えたりしてしまいます。でもそれができなくなるとしたら・・・・。 前項の圧縮のところでも言いましたが、ビートバイスはプログラム本体にも圧縮がかかっています。この実行ファイル圧縮は米国の某社のもので、マイナーな製品のためデコーダなどは存在しません。すると簡単にプログラムを書き換えることができなくなってしまいます。まずこれが一点。そして、回転数を落として(実際には回転数そのままで、データの書き込み周波数をちょっと上げる)作られた「オーバーセクタ」に、実際にゲームで使う重要なデータを書き込んでおきます。さらに、そのフロッピーディスクの全てのセクタにも、実際にゲームで使用されるデータが入るように調整します(というか、調整などしなくても目一杯になってます)。こうしておくと、「オーバーセクタ」の内容を他の空いているセクタに移して、プログラムを書き換えるということもできなくなります。オーバートラックを使って空きスペースを作るという方法が考えられますが、そもそも“プロテクトチェック”をしているわけではないので、プログラムに大幅な変更が必要になり、ファイラーそのものが現実的な手法でなくなってしまうという仕掛けです。 さて、では実際はどうだったかというと・・・・・プロテクトは掛けられていません。いや、正確に言うなら、物理的に掛けられたプロテクトを利用していません。理由は単純でプロテクト処理を施す時間的余裕が全くなかったからです。デュプリケート会社の方に多大な苦労をおかけしたにもかかわらず、全くそれを無視していることに対して猛烈に申し訳ない気持ちでいっぱいでした。 ところが意外な結果が待っていました。一ヶ月近く経ってもバックアップツール用ファイラーが出てこないのです。警告メッセージなどを無視すれば、ごく当たり前にバックアップツールでコピーが取れるからなのかな?などと思っていたのですが、どうやらそうではなかったようです。つまり、ファイラー作成側はフロッピーディスクをまず調べてみるわけです。すると某トラックのセクタ数が多いことに気づきます。容量を計算してみると回転数プロテクト用のトラックだと判明します。そのセクタの中身を見てみると特にデータが書き込まれていないようです。すると、トラック内のセクタ数をチェックするか、容量をチェックするかしているのだなと推測します。でも、インストール時にもプレイ開始時にもチェックはなく、とりあえずコピーしたフロッピーディスクでも問題なくプレイできます。ということは、ある程度ゲームが進んでからチェックしているのだなと考えます。実行中のマシンのメモリ内をICEを使って調べてもプロテクトチェックらしき部分は見あたりません。すると、プロテクトチェックルーチン自体に暗号がかけられていて、チェック時にその暗号をはずしてから実行するのだなと推測します。しかし、そうなっているとすると、全プログラムを解析するか、実際にチェックルーチンが走る瞬間をICEで捕まえるかしないといけません。これは大仕事です。まさにプロテクトを掛ける側と外す側との「読み合い」ですが、まさか「ここまでやっておいてチェックしてないってことはないだろう・・・・」という考えが強かったのでしょう。時間をたっぷりかけて解析されたか、ゲームをクリアされたのだと思います(RPGタイプなので一通りプレイするのはすんごい大変だったことでしょうけど)。結局、一ヶ月以上経ってから、「自機増殖」「無敵」などのオマケ付きのファイラーが発売されました。これは結果的に、プロテクトチェックをしなかった方がコピープロテクト効果が高いということに他なりません。以後、理由は他にもありますが、風雅システムの製品はノンプロテクトか、簡単なプロテクトがかかっていてもまともにチェックしていません。 |
|||||||||||||||||||||||||