萌塵/PC-6001/TINY GRADIUS


PC-6001用 グラディウス勝手移植 TINY GRADIUSについての雑談


・はじめに(2021/8/29)

なんでPC-6001にグラディウスを移植しようなど無謀な事をしようとしたのか?これはひとえに子供の頃からの淡い夢でした。
「PC-6001でグラディウスを遊びたい」
しかし当時は学生で知識も技術力もなく、単なる妄想にすぎません。
それが何故始めたかというと、今から5〜6年前にふと自分の夢でやり残しは無いかと思い返すことがあり、そういえばPC-6001でグラディウスを作って遊ぶのが子供のころの夢だったなーと思い出したのがきっかけです。
当時丁度PC-6001がネット上で盛り上がっていてPC-6001用メガロムや戦士のカートリッジといった同人ハードや特攻空母ベルーガを目にしていたため、あれこれ組み合わせたら何とかなるんじゃないだろうかという打算を妄想し始めてしまいました。
処理速度については色々実験をしてキャラクタ表示やオブジェクトコントローラー、ベクトル演算による弾の発射。差分表示による高速スクロール処理を作りながら、どう考えてもメモリが足りない悩みが解決しません。
そこでメガロム専用で作ることを考えましたが、当時は無かったメガロムで作るのはどうかと悩みました。
最新技術で作るのならいくらでもハードを追加すれば何でも出来てしまうのでそれはしたくなかったのです。
そこで同じCPUであるZ80で動いているMSX版の事を思い出しました。
そういえばMSX版はメモリエリア64kbytes(Z80などの8BitCPUは通常はメモリは最大64Kbytesまでで、それ以上はバンク切り替えによるものになります)に収まっているのか?と調べたらやはりMSX版もメガロム(1Mビットでしたっけ?)を使っていました。
であればPC-6001版もメガロムまではセーフにしよう、と思い込むようにしました。

これでやっとPC-6001用グラディウス勝手移植が始められる。といった経緯でした。


・画面表示の工夫について(2021/8/29)

動画をご覧いただいた方であれば気付いていただいたかと思いますが、画面表示について縦方向に偶数行のラインはキャラクター、奇数行のラインは背景と交互に描画しています。

ステージ1

ステージ1の1シーン。山に埋もれている敵がわかりやすい?背景と重なっていないとレトロPCに良くある荒い画面なのかな?コンポジットのスキャンラインかな?って思う程度です。

こうする事でPC-6001にとっては処理の重いキャラクター表示、重ね合わせ、背景スクロールに加えて(表示するドット数が縦方向に半分になるので)メモリの節約をしています。
この方法を思いついたため、PC-6001へのグラディウスの移植が現実的なものとなった要のアイデアです。
最初はどうやってPC-6001でキャラクターと背景の重ね合わせを高速に実現するか悩んでいました。
色々考えた結果、(1)背景とキャラクターはマスク処理しないが真面目に重ね合わせ表示する。(2)2画面使って交互に表示する。(3)1行おきに交互に表示する。の3アイデアを思いつきました。
(1)は多分激重だろうから、画面表示を狭くしたり色々省略して当時コナミからPC-6001に移植されたスーパーコブラのような似ても似つかないものになるだろうと想定していました。
正直思いついたときは(2)が有力で、(3)は見れたものではないだろうと想定していました。早速(2)の方法を実験するとチラつきが激しすぎてとてもゲームになりません。キャラクターと背景が重なって表示されているようには見えませんでした。もう少し高速に切り替わって、残像効果で実現できると思っていたので非常に落胆しました。(3)はあまり期待していませんでしたが、これがダメだと残るは(1)のみです。早速テストしてみると元がコンポジット映像なので割と見れる結果が得られてビックリしました。これなら何とかイケると確信をもった瞬間です。


・背景のスクロールについて(2021/8/29)

背景のスクロールはいわゆる差分スクロールになっています。 差分スクロールを言葉で解説すると、スクロール後の変更箇所のみ書き換えて、変更の無いところは書き換えないというモノです。
スクロールで変更の無いところ?と思われるかもしれません。1ドット単位でスクロールした場合はベタ塗な背景でもない限り、どこもかしこも書き変わっていると思います。ですがそれでは高速化にはなりません。マップチップ単位でスクロールさせることで、はじめて書き換える場所が減って高速化が期待できます。
ここで、マップチップ単位でスクロールさせるということは、スクロールがガタガタになってしまうということです。これは高速化とのトレードオフになります。
差分スクロールとは何かがわかったところで、どのように実装するのでしょうか?素直に考えるとマップチップを配置するマップデータから、現在描画されているマップチップを画面左上から右下まで調べて、マップチップのIDが違っている場合は描画、同じ場合は描画をスキップする処理になります。
TINYGRADIUSでは差分スクロールですが、この実装はしていません。
理由は単純でメモリが足りないのです。
どうしようか悩んだ結果思いついたのが、マップデータからマップチップを比較して描画するのではなく、スクロールで変化するであろう場所のみ事前に予測して描画するという方式です。
と、ここまで書いておいて何ですが・・・言葉で説明すると難しい内容になります。
例として真っ黒な背景を星がスクロールする場合で説明します。

1.左端に星のマップチップを1つ描画します。

2.スクロールのタイミングで星を1つ左側に描画します。
 →このタイミングで星は左端とその1つ隣に描画されているので、計2個並んで表示されています。

3.ここで、左端に星を消すためのブランクのマップチップを新たに出現させます。
  このブランクが左端の星を消すことで、星が左側に1つ移動したように見えます。

4.2に戻りますが、プログラムの裏側ではこの2〜3の動作を繰り返すための「オブジェクト」を管理しています。

動作イメージ


背景のスクロールの為だけに「オブジェクト」を管理していると聞くと、なんだか重たそうな事をしているように思われるかもしれませんが、ここでやっている管理は敵キャラや弾などと同じ(厳密には簡易版)仕組みになるため、むしろシステムを軽く出来ます。
また、この方法だと画面に表示するためのデータのみを保持すればよいのでマップデータのメモリ圧縮に繋がります。
問題点も多々あります。
書き換え箇所が多くなると処理が重たく、データ量も増えてしまうのでデータ設計に工夫が必要になります。
またこの差分データを手作業で作るのは理屈では動作しても実質不可能に近いフォーマットになります。仕方なく専用のマップエディタを作ることになりました。

マップエディタ画面

マップエディタの画面です。楽をするためにJAVAで作りました。また、ファイルのセーブ・ロードはセキュリティ周りの実装がめんどくさかったのでテキストボックスへのコピペで済ませています。自分用なのでレイアウトも滅茶苦茶です。
ここではステージ2の冒頭を表示していますが、なんとなくストーンヘンジのレイアウトがわかりますでしょうか?
のちにAXFLAMEやBOUNDKNIGHTで大活躍するなんて思ってもみませんでしたww


・(補足)PC-6001のメモリについて(2021/8/30)

このページでは頻繁に「メモリが足りない」というキーワードが出てきます。いくら何でもそんなにメモリが足りないの?と思われるかたもいらっしゃるかもしれませんので、念のため補足しておきます。

PC-6001にはZ80(互換のNEC製)という8bitCPUが搭載されています。当時の8bitCPUの一般的な仕様として、データバスが8bit、アドレスバスが16bitでした。データバスが8bitなので8bitCPUと一般的に呼ばれていたんですね。
データバスというのはCPUがデータを同時にやり取りできる電気信号の数になります。1回のやり取りで8bitつまり0〜255までの数値をやり取り出来る性能をもっているということになります。
アドレスバスは何かというと、やり取りするメモリの場所(アドレス)を指定する信号になります。これが16bitということは一度に0〜65535までの数値を指定できます。つまり指定できるメモリのアドレスは64kbyteまでとなります。
64kbyteを超えたメモリを取り扱おうとした場合、CPUの機能だけでは出来ないんですね。
それでも当時64kbyteを超えるメモリを搭載したPCがありました。それらはCPUとは別のハードを追加してメモリを扱えるようにしています。
この仕組みは当時一般的にバンク切り替えという方式で実現していました。(MSXのVRAMのように、これとは別の方式もあります)

それではPC-6001はどうなっているのでしょうか?PC-6001には64kbyteを超えるメモリアクセスをするハードは実装されていませんでした。(厳密にはROMフォントだけバンク切り替えでアクセス出来た)
メモリは標準で、先頭16kbyteはROMとして固定されていました。このROMにはBASICやBIOS(当時はBIOSなんて呼び名が無くROMルーチンと呼ばれていました)が収録されています。ROMなので書き換えたり出来ません。
次にRAMが後ろから16kbyte割り当てられています。
真ん中の32kbyteには何も実装されておらず標準では使用できません。
ただ、当時純正で拡張ROM&RAMカートリッジという拡張カートリッジが用意されていて、この真ん中の何も割り当てられていないメモリを拡張する事が出来ました。
ただし、ROM&RAMカートリッジを増設して埋められるのは真ん中後半のRAM16kbyteだけです。ROM部分はソケットの口があるだけで何も実装されていません。自分でROMを用意してね、という仕様です。

ここまでの話をまとめると、PC-6001のメモリは最大で後半32kbyteのメモリがRAMとして使えるように見えます。
ですが、ここから更に削られます。
まずVRAMですが・・・この32kbyteのRAM領域から割り当てられます。グラフィックが扱える画面を利用した場合、ここから1画面に付き6kbyte削られます。

また、ROMルーチンを利用する場合はROMルーチンが作業領域として使っている1kbyte強も使えません(厳密には使えるメモリを探せばあるかも程度です)

ですので、最初の設計段階で自由に使える領域は0x8000から0xE000までの24kbyteになります。

TINYGRADIUSは1ステージ単位ですが、このメモリにプログラム、ステージデータ、マップ、CGデータ、音楽データ、サウンドドライバ、スタック領域が全て納まるよう作られています。(タイトル画面を削ればあと数百バイトが・・・じゅるり)

ちなみにPC-6001mk2以降ではメモリが拡張され(バンク切り替えも実装されて)64kbyteのRAMメモリからVRAMとワークを除いた56kbyteが全て使えます。TINYGRADIUSの総メモリ使用量は約48kbyteになるので余裕でオンメモリ動作します。

・当たり判定は特殊なことをしています(2021/8/30)

当たり判定について書きます。

通常当たり判定はプレイヤーと敵や弾が衝突したかどうかを判定する処理です。
これを真面目に行うと、プレイヤー×画面上に出現している敵や弾の数だけ演算する必要があります。

またプレイヤーが発射する弾の数×敵の数も当たり判定を演算しなくてはいけません。
まだ単に敵や弾だけだとPC-6001でも負荷として耐えられそうでしたが、問題はグラディウス特有のオプションの存在です。

オプションからはプレイヤーと同じ数だけの弾(ショット2発+ミサイル1発)が発射されます。オプション2個で最大9発の弾と敵を当たり判定処理しないといけません。
実際どうなるかわからないまま作ってみたら案の定オプション2個で激重になりました。

これはどうしよう・・・色々試案した結果、どうせ一般配布できないし見た目重視だから当たり判定を手抜きしよう。という事にしました。
ただ手抜きといってもどう手抜きをするかです。昔X68000で同人STG作った時には2フレームに1回判定させるとかして速度を稼いでいましたが、そもそもそんな高速なレベルでは動作していません。

思いついた手法は仮想マップを使った手法です。

実際には画面を16x24マスに区切って、敵や弾などの当たったら処理するルーチンのアドレスを書き込みます。
当たったかどうかの判定はキャラクターの座標から仮想マップの場所を計算し値を読みだします。値が0ならそこにキャラクターはいない。0以外ならキャラクターが存在するといった処理です。
ちなみに仮想マップのマス目が荒いのはメモリの節約と、荒いほうが何度も仮想マップをチェックする必要がないからです。
当然欠点として当たり判定が荒くなりゲームとしてどうなん?っていうぐらい厳しいものがあります。(AXFLAMEではこのマス目を細かくしてゲームとして成立するよう調整しています、BoundKightはアクションゲームなので厳密に演算しています。)
また、同じマス目に複数のキャラクターが重なると上書きされてすり抜けや意図しない当たり判定処理になることも多々あるので普通はやらない手法です。


・ミサイルが地面に沿って進んだり、ダッカーが歩く仕組み(2021/9/16)

地形にミサイルを沿わせて進ませたり、ダッカーが地形に沿って歩く仕組みをどう実装しているのか解説します。
地形に沿った動きをさせるために、地形とは別に透明な当たり判定専用のマップチップが地形データに仕込んであります。

地形データに仕込んである当たり判定データは、以下の4種類になります。

2  :水平
3  :右上または左下勾配
2+3:右下または左上勾配

それぞれビットで表しているので2ビットの組み合わせでマップ上に当たり判定を仕込むことができます。

当たり判定イメージ

仕込んである当たり判定がどのように使われるのかは、それぞれのオブジェクトのアルゴリズム側にプログラムしています。

アルゴリズムの手続きはこうです。
1.ミサイルは通常右下へ飛んでいきます。
2.地形に当たり判定が無い場合は右下の方向に飛ばします。
3.地形の当たり判定が2の水平なら飛ぶ方向を右方向へ変更します。また、3の右上勾配の場合は右上に飛ばします。

2+3の右下勾配はミサイルにとっては通常飛んでいく右下方向になるので判定はしません。

ミサイルのことだけを考えると、地形の当たり判定は右方向と右上方向の2種類で良いはずですが、ダッカーの歩行のために水平、右上勾配、右下勾配の3種類を用意しています。

右上または左下という表記となっているのは画面下の地面側なのか画面上の天井側なのかで意味合いが違っていきます。
画面下の地形を歩行するダッカーが右方向へ歩行していて3:右上または左下のの当たり判定を確認すると左下→右上方向に歩行させます。同じく天井側の地形を右方向へ歩行するダッカーが同じ3の当たり判定を確認した場合は左上→右下の方向へ歩行させます。

なお、地形の当たり判定を使って移動しているのはこの2種類のみで、ジャンパーは地形の影響を受けずに同じ高さをジャンプしています。これは滅多に山に重ならない事を考慮してあえて地形判定を無視しています。

また、敵の弾やモアイの吐くイオンリングも本来地形の当たり判定を考慮すべきですが、処理がとても重たくなるので無視しています。
これでは地形に隠れることが出来ないので、ゲームとしては非常にバランスの悪いものになっていて、完全に見栄え優先の処置です。