TOP > プログラミング関係解説&調査 > ラスタースクロール

ラスタースクロール

当ページの内容は、ある程度のプログラミングの知識を必要とする。
まず、プログラミング関係解説&調査をひととおり読んでいただきたい。

ラスタースクロールは、カラーマスやマスク処理などと同じく、HDMA転送を使ったウィンドウ処理のひとつである。
名前の通りスクロール処理も絡むため、スクロールを先にお読みいただきたい。

ラスタースクロールは画面が波打つように左右に揺れる処理のことで(縦方向にも揺らせることは可能)、近未来編の序盤、物質転送装置に乗った時や、SF編のラストバトル前で使われている。
仕組みとしては、画面を描画する時、1スキャンライン毎にHDMA転送で描画開始位置(スクロール位置)を変化させる、というものである。
スーパーファミコンは横方向に1スキャンラインずつ描画していく都合で、左右に揺らす処理は縦方向に揺らす処理よりも簡単に実装できる、らしい。
うねうねしている形はいわゆる「サイン (sine) 波形」とか「正弦波形」と呼ばれるものである。
高校数学で習う、三角関数のy = sin xのグラフの図とか、周波数の正弦波の形である。
正弦波 - Wikipedia

※このページを読んでいる方は多分、ある程度の数学の知識があると思うので、三角関数とは何かという根本的な説明は省く。
三角関数 - Wikipedia

筆者が表計算ソフトで適当に作ってみたy = sin xのグラフが以下で、

ラスタースクロールは元画像(背景グラフィック)がグラフのように開始ピクセルが変わるので全体が歪む感じである。
本作の処理を見る限り、実際には、y = a sin (bx) のように定数を設定して、うねうね(振れ幅)の高さa、うねうねの周期bを徐々に変化させる処理を行っている。

画像の横幅自体は変化しないが、書き始める位置がわずかずつ変わるからうねっているように見える。
左右に揺れる都合で、本来表示する横幅256ピクセル分の外側(右または左)が映ってしまうが、本作の場合、外側をマスクし真っ黒にして外側を表示しないようにしている。
戦闘開始時の三角形がぐるぐる回って暗くなる時やアイリスイン・アイリスアウト時の、ウィンドウ処理で外側をマスクして真っ暗にする処理と似たようなものである。

本作でのラスタースクロールはいずれもフィールド上で、背景のBG1, BG2に対してのみ行っている。
セリフを表示するBG3には使用しない。
また、OBJ(スプライト)にも使用しない。背景のスクロール量を変化させるという処理の都合で、スプライトにはラスタースクロールがかからない。

大まかな流れを見てみると、ラスタースクロール中の画面1枚分の表示(1フレーム分)は以下のように処理している。

  1. BG1とBG2をウィンドウ2に表示、ウィンドウ外側マスク
  2. BG1とBG2のスキャンライン全ての水平スクロール量を、三角関数表から計算し、$7F:4000$7F:41BFにコピー
  3. ウィンドウ2の[$00:210D](BG1水平スクロール)と[$00:210F]BG2水平スクロール量224スキャンライン分$7F:4000$7F:41BFをHDMA転送
  4. ウィンドウ1(スプライトとBG3)とウィンドウ2(ラスタースクロールしたBG1とBG2)を合成してゲーム画面完成

以下、SF編ラストバトル前のラスタースクロールのサブルーチンを紹介する。仕組み自体は近未来編でのラスタースクロールと変わらないはず。
HDMA転送の設定は以下になる。

$C0/D721 LDA $20    [$00:0020]   ;Aに[$00:0020]をロード
$C0/D723 BIT #$F3                ;Aと$F3で論理積(ステータスフラグ変更のみ)
$C0/D725 BEQ $6B    [$D792]      ;ゼロフラグが立っているとき[$D792]分岐
$C0/D727 LDA $0114  [$00:0114]   ;Aに[$00:0114]をロード
$C0/D72A BNE $1A    [$D746]      ;ゼロフラグが立っていないとき[$D746]分岐
$C0/D72C LDA #$41                ;Aに$41をロード
$C0/D72E STA $4330  [$00:4330]   ;Aを[$00:4330]に書き込み
$C0/D731 LDA #$28                ;Aに$28をロード
$C0/D733 STA $4331  [$00:4331]   ;Aを[$00:4331]に書き込み
$C0/D736 LDA #$7F                ;Aに$7Fをロード
$C0/D738 STA $4334  [$00:4334]   ;Aを[$00:4334]に書き込み
$C0/D73B STA $4337  [$00:4337]   ;Aを[$00:4337]に書き込み
;
;[$00:4330]	GDMA / HDMA設定レジスタ	$41 間接テーブルモード 2レジスタ1書き込み 
;[$00:4331]	転送先指定	$28=WH2 ウィンドウ2左座標設定
;[$00:4333][$00:4332]	データ元アドレス(開始位置)	→$C0/DA2Dで、$3C30か$3C20セット
;[$00:4334]	データ元バンク	$7F
;[$00:4337]	HDMA間接アドレスバンクバイト	$7F
;
$C0/D73E LDA $014F  [$00:014F]   ;Aに[$00:014F]をロード
$C0/D741 ORA #$08                ;Aと$08で論理和
$C0/D743 STA $014F  [$00:014F]   ;Aを[$00:014F]に書き込み
$C0/D746 LDA $20    [$00:0020]   ;Aに[$00:0020]をロード
$C0/D748 BIT #$40                ;Aと$40で論理積(ステータスフラグ変更のみ)
$C0/D74A BEQ $20    [$D76C]      ;ゼロフラグが立っているとき[$D76C]分岐
$C0/D74C LDA #$42                ;Aに$42をロード
$C0/D74E STA $4340  [$00:4340]   ;Aを[$00:4340]に書き込み
$C0/D751 LDA #$0D                ;Aに$0Dをロード
$C0/D753 STA $4341  [$00:4341]   ;Aを[$00:4341]に書き込み
$C0/D756 LDX #$3C60              ;Xに$3C60をロード
$C0/D759 STX $4342  [$00:4342]   ;Xを[$00:4343][$00:4342]に書き込み
$C0/D75C LDA #$7F                ;Aに$7Fをロード
$C0/D75E STA $4344  [$00:4344]   ;Aを[$00:4344]に書き込み
$C0/D761 STA $4347  [$00:4347]   ;Aを[$00:4347]に書き込み
;
;[$00:4340]	GDMA / HDMA設定レジスタ	$42 間接テーブルモード 1レジスタ2書き込み
;[$00:4341]	転送先指定	$0D=BG1HOFS BG1水平スクロール量
;[$00:4343][$00:4342]	データ元アドレス(開始位置)	$3C60
;[$00:4344]	データ元バンク	$7F
;[$00:4347]	HDMA間接アドレスバンクバイト	$7F
;
$C0/D764 LDA $014F  [$00:014F]   ;Aに[$00:014F]をロード
$C0/D767 ORA #$10                ;Aと$10で論理和
$C0/D769 STA $014F  [$00:014F]   ;Aを[$00:014F]に書き込み
$C0/D76C LDA $20    [$00:0020]   ;Aに[$00:0020]をロード
$C0/D76E BIT #$80                ;Aと$80で論理積(ステータスフラグ変更のみ)
$C0/D770 BEQ $20    [$D792]      ;ゼロフラグが立っているとき[$D792]分岐
$C0/D772 LDA #$42                ;Aに$42をロード
$C0/D774 STA $4350  [$00:4350]   ;Aを[$00:4350]に書き込み
$C0/D777 LDA #$0F                ;Aに$0Fをロード
$C0/D779 STA $4351  [$00:4351]   ;Aを[$00:4351]に書き込み
$C0/D77C LDX #$3C60              ;Xに$3C60をロード
$C0/D77F STX $4352  [$00:4352]   ;Xを[$00:4352]に書き込み
$C0/D782 LDA #$7F                ;Aに$7Fをロード
$C0/D784 STA $4354  [$00:4354]   ;Aを[$00:4354]に書き込み
$C0/D787 STA $4357  [$00:4357]   ;Aを[$00:4357]に書き込み
;
;[$00:4350]	GDMA / HDMA設定レジスタ	$42 間接テーブルモード 1レジスタ2書き込み
;[$00:4351]	転送先指定	$0F=BG2HOFS BG2水平スクロール量
;[$00:4353][$00:4352]	データ元アドレス(開始位置)	$3C60
;[$00:4354]	データ元バンク	$7F
;[$00:4357]	HDMA間接アドレスバンクバイト	$7F
;
$C0/D78A LDA $014F  [$00:014F]   ;Aに[$00:014F]をロード
$C0/D78D ORA #$20                ;Aと$20で論理和
$C0/D78F STA $014F  [$00:014F]   ;Aを[$00:014F]に書き込み
$C0/D792 LDA $0156  [$00:0156]   ;Aに[$00:0156]をロード
$C0/D795 BIT #$F3                ;Aと$F3で論理積(ステータスフラグ変更のみ)
$C0/D797 BEQ $2B    [$D7C4]      ;ゼロフラグが立っているとき[$D7C4]分岐
$C0/D7C4 STZ $30    [$00:0030]   ;[$00:0030]に$00を書き込み
$C0/D7C6 STZ $31    [$00:0031]   ;[$00:0031]に$00を書き込み
$C0/D7C8 LDA $0114  [$00:0114]   ;Aに[$00:0114]をロード
$C0/D7CB BNE $28    [$D7F5]      ;ゼロフラグが立っていないとき[$D7F5]分岐
$C0/D7CD LDA $0156  [$00:0156]   ;Aに[$00:0156]をロード
$C0/D7D0 BEQ $11    [$D7E3]      ;ゼロフラグが立っているとき[$D7E3]分岐
$C0/D7E3 LDA $20    [$00:0020]   ;Aに[$00:0020]をロード
$C0/D7E5 BEQ $0E    [$D7F5]      ;ゼロフラグが立っているとき[$D7F5]分岐
$C0/D7E7 LDA $0145  [$00:0145]   ;Aに[$00:0145]をロード
$C0/D7EA AND #$33                ;Aと$33で論理積
$C0/D7EC ORA #$CC                ;Aと$CCで論理和
$C0/D7EE STA $0145  [$00:0145]   ;Aを[$00:0145]に書き込み
$C0/D7F1 LDA #$03                ;Aに$03をロード
$C0/D7F3 STA $31    [$00:0031]   ;Aを[$00:0031]に書き込み
$C0/D7F5 LDA $0140  [$00:0140]   ;Aに[$00:0140]をロード
$C0/D7F8 ORA #$04                ;Aと$04で論理和
$C0/D7FA AND $30    [$00:0030]   ;Aと[$00:0030]で論理積
$C0/D7FC EOR #$FF                ;Aと$FFで排他的論理和XOR
$C0/D7FE AND $0142  [$00:0142]   ;Aと[$00:0142]で論理積
$C0/D801 STA $0142  [$00:0142]   ;Aを[$00:0142]に書き込み
$C0/D804 LDA $0141  [$00:0141]   ;Aに[$00:0141]をロード
$C0/D807 AND $30    [$00:0030]   ;Aと[$00:0030]で論理積
$C0/D809 EOR #$FF                ;Aと$FFで排他的論理和XOR
$C0/D80B AND $0143  [$00:0143]   ;Aと[$00:0143]で論理積
$C0/D80E STA $0143  [$00:0143]   ;Aを[$00:0143]に書き込み
$C0/D811 LDA $0140  [$00:0140]   ;Aに[$00:0140]をロード
$C0/D814 ORA #$04                ;Aと$04で論理和
$C0/D816 AND $31    [$00:0031]   ;Aと[$00:0031]で論理積
$C0/D818 ORA $0142  [$00:0142]   ;Aと[$00:0142]で論理和
$C0/D81B STA $0142  [$00:0142]   ;Aを[$00:0142]に書き込み
$C0/D81E LDA $0141  [$00:0141]   ;Aに[$00:0141]をロード
$C0/D821 AND $31    [$00:0031]   ;Aと[$00:0031]で論理積
$C0/D823 ORA $0143  [$00:0143]   ;Aと[$00:0143]で論理和
$C0/D826 STA $0143  [$00:0143]   ;Aを[$00:0143]に書き込み
$C0/D829 RTS                     ;サブルーチン戻り

HDMAチャネル3, 4, 5が設定されており、どれもラスタースクロールで使われる。
順に見ていく。

HDMAチャネル3
アドレス内容詳細
[$00:4330]GDMA / HDMA設定レジスタ$41間接テーブルモード 2レジスタ1書き込み
[$00:4331]転送先指定$28=WH2ウィンドウ2左座標[$00:2128]設定
[$00:4333][$00:4332]データ元アドレス(開始位置)-$C0/DA2Dで、$3C30$3C20セット
[$00:4334]データ元バンク$7F 
[$00:4337]HDMA間接アドレスバンクバイト$7F 

[$00:4330]$41が書き込まれているので、HDMA設定は間接テーブルモード、2レジスタ1書き込みである。
先に述べたが、ラスタースクロールするBG1とBG2は、ウィンドウ2に表示する。
本作では必要がない限り、各BGとスプライトはウィンドウ1に表示して重ねているようだが、ラスタースクロールではBG1とBG2だけウィンドウ2に表示させて処理をしている。
[$00:4331]の転送先は[$00:2128](ウィンドウ2左座標)だが、2レジスタ1書き込みであることから、実際には[$00:2128][$00:2129]の値の転送である。
つまり、ウィンドウ2左座標[$00:2128]・ウィンドウ2右座標[$00:2129]に同時に値を送っている。
ウィンドウ2の内側・外側位置を決定するためのHDMA転送であることがわかる。

なお、通常のフィールドではウィンドウ2を使っていない。マップ切り替え時に、ウィンドウ2左座標[$00:2128]もウィンドウ2右座標[$00:2129]$00が入っており、ウィンドウ2を使うとしたら、ウィンドウ2全体が「ウィンドウの外側」扱いである。

HDMA設定は間接テーブルモードだが、間接テーブルモードでのアドレスの読み方はウィンドウ効果にあるので参照していただきたい。
HDMAテーブルが書き込まれているアドレスなのだが、[$00:4337]からバンクは$7Fとわかるが、上処理にはアドレスの[$00:4333][$00:4332]が指定されていない。
というのは、後の処理でわかる通り、[$00:4333][$00:4332]には状況により$3C30$3C20が入るのである。
HDMAテーブルは$7F:3C20からか、$7F:3C30からのどちらかである。

HDMAチャネル4
アドレス内容詳細
[$00:4340]GDMA / HDMA設定レジスタ$42間接テーブルモード 1レジスタ2書き込み
[$00:4341]転送先指定$0D=BG1HOFSBG1水平スクロール量
[$00:4343][$00:4342]データ元アドレス(開始位置)$3C60 
[$00:4344]データ元バンク$7F 
[$00:4347]HDMA間接アドレスバンクバイト$7F 

HDMAチャネル4はBG1水平スクロール量[$00:210D]へのHDMA転送であるが、スクロール量は2度書き込みして10ビットの値を設定しなければならない。
このため、[$00:4340]で1レジスタ2書き込みの設定がされている。
間接テーブルモードで、バンクは$7F、アドレスが$7F:3C60と指定されているので、$7F:3C60~をみてみる。

$7F:3C60
F0 00 40 F0 E0 40 00 FF 00 00 FF 00 55 55 55 55

3バイトで区切ると、

$7F:4000から$F0(240)ピクセル分
$7F:40E0から$F0(240)ピクセル分

の転送処理だということがわかる。

HDMAチャネル5
アドレス内容詳細
[$00:4350]GDMA / HDMA設定レジスタ$42間接テーブルモード 1レジスタ2書き込み
[$00:4351]転送先指定$0F=BG2HOFSBG2水平スクロール量
[$00:4353][$00:4352]データ元アドレス(開始位置)$3C60 
[$00:4354]データ元バンク$7F 
[$00:4357]HDMA間接アドレスバンクバイト$7F 

HDMAチャネル4と設定が異なるのは、[$00:4351]の転送先がBG2水平スクロール量[$00:210F]に変わることだけ。
転送するデータも、

$7F:4000から$F0(240)ピクセル分
$7F:40E0から$F0(240)ピクセル分

である。
これは当然といえば当然で、BG1とBG2はいずれも同じ位置に表示される背景であり、多重スクロールのマップでない限り、スクロール量は同一でなければならない。
サブルーチンを見る限り、「多重スクロールのマップではない」という前提でのラスタースクロール設定のようである。
本作でラスタースクロールが使われる、近未来編の壽商会の地下1階や、SF編のリフレッシュルームは多重スクロールのマップではないので、このサブルーチンで特に問題は起こらない。
ゲーム制作時点で「多重スクロールのマップではラスタースクロールはなし」と決めてから演出を考えていったのかもしれない、ということがサブルーチンから読み取れる。

以上から、ラスタースクロール時、

$7F:4000から$F0(240)ピクセル分
$7F:40E0から$F0(240)ピクセル分

は、$7F:4000$7F:41BFにあたり、2バイトずつ、BG1水平スクロール量[$00:210D]・BG2水平スクロール量[$00:210F]に書き込まれていくことがわかる。
$7F:4000$7F:41BFは1フレーム毎に更新されているが、では$7F:4000$7F:41BFはどのような値になるのか。
辿ってみると、

$C0/D6B3 PHX                     ;Xレジスタをスタックにプッシュ
$C0/D6B4 PHY                     ;Yレジスタをスタックへプッシュ
$C0/D6B5 PHB                     ;DBレジスタをスタックにプッシュ
$C0/D6B6 LDX #$BC96              ;Xに$BC96をロード
$C0/D6B9 LDY #$5402              ;Yに$5402をロード
$C0/D6BC LDA #$0023              ;Aに$0023をロード
$C0/D6BF MVN C0 7F               ;データ転送
;A:転送データサイズ-1 $0023
;X:転送元アドレス $BC96
;Y:転送先アドレス $5402
;オペランド:転送元と転送先バンク C0 -> 7F
;$C0:BC96 -> $7F:5402 +$0023バイト
$C0/D6BF MVN C0 7F               ;データ転送
$C0/D6C2 STZ $5400  [$7F:5400]   ;[$7F:5401][$7F:5400]に$00を書き込み
$C0/D6C5 STZ $5448  [$7F:5448]   ;[$7F:5449][$7F:5448]に$00を書き込み
$C0/D6C8 LDX #$5402              ;Xに$5402をロード
$C0/D6CB LDY #$5446              ;Yに$5446をロード
$C0/D6CE LDA #$0011              ;Aに$0011をロード
$C0/D6D1 STA $20    [$00:0020]   ;Aを[$00:0021][$00:0020]に書き込み
$C0/D6D3 LDA $0000,x[$7F:5402]   ;Aに[$7F:5403][$7F:5402]をロード
$C0/D6D6 STA $32    [$00:0032]   ;Aを[$00:0033][$00:0032]に書き込み
$C0/D6D8 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/D6DA PHB                     ;DBレジスタをスタックにプッシュ
$C0/D6DB LDA #$00                ;Aに$00をロード
$C0/D6DD PHA                     ;Aレジスタをスタックにプッシュ
$C0/D6DE PLB                     ;DBレジスタにスタックからプル
$C0/D6DF JSR $BA16  [$C0:BA16]   ;[$C0:BA16]へジャンプ

まず、この処理で、$C0:BC96$C0:BCB9$7F:5402$7F:5425に書き込まれる($24バイト)。

$C0:BC96$C0:BCB9
16 00 2C 00 42 00 58 00 6C 00 80 00 93 00 A5 00
B5 00 C4 00 D2 00 DE 00 E8 00 F1 00 F7 00 FC 00
FF 00 00 01

一見、何だかよくわからない文字列であるが、これは正接(sin)の三角関数表である。
2バイト値で、$0016, $002C, $0042,……$00FF, $0100となる。
[$7F:5401][$7F:5400]$0000だから、初期値に$0000を加えてグラフを書いてみると以下のようになる。

サイン波形の半分っぽい形になった。
このままだと最大値が$0100256)なので、画面の横幅のピクセル数と同一である。
いきなりこの量だけ画面を揺らしたりはせず、上の値に一定の数値を掛け算することで画面の揺れ量を調整する。
例えば0.1倍にすれば最大部分で25ピクセルだけ画面をズラすことができる、という理屈である。

以上から、$C0:BC96$C0:BCB9は三角関数表(sin x)であるということがわかった。
ちなみに、実際には$C0:BC94, $C0:BC95$00が入っているため、$C0:BC94$C0:BCB90~90度までの三角関数表(sin x)である。
また、$C0:BCBA$C0:BCDCには$C0:BC96$C0:BCB9を逆転した値が入っているから、$C0:BC96$C0:BCDCは0~180度(ラジアンで0~π)、5度区切りで180度分の三角関数表(sin x)ということになる。

余談 三角関数表について

$C0:BC96$C0:BCB9が正接(sin)の三角関数表なのだが、実際の三角関数の答えときちんと一致するものなのだろうか。
という話を記しておく。
$C0:BC96$C0:BCB9は、0~90度(ラジアンなら0~π/2)分の正接(sin)の三角関数表になるのだが、1度ずつの値ではなく、18等分している。つまり5度ずつの値である。
sin00°, sin05°, ……という値は関数電卓や表計算ソフトなどで求められるが、ここでは古風に、実際の三角関数表で見てみよう。検索すれば出てくる。

三角関数表(PDFファイル)

また、最小値を$00、最大値を$FF(10進数256)としている。三角関数の値は直角三角形の角度から「対辺/斜辺」のように割り算して割合を求めるから、0~1の値であり、$C0:BC96$C0:BCB9と合わせるのなら256倍しなければならない。
5度刻みで三角関数表のsinの値を転載させていただくと以下のようになる。
これに256を掛けたのが3列目の値であり(小数点以下第2位四捨五入)、4列目は$C0:BC96$C0:BCB9を10進数にした値。

角度sin×256$C0:BC96
00°0.000000
05°0.087222.322
10°0.173644.444
15°0.258866.366
20°0.342087.688
25°0.4226108.2108
30°0.5000128.0128
35°0.5736146.8147
40°0.6428164.6165
45°0.7071181.0181
50°0.7660196.1196
55°0.8192209.7210
60°0.8660221.7222
65°0.9063232.0232
70°0.9397240.6241
75°0.9659247.3247
80°0.9848252.1252
85°0.9962255.0255
90°1.0000256256

この通り、三角関数表の値を小数点以下で四捨五入すると$C0:BC96$C0:BCB9の値とぴったり一致しており、きちんと三角関数で計算された値だったことがはっきりした。

なお、現代のプログラミング言語であれば、標準ライブラリで数学関数を呼び出したり、組み込み関数(例えばJavaScriptならMath.sin(x))で簡単に三角関数が計算できるから、わざわざ三角関数表をプログラムの中に書き込んでおく必要はない。
だが、スーパーファミコンのアセンブリ言語(65C816)は、三角関数どころか掛け算や割り算の命令もない(乗算・除算のやり方はこれまでにも説明しているので省く)。
よって、三角関数で滑らかな曲線の計算をするといった場合は、三角関数表を埋め込んでおく必要があったのである。
(何かしらの物体を投げて落下する、という場合は放物線、y = a(x - p)2 + qで座標計算できるから、三角関数のように表は必要ない……と思う)

ということで$7F:5400$7F:5425はサイン波形の半分の値が入ることになる。残りの半分は形を左右反転すれば良いし、波形が幾つも続くなら$7F:5400$7F:5425を元にコピーしていけば良い。
結果、$7F:5400$7F:548Fにサイン波形の元となる90バイトのデータが書き込まれ(処理省略)、

$C0/D65E LDA $00,x  [$00:0250]   ;Aに[$00:0250]をロード
$C0/D660 STA $20    [$00:0020]   ;Aを[$00:0020]に書き込み
$C0/D662 LDA $08,x  [$00:0258]   ;Aに[$00:0258]をロード
$C0/D664 STA $22    [$00:0022]   ;Aを[$00:0022]に書き込み
$C0/D666 LDA $3B    [$00:003B]   ;Aに[$00:003B]をロード
;ループ処理
$C0/D668 AND #$00FF              ;Aと$00FFで論理積
$C0/D66B CMP #$0002              ;Aと$0002を減算比較(ステータスレジスタ変更のみ)
$C0/D66E BNE $05    [$D675]      ;ゼロフラグが立っていないとき[$D675]分岐
$C0/D675 LDA $0A3A  [$00:0A3A]   ;Aに[$00:0A3A]をロード
$C0/D678 LSR A                   ;Aを論理右シフト (/2)
$C0/D679 LSR A                   ;Aを論理右シフト (/2)
$C0/D67A LSR A                   ;Aを論理右シフト (/2)
$C0/D67B LSR A                   ;Aを論理右シフト (/2)
$C0/D67C STA $24    [$00:0024]   ;Aを[$00:0024]に書き込み
$C0/D67E SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/D680 PHB                     ;DBレジスタをスタックにプッシュ
$C0/D681 LDA #$7F                ;Aに$7Fをロード
$C0/D683 PHA                     ;Aレジスタをスタックにプッシュ
$C0/D684 PLB                     ;DBレジスタにスタックからプル
$C0/D685 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/D687 LDA #$00E0              ;Aに$00E0をロード
$C0/D68A STA $26    [$00:0026]   ;Aを[$00:0026]に書き込み
$C0/D68C LDA $21    [$00:0021]   ;Aに[$00:0021]をロード
$C0/D68E AND #$00FF              ;Aと$00FFで論理積
$C0/D691 ASL A                   ;Aを算術左シフト *2
$C0/D692 TAX                     ;Aの値をXレジスタに転送
$C0/D693 LDA $5400,x[$7F:5400]   ;Aに[$7F:5401][$7F:5400]をロード
$C0/D696 CLC                     ;キャリーフラグクリア
$C0/D697 ADC $24    [$00:0024]   ;A + [$00:0024]
$C0/D699 STA $0000,y[$7F:4000]   ;Aを[$7F:4001][$7F:4000]~[$7F:41BF][$7F:41BE]に書き込み
$C0/D69C LDA $20    [$00:0020]   ;Aに[$00:0020]をロード
$C0/D69E CLC                     ;キャリーフラグクリア
$C0/D69F ADC $22    [$00:0022]   ;A + [$00:0022]
$C0/D6A1 CMP #$4800              ;Aと$4800を減算比較(ステータスレジスタ変更のみ)
$C0/D6A4 BCC $03    [$D6A9]      ;キャリーフラグが立っていないとき[$D6A9]分岐
$C0/D6A6 SBC #$4800              ;A - $4800
$C0/D6A9 STA $20    [$00:0020]   ;Aを[$00:0020]に書き込み
$C0/D6AB INY                     ;Yをインクリメント +1
$C0/D6AC INY                     ;Yをインクリメント +1
$C0/D6AD DEC $26    [$00:0026]   ;[$00:0026]をデクリメント -1
$C0/D6AF BNE $DB    [$D68C]      ;ゼロフラグが立っていないとき[$D68C]分岐
;ループ処理ここまで
$C0/D6B1 PLB                     ;DBレジスタにスタックからプル
$C0/D6B2 RTS                     ;サブルーチン戻り

$7F:5400$7F:548Fからさらに$7F:4000$7F:41BEにコピーされ、$7F:4000$7F:41BEがHDMA転送に使われることになるようである。
$C0/D6B3$C0/D6DFの処理だけでは毎回三角関数表をコピーするだけだから、同じ形状のサイン波形にしかならないが、$C0/D404$C0/D485$C0/D4F4$C0/D54E$C0/D54F$C0/D5ADあたりの処理で係数を毎フレーム計算し直しているようである。詳細省略。

$C0/D9DC LDA $0154  [$00:0154]   ;Aに[$00:0154]をロード
$C0/D9DF ORA $0156  [$00:0156]   ;Aと[$00:0156]で論理和
$C0/D9E2 BNE $03    [$D9E7]      ;ゼロフラグが立っていないとき[$D9E7]分岐
$C0/D9E7 STA $30    [$00:0030]   ;Aを[$00:0030]に書き込み
$C0/D9E9 LDA $0142  [$00:0142]   ;Aに[$00:0142](03 0000 0011)をロード
$C0/D9EC STA $212E  [$00:212E]   ;Aを[$00:212E]に書き込み メインスクリーンウインドウマスク BG1/BG2ウインドウマスク
$C0/D9EF LDA $0143  [$00:0143]   ;Aに[$00:0143](00)をロード
$C0/D9F2 STA $212F  [$00:212F]   ;Aを[$00:212F]に書き込み サブスクリーンウインドウマスク
$C0/D9F5 LDA $0145  [$00:0145]   ;Aに[$00:0145](CC 1100 1100)をロード
$C0/D9F8 STA $2123  [$00:2123]   ;Aを[$00:2123]に書き込み BG1,BG2ウィンドウマスク設定
$C0/D9FB LDA $0146  [$00:0146]   ;Aに[$00:0146](00)をロード
$C0/D9FE STA $2124  [$00:2124]   ;Aを[$00:2124]に書き込み BG3,BG4ウィンドウマスク設定
$C0/DA01 LDA $0147  [$00:0147]   ;Aに[$00:0147](00)をロード
$C0/DA04 STA $2125  [$00:2125]   ;Aを[$00:2125]に書き込み スプライトウィンドウマスク設定
$C0/DA07 LDX $01FE  [$00:01FE]   ;Xに[$00:01FF][$00:01FE](FF00)をロード
$C0/DA0A STX $2128  [$00:2128]   ;Xを[$00:2129][$00:2128]に書き込み ウィンドウ2右・左座標設定
$C0/DA0D LDA $0148  [$00:0148]   ;Aに[$00:0148](00)をロード
$C0/DA10 STA $212A  [$00:212A]   ;Aを[$00:212A]に書き込み BGウインドウマスクロジック設定
$C0/DA13 LDA $0149  [$00:0149]   ;Aに[$00:0149](00)をロード
$C0/DA16 STA $212B  [$00:212B]   ;Aを[$00:212B]に書き込み スプライトウインドウマスクロジック設定
$C0/DA19 LDA $014F  [$00:014F]   ;Aに[$00:014F]をロード
$C0/DA1C BIT #$08                ;Aと$08で論理積(ステータスフラグ変更のみ)
$C0/DA1E BEQ $10    [$DA30]      ;ゼロフラグが立っているとき[$DA30]分岐
$C0/DA20 LDA $01FB  [$00:01FB]   ;Aに[$00:01FB]をロード
$C0/DA23 BNE $05    [$DA2A]      ;ゼロフラグが立っていないとき[$DA2A]分岐
;ゼロフラグON
$C0/DA25 LDX #$3C20              ;Xに$3C20をロード
$C0/DA28 BRA $03    [$DA2D]      ;フラグにかかわりなく常に分岐[$DA2D]
;ゼロフラグOFF
$C0/DA2A LDX #$3C30              ;Xに$3C30をロード
$C0/DA2D STX $4332  [$00:4332]   ;Xを[$00:4333][$00:4332]に書き込み
$C0/DA30 LDA $014F  [$00:014F]   ;Aに[$00:014F]をロード
$C0/DA33 BIT #$10                ;Aと$10で論理積(ステータスフラグ変更のみ)
$C0/DA35 BEQ $25    [$DA5C]      ;ゼロフラグが立っているとき[$DA5C]分岐
$C0/DA37 LDA $01FB  [$00:01FB]   ;Aに[$00:01FB]をロード
$C0/DA3A BNE $05    [$DA41]      ;ゼロフラグが立っていないとき[$DA41]分岐
;ゼロフラグON
$C0/DA3C LDX #$4000              ;Xに$4000をロード
$C0/DA3F BRA $03    [$DA44]      ;フラグにかかわりなく常に分岐[$DA44]
;ゼロフラグOFF
$C0/DA41 LDX #$4380              ;Xに$4380をロード
$C0/DA44 PHB                     ;DBレジスタをスタックにプッシュ
$C0/DA45 LDA #$7F                ;Aに$7Fをロード
$C0/DA47 PHA                     ;Aレジスタをスタックにプッシュ
$C0/DA48 PLB                     ;DBレジスタにスタックからプル
$C0/DA49 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/DA4B TXA                     ;Xレジスタの値をAレジスタに転送
$C0/DA4C LDX #$3C60              ;Xに$3C60をロード
$C0/DA4F STA $0001,x[$7F:3C61]   ;Aを[$7F:3C62][$7F:3C61]に書き込み
$C0/DA52 CLC                     ;キャリーフラグクリア
$C0/DA53 ADC #$00E0              ;A + $00E0
$C0/DA56 STA $0004,x[$7F:3C64]   ;Aを[$7F:3C65][$7F:3C64]に書き込み
$C0/DA59 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/DA5B PLB                     ;DBレジスタにスタックからプル
$C0/DA5C LDA $014F  [$00:014F]   ;Aに[$00:014F](38 = 0011 1000)をロード 
$C0/DA5F STA $420C  [$00:420C]   ;Aを[$00:420C]に書き込み HDMAチャネル3,4,5を実行
$C0/DA62 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/DA64 LDA $0A38  [$00:0A38]   ;Aに[$00:0A39][$00:0A38]をロード
$C0/DA67 LSR A                   ;Aを論理右シフト (/2)
$C0/DA68 LSR A                   ;Aを論理右シフト (/2)
$C0/DA69 LSR A                   ;Aを論理右シフト (/2)
$C0/DA6A LSR A                   ;Aを論理右シフト (/2)
$C0/DA6B CLC                     ;キャリーフラグクリア
$C0/DA6C ADC $0158  [$00:0158]   ;A + [$00:0159][$00:0158]
$C0/DA6F STA $30    [$00:0030]   ;Aを[$00:0031][$00:0030]に書き込み
$C0/DA71 LDA $0A3C  [$00:0A3C]   ;Aに[$00:0A3D][$00:0A3C]をロード
$C0/DA74 LSR A                   ;Aを論理右シフト (/2)
$C0/DA75 LSR A                   ;Aを論理右シフト (/2)
$C0/DA76 LSR A                   ;Aを論理右シフト (/2)
$C0/DA77 LSR A                   ;Aを論理右シフト (/2)
$C0/DA78 CLC                     ;キャリーフラグクリア
$C0/DA79 ADC $015C  [$00:015C]   ;A + [$00:015D][$00:015C]
$C0/DA7C DEC A                   ;Aをデクリメント -1
$C0/DA7D STA $32    [$00:0032]   ;Aを[$00:0033][$00:0032]に書き込み
$C0/DA7F LDA $0A3A  [$00:0A3A]   ;Aに[$00:0A3B][$00:0A3A]をロード
$C0/DA82 LSR A                   ;Aを論理右シフト (/2)
$C0/DA83 LSR A                   ;Aを論理右シフト (/2)
$C0/DA84 LSR A                   ;Aを論理右シフト (/2)
$C0/DA85 LSR A                   ;Aを論理右シフト (/2)
$C0/DA86 CLC                     ;キャリーフラグクリア
$C0/DA87 ADC $015A  [$00:015A]   ;A + [$00:015B][$00:015A]
$C0/DA8A STA $34    [$00:0034]   ;Aを[$00:0035][$00:0034]に書き込み
$C0/DA8C LDA $0A3E  [$00:0A3E]   ;Aに[$00:0A3F][$00:0A3E]をロード
$C0/DA8F LSR A                   ;Aを論理右シフト (/2)
$C0/DA90 LSR A                   ;Aを論理右シフト (/2)
$C0/DA91 LSR A                   ;Aを論理右シフト (/2)
$C0/DA92 LSR A                   ;Aを論理右シフト (/2)
$C0/DA93 CLC                     ;キャリーフラグクリア
$C0/DA94 ADC $015E  [$00:015E]   ;A + [$00:015F][$00:015E]
$C0/DA97 DEC A                   ;Aをデクリメント -1
$C0/DA98 STA $36    [$00:0036]   ;Aを[$00:0037][$00:0036]に書き込み
$C0/DA9A SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/DA9C LDA $30    [$00:0030]   ;Aに[$00:0030]をロード
$C0/DA9E STA $210D  [$00:210D]   ;Aを[$00:210D]に書き込み
$C0/DAA1 LDA $31    [$00:0031]   ;Aに[$00:0031]をロード
$C0/DAA3 STA $210D  [$00:210D]   ;Aを[$00:210D]に書き込み
$C0/DAA6 LDA $32    [$00:0032]   ;Aに[$00:0032]をロード
$C0/DAA8 STA $210E  [$00:210E]   ;Aを[$00:210E]に書き込み
$C0/DAAB LDA $33    [$00:0033]   ;Aに[$00:0033]をロード
$C0/DAAD STA $210E  [$00:210E]   ;Aを[$00:210E]に書き込み
$C0/DAB0 LDA $34    [$00:0034]   ;Aに[$00:0034]をロード
$C0/DAB2 STA $210F  [$00:210F]   ;Aを[$00:210F]に書き込み
$C0/DAB5 LDA $35    [$00:0035]   ;Aに[$00:0035]をロード
$C0/DAB7 STA $210F  [$00:210F]   ;Aを[$00:210F]に書き込み
$C0/DABA LDA $36    [$00:0036]   ;Aに[$00:0036]をロード
$C0/DABC STA $2110  [$00:2110]   ;Aを[$00:2110]に書き込み
$C0/DABF LDA $37    [$00:0037]   ;Aに[$00:0037]をロード
$C0/DAC1 STA $2110  [$00:2110]   ;Aを[$00:2110]に書き込み
$C0/DAC4 LDA #$01                ;Aに$01をロード
$C0/DAC6 STA $0166  [$00:0166]   ;Aを[$00:0166]に書き込み
$C0/DAC9 RTS                     ;サブルーチン戻り

ここがHDMA転送の前準備+HDMA転送開始処理である。
最初はHDMA転送前のI/Oレジスタへの書き込みである。まとめると以下のとおり。

I/Oレジスタ内容
[$00:212E]メインスクリーンウインドウマスク[$00:0142]($03)
[$00:212F]サブスクリーンウインドウマスク[$00:0143]($00)
[$00:2123]BG1,BG2ウィンドウマスク設定[$00:0145]($CC)
[$00:2124]BG3,BG4ウィンドウマスク設定[$00:0146]($00)
[$00:2125]スプライトウィンドウマスク設定[$00:0147]($00)
[$00:2129][$00:2128]ウィンドウ2右・左座標設定[$00:01FF][$00:01FE]($FF00)
[$00:212A]BGウインドウマスクロジック設定[$00:0148]($00)
[$00:212B]スプライトウインドウマスクロジック設定[$00:0149]($00)

メインスクリーンのBG1・BG2のみウインドウマスクがON。
BG1,BG2ウィンドウマスクはどちらも「ウィンドウ2有効・外側をマスク」「ウィンドウ1無効」である。

  • [$00:2128](ウィンドウ2右座標): $00
  • [$00:2129](ウィンドウ2左座標): $FF

だから、ウィンドウ2の$00$FF、つまりすべてが内側扱いであり、このタイミングだとマスクされている部分は画面外である。
[$00:212A][$00:212B]は、ウィンドウ1とウィンドウ2をどう合成するかという設定になるのだが、たぶん、例えば「BG1のウィンドウ1とウィンドウ2をどう合成するか」の意味なので、ウィンドウ1とウィンドウ2のどちらかしか使っていない場合、たぶん意味はない(あくまでもたぶん)

$C0/DA20$C0/DA2Dで、HDMA転送チャネル3の、決定していなかった[$00:4333][$00:4332]の値が書き込まれる。
処理を見ると[$00:01FB]$00なら$3C30[$00:01FB]$01なら$3C20が書き込まれている。

$C0/DA5FでHDMA転送チャネル3~5の転送が開始になっている。

$C0/DA5C LDA $014F  [$00:014F]   ;Aに[$00:014F](38 = 0011 1000)をロード 
$C0/DA5F STA $420C  [$00:420C]   ;Aを[$00:420C]に書き込み HDMAチャネル3,4,5を実行

HDMA転送をまとめると、

  1. 描画開始
  2. HDMA転送チャネル3でアドレス$3C30が指定された時、スキャンライン0書き込み
  3. HDMA転送チャネル4と5がスキャンライン0~111書き込み
  4. HDMA転送チャネル3でアドレス$3C20が指定された時、スキャンライン112書き込み
  5. HDMA転送チャネル4と5がスキャンライン112~223書き込み
  6. 描画終了

と、いうような順序のようである。

その後から$C0/DA64$C0/DAC9の処理が入るが、スクロールにおける$C0/53C0$C0/541Cの処理に似ている。
ただし[$00:0158][$00:015F]の加算がある。
[$00:0158][$00:015F]が実際のスクロール量で、そこに計算値を加算している、と見て良いようである。

  • [$00:0A39][$00:0A38]/$10 + [$00:0159][$00:0158] = [$00:0031][$00:0030][$00:210D]2度書き
  • [$00:0A3D][$00:0A3C]/$10 + [$00:015D][$00:015C] - 1 = [$00:0033][$00:0032][$00:210E]2度書き
  • [$00:0A3B][$00:0A3A]/$10 + [$00:015B][$00:015A] = [$00:0035][$00:0034][$00:210F]2度書き
  • [$00:0A3F][$00:0A3E]/$10 + [$00:015F][$00:015E] - 1 = [$00:0037][$00:0036][$00:2110]2度書き

BG1水平スクロール量・BG1垂直スクロール量・BG2水平スクロール量・BG2垂直スクロール量の更新なのだが、ここは実質、スクロール量のリセットである。
というのは、毎フレーム、必ず以下の値(2バイト)が2度書きで入るからである。

アドレス
[$00:210D]$0000
[$00:210E]$FFFF
[$00:210F]$0000
[$00:2110]$FFFF

この後に画面が更新され、HDMA転送で毎スキャンライン水平スクロール量がずれる。

以上が大まかな仕組みである。

おまけ

1フレーム毎に更新される$7F:4000$7F:41BFだが、揺れだした初期あたりの値を取り出してみると以下のような感じである(16バイト区切り)。

01 00 01 00 00 00 00 00 00 00 FF FF FE FF FE FF
FD FF FD FF FC FF FC FF FB FF FB FF FB FF FA FF
FA FF FA FF FA FF FA FF FA FF FA FF FB FF FB FF
FB FF FC FF FC FF FD FF FD FF FE FF FE FF FF FF
00 00 00 00 00 00 01 00 01 00 02 00 03 00 03 00
04 00 04 00 05 00 05 00 05 00 06 00 06 00 06 00
06 00 06 00 06 00 06 00 06 00 05 00 05 00 04 00
04 00 04 00 03 00 02 00 02 00 01 00 01 00 00 00
00 00 FF FF FF FF FE FF FE FF FD FF FC FF FC FF
FC FF FB FF FB FF FA FF FA FF FA FF FA FF FA FF
FA FF FA FF FA FF FB FF FB FF FC FF FC FF FC FF
FD FF FD FF FE FF FF FF FF FF 00 00 00 00 01 00
01 00 02 00 02 00 03 00 03 00 04 00 04 00 05 00
05 00 05 00 06 00 06 00 06 00 06 00 06 00 06 00
06 00 05 00 05 00 05 00 04 00 04 00 03 00 03 00
02 00 02 00 01 00 00 00 00 00 00 00 FF FF FE FF
FE FF FD FF FD FF FC FF FC FF FB FF FB FF FB FF
FA FF FA FF FA FF FA FF FA FF FA FF FA FF FB FF
FB FF FB FF FC FF FC FF FC FF FD FF FE FF FE FF
FF FF FF FF 00 00 00 00 01 00 01 00 02 00 03 00
03 00 04 00 04 00 04 00 05 00 05 00 06 00 06 00
06 00 06 00 06 00 06 00 06 00 06 00 05 00 05 00
04 00 04 00 04 00 03 00 03 00 02 00 01 00 01 00
00 00 00 00 FF FF FF FF FE FF FE FF FD FF FC FF
FC FF FC FF FB FF FB FF FB FF FA FF FA FF FA FF
FA FF FA FF FA FF FA FF FB FF FB FF FB FF FC FF
FC FF FD FF FD FF FE FF FE FF FF FF 00 00 00 00
00 00 01 00 02 00 02 00 03 00 03 00 04 00 04 00

2バイト区切りで、符号付き16ビット整数から10進数に変換すると、スクロール量がわかりやすくなる。
プラスの値なら右にずれるし、マイナスの値なら左にずれる。

ライン符号付き
16ビット整数
10進数
000$00011
001$00011
002$00000
003$00000
004$00000
005$FFFF-1
006$FFFE-2
007$FFFE-2
008$FFFD-3
009$FFFD-3
010$FFFC-4
011$FFFC-4
012$FFFB-5
013$FFFB-5
014$FFFB-5
015$FFFA-6
016$FFFA-6
017$FFFA-6
018$FFFA-6
019$FFFA-6
020$FFFA-6
021$FFFA-6
022$FFFB-5
023$FFFB-5
024$FFFB-5
025$FFFC-4
026$FFFC-4
027$FFFD-3
028$FFFD-3
029$FFFE-2
030$FFFE-2
031$FFFF-1
032$00000
033$00000
034$00000
035$00011
036$00011
037$00022
038$00033
039$00033
040$00044
041$00044
042$00055
043$00055
044$00055
045$00066
046$00066
047$00066
048$00066
049$00066
050$00066
051$00066
052$00066
053$00055
054$00055
055$00044
056$00044
057$00044
058$00033
059$00022
060$00022
061$00011
062$00011
063$00000
064$00000
065$FFFF-1
066$FFFF-1
067$FFFE-2
068$FFFE-2
069$FFFD-3
070$FFFC-4
071$FFFC-4
072$FFFC-4
073$FFFB-5
074$FFFB-5
075$FFFA-6
076$FFFA-6
077$FFFA-6
078$FFFA-6
079$FFFA-6
080$FFFA-6
081$FFFA-6
082$FFFA-6
083$FFFB-5
084$FFFB-5
085$FFFC-4
086$FFFC-4
087$FFFC-4
088$FFFD-3
089$FFFD-3
090$FFFE-2
091$FFFF-1
092$FFFF-1
093$00000
094$00000
095$00011
096$00011
097$00022
098$00022
099$00033
100$00033
101$00044
102$00044
103$00055
104$00055
105$00055
106$00066
107$00066
108$00066
109$00066
110$00066
111$00066
112$00066
113$00055
114$00055
115$00055
116$00044
117$00044
118$00033
119$00033
120$00022
121$00022
122$00011
123$00000
124$00000
125$00000
126$FFFF-1
127$FFFE-2
128$FFFE-2
129$FFFD-3
130$FFFD-3
131$FFFC-4
132$FFFC-4
133$FFFB-5
134$FFFB-5
135$FFFB-5
136$FFFA-6
137$FFFA-6
138$FFFA-6
139$FFFA-6
140$FFFA-6
141$FFFA-6
142$FFFA-6
143$FFFB-5
144$FFFB-5
145$FFFB-5
146$FFFC-4
147$FFFC-4
148$FFFC-4
149$FFFD-3
150$FFFE-2
151$FFFE-2
152$FFFF-1
153$FFFF-1
154$00000
155$00000
156$00011
157$00011
158$00022
159$00033
160$00033
161$00044
162$00044
163$00044
164$00055
165$00055
166$00066
167$00066
168$00066
169$00066
170$00066
171$00066
172$00066
173$00066
174$00055
175$00055
176$00044
177$00044
178$00044
179$00033
180$00033
181$00022
182$00011
183$00011
184$00000
185$00000
186$FFFF-1
187$FFFF-1
188$FFFE-2
189$FFFE-2
190$FFFD-3
191$FFFC-4
192$FFFC-4
193$FFFC-4
194$FFFB-5
195$FFFB-5
196$FFFB-5
197$FFFA-6
198$FFFA-6
199$FFFA-6
200$FFFA-6
201$FFFA-6
202$FFFA-6
203$FFFA-6
204$FFFB-5
205$FFFB-5
206$FFFB-5
207$FFFC-4
208$FFFC-4
209$FFFD-3
210$FFFD-3
211$FFFE-2
212$FFFE-2
213$FFFF-1
214$00000
215$00000
216$00000
217$00011
218$00022
219$00022
220$00033
221$00033
222$00044
223$00044

試しにエクセル方眼紙でそれっぽい図にしてみたが、うねうねの再現こそできたものの数値は縮小されて読めなくなってしまった。ご了承いただきたい。



このページをシェアする

上へ