野うさぎ亭
よく誤解されているファミコンの仕様
ROMカセット内の結線でスクロール方向が縦か横に決まる
ROMカセット内の結線で決まるのは、BGのネームテーブルのミラーリング方向。
水平ミラーリングであろうが垂直ミラーリングであろうが、PPUの機能としては縦横両方向にスクロールが可能です。
ネームテーブルは、BG画面に表示するタイル(パターンデータ)を指定するPPUのメモリ領域です。 ファミコンの場合、32x30個(960byte)を1画面分として構成します。 タイルに割り当てる色情報のアトリビュート情報の64byteと合わせて1画面辺り1024byte(1Kbyte)の領域となっています。
この1画面分の領域がさらに縦横2画面サイズ、全体で4画面分の領域で構成されています。
NESDEV wikiより引用 (0,0) (256,0) (511,0) +-----------+-----------+ | | | | | | | $2000 | $2400 | | | | | | | (0,240)+-----------+-----------+(511,240) | | | | | | | $2800 | $2C00 | | | | | | | +-----------+-----------+ (0,479) (256,479) (511,479)
全体で4画面分ですのでメモリ領域は4Kbyteになりますが、ファミコン本体に内蔵されているPPU用のRAMは2Kbyteで2画面分しかありません。 この2画面分を縦並び(水平ミラーリング、横同士が同じ画面になる)にマップするか、横並び(垂直ミラーリング、縦同士が同じ画面になる)にマップするかを決めるのがミラーリングの指定です。
ミラーリングした方向にスクロールすると同じ画面が並んでいるので、見ため的にはスクロールアウトした絵がそのまますぐにスクロールインするように見えます。
このため、横方向にスクロールするゲームは垂直ミラーリング、縦方向にスクロールするゲームは水平ミラーリングが適することになります。また、縦横両方にスクロールするゲームは作る側の実装の都合に合わせてどちらでも良いことになります。(ミラーリングした方向の画面端の書き換えが見える程度の影響)
なお、ミラーリングの選択の仕組みはシンプルです。
ファミコン本体からカセット側にPPU用RAMのA10ピンに繋がるアドレス線が出ており、カセット側でこのPPU用RAMのA10ピンにPPUのA10またはA11のどちらかのアドレス線を接続するかです。
ネームテーブルとアトリビュートのメモリ領域は$2000~$2fffにマップされていますが、PPU用RAMへアクセスするアドレスがミラーリングの方向で下記の通りに変化します。
PPUのアドレス | RAMのアドレス (水平ミラーリング) |
RAMのアドレス (垂直ミラーリング) |
$2000~$23ff | $0000~$03ff | $0000~$03ff |
$2400~$27ff | $0000~$03ff | $0400~$07ff |
$2800~$2bff | $0400~$07ff | $0000~$03ff |
$2c00~$2fff | $0400~$07ff | $0400~$07ff |
1フレームで一度に書き換えられるのは32キャラ
まず、キャラが何を指す単位か曖昧です。
スプライトおよびBG面を表示した状態でCPUからPPUへのデータ転送が可能なのはVBlank期間中のみであるのは周知の通りです。 このためバッファにデータを貯めておきVBlank期間中に転送するのが効率の良い方法となります。
私の場合、転送データサイズと処理時間が比例するように設計した下記のルーチンを使用しています。(clkはCPUクロックを指す)
バッファ(VCMD_DATA)は$0100のスタック領域に配置しています。
転送のメインは.3~.4の間にある処理で、plaでバッファからデータを読み込んで$2007に書き込む処理を2回行いループします。
これで、1byte辺り10.5clkで処理します。
.2~の処理で転送処理のセットアップを行っており、転送サイズ、アドレスの増加量指定(+1/+32)、転送先アドレスの4byteのデータを42clk(10.5*4clk)以内で処理しています。
.3の手前にデータ量が奇数の場合の1byte分の転送処理があり10.5clkを超過していますが、超過分はセットアップの処理の余裕分と相殺となっています。
; PPU転送 ldx <VCMD_SIZE ; 3 lda #$00 ; 2 sta VCMD_DATA,x ; 4 tsx ; 2 stx <SAVE_SP ; 3 ldx #LOW(VCMD_DATA-1) ; 2 txs ; 2 (+18) pla ; 4 beq .5 ; 3 .2: lsr a ; 2 tax ; 2 pla ; 4 ora <PPU_REG1 ; 3 sta $2000 ; 4 pla ; 4 sta $2006 ; 4 pla ; 4 sta $2006 ; 4 bcc .3 ; 2/3 (+40/+41) pla ; 4 sta $2007 ; 4 txa ; 2 beq .4 ; 2/3 (+12/+13) .3: pla ; 4 sta $2007 ; 4 pla ; 4 sta $2007 ; 4 dex ; 2 bne .3 ; 3 (+21) .4: pla ; 4 bne .2 ; 3 .5: ldx <SAVE_SP ; 3 txs ; 2 ; lda #$00 sta <VCMD_SIZE ; 3
VBlank期間は全体で2273clkです。スプライトDMA転送にかかる514clkを引いて10.5clkで割り転送可能なデータ量を求めると次の通りとなります。
( 2273 - 514 ) / 10.5 = 167.5238095238095
VBlank割り込み時の処理の処理時間も考慮し、切の良い数値ということで160を上記PPU転送処理ルーチンが処理できる最大転送量とします。
1byteごとに転送先を指定した場合、下記の通り32か所(32byte)が1フレームで転送できる量となります。(ワーストケース)
最大転送量 160 byte / (転送先情報 4byte + 転送データ 1byte) = 32
ネームテーブル1ライン(32byte)はアドレスが連続していますので、これをひと塊として転送する場合、下記の通り4ライン(128byte)が1フレームで転送できる量となります。
最大転送量 160 byte / (転送先情報 4byte + 転送データ 32byte) = 4.444..
ネームテーブルと同時にアトリビュートの更新(転送先情報 4byte + 転送データ 8byte = 12byte)も行うことを考えると、1フレームで32x4の範囲の画面を書き換えるのが現実的な値と言えます。
スプライトとBGを表示した状態で、画面全体を書き換えるには8フレームかかる計算になります。
画面全体を書き換えるには時間がかかるのは正しい認識であるが、1フレームで1ライン分しか書き換えられないというのは誤った認識となります。
スプライトのサイズは16x16で横方向に5個以上並ぶとちらつく
スプライトのサイズは、8x8 または 8x16です。
横に並べることができるスプライトの個数は8個までで、9個目以降は表示されません。
ハードウェアでスプライトをちらつかせる機能は持っていません。ソフトウェアで毎フレームスプライトの優先順位を変えることで消えるスプライトを変更しています。結果、見ため的にちらついて見えます。
色とパレット
仕様を正確に記述すると次の通り。
- スプライト用の(3色+透明)の4組のパレット
- BG用の(3色+透明)の4組のパレット
- 1色の背景色
- 同時発色数は、3×4+3×4+1=25色
BGは3色×4パレット+共通の1色と記述されることが多いですが、スプライトをBGの後に表示する場合、BGパターンのピクセル値0の部分に絵が表示されるため、BGパターンのピクセル値0の部分は透明と考えるのが適切であると思います。
したがって、スプライト面とBG面の背後に背景色を表示する面が存在し、スプライトとBGが共に透明であるドットは背景色の面が透けて見えていると考えるのが妥当であると思います。
/| /| /| /| / | / | / | / | / | / | / | / | | | | | | | | | | | | | | | | | →画面手前 | | | | | | | | | / | / | / | / | / | / | / | / |/ |/ |/ |/ 背景色 SP後面 BG面 SP前面