TOP > プログラミング関係解説&調査 > HDMA転送による多重スクロール

HDMA転送による多重スクロール

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

先にスクロールをお読みいただきたい。
スクロールの解説の中で、多重スクロールについて説明した。
多重スクロールでは、BG1とBG2でスクロール量を変えることで多重スクロールさせる、という方法を紹介したが、ここでは「同じBGの中で、一部のスキャンラインのスクロール量をHDMA転送で変えることで多重スクロールを表現する」という場面の処理を紹介する。
本作では、タイトル画面で雲が多重スクロールしている(大きく近い位置の雲ほど速くスクロールする)。

ちなみに、エンディングで朝日をバックに皆が走ってくるシーンなどでも雲が多重スクロールするが、エンディングではスプライトで雲を動かしているので、当項目の方法とは異なる。
このシーンでは、BG2が背景(一番下の地面)で、BG1に太陽(少しずつ上る)、BG3にスタッフクレジットを表示している。

ここではタイトル画面の多重スクロール処理を紹介する。
中世編の魔王山の山頂からの景色をバックに「LIVE A LIVE」のロゴと「©1994 SQUARE」の文字が表示される場面である。
この画面では空の雲が横にスクロールしていくが、雲によってスクロールのスピードが変わる。

デモ画面において何のボタンも押さずに待っていると、最終的に「Welcome to LIVE A LIVE」と「LIVE A LIVE」のロゴが表示され、画面上から下にスクロールし、タイトル画面となる。
それとは別に、デモ画面で何かのボタンを押した時も、画面上から下にスクロールするのはスキップされ、直接タイトル画面となる。
ここではひとまず、タイトル画面のみ取り上げる。
(ちなみに、「Welcome to LIVE A LIVE」の表示時、「LIVE A LIVE」部分だけ横拡大処理が入るのはBGMode7を使っている。スーパーファミコンの特徴の拡大・縮小・回転機能のひとつであり、本作において少ないMode7の使用箇所のひとつである)

画面上から下にスクロールする都合で、ここではBGサイズが縦長(縦64タイル×横32タイル)であり、タイトル画面に表示されるのは下部にあたる。
画面構成は他の多くのシーンと同じくBGMode1で、BG1・BG2・BG3・スプライトが使用可能。
実際には、画面一番手前から順に、

画面内容
スプライト「LIVE A LIVE」のロゴと「©1994 SQUARE」の文字
BG1雲と魔王山の崖
BG2森・山・空(一番下の背景)

このように表示しており、BG3は使っていない。
BG1が多重スクロールを担当していて、一番下の魔王山部分は固定だが、雲は3層に分けて左方向に多重スクロールさせている。
ざっと図を書くと下のようになる。スプライト部分(ロゴなど)は省略。

スキャンラインは4層に分けており、雲がないスキャンライン137以降はスクロールしない。137以降に魔王山の崖などが表示されているため、スクロールさせたら崖まで動いてしまう。
スキャンライン0~136は、雲のサイズにより3層に分かれていて、

スキャンラインスクロール速度
0~724ピクセル/8フレーム
73~1042ピクセル/8フレーム
105~1361ピクセル/8フレーム

と設定されている。
空の上の方に表示されている大きな雲ほどスクロール速度が高く、地平線(山の近く)の小さい雲ほどスクロール速度が低い、ということである。

無限ループとスクロールで説明したが、BGはスクロールで無限ループする。
例えば、128フレームのスクロール、ほぼ2秒くらい(128/60秒)経過すると以下のようになる。

スキャンライン0~72は左に64ピクセル、スキャンライン73~104は左に32ピクセル、スキャンライン105~136は左に16ピクセル移動する。
移動してゲーム画面の外にはみ出した分は、あたかも画面右からそのまま出てきたかのように見える(無限ループのように見える)。

以上から、BG1水平スクロール量BG1HOFS[$00:210D]を、スキャンライン毎にズラしていく処理をHDMA転送でやっているのだろう、ということがだいたい察せられる。
実際の処理を見てみよう。

初期設定

これまでもI/Oレジスタアドレスの初期設定サブルーチンを紹介してきたが、タイトル画面の初期設定は、フィールドでのサブルーチンにあたるバンク$C0のサブルーチンを使っているものの、通常のプレイにおけるフィールドでのサブルーチンの初期設定$C0/0DF6$C0/0ED7ではない。
キャプテンスクウェア開始時(BGMode7切り替え時)も同じ処理が入るため、BGMode7が関係する場面での初期化に使うサブルーチンなのかもしれない。

$C0/0EDF LDA #$8F                ;Aに$8Fをロード
$C0/0EE1 STA $2100  [$00:2100]   ;Aを[$00:2100]に書き込み
$C0/0EE4 STZ $420C  [$00:420C]   ;[$00:420C]に$00を書き込み
$C0/0EE7 LDA #$63                ;Aに$63をロード
$C0/0EE9 STA $2101  [$00:2101]   ;Aを[$00:2101]に書き込み
$C0/0EEC STZ $2102  [$00:2102]   ;[$00:2102]に$00を書き込み
$C0/0EEF STZ $2103  [$00:2103]   ;[$00:2103]に$00を書き込み
$C0/0EF2 LDA #$09                ;Aに$09をロード
$C0/0EF4 STA $2105  [$00:2105]   ;Aを[$00:2105]に書き込み
$C0/0EF7 STZ $2106  [$00:2106]   ;[$00:2106]に$00を書き込み
$C0/0EFA LDA #$41                ;Aに$41をロード
$C0/0EFC STA $2107  [$00:2107]   ;Aを[$00:2107]に書き込み
$C0/0EFF LDA #$51                ;Aに$51をロード
$C0/0F01 STA $2108  [$00:2108]   ;Aを[$00:2108]に書き込み
$C0/0F04 LDA #$48                ;Aに$48をロード
$C0/0F06 STA $2109  [$00:2109]   ;Aを[$00:2109]に書き込み
$C0/0F09 LDA #$00                ;Aに$00をロード
$C0/0F0B STA $210A  [$00:210A]   ;Aを[$00:210A]に書き込み
$C0/0F0E LDA #$00                ;Aに$00をロード
$C0/0F10 STA $210B  [$00:210B]   ;Aを[$00:210B]に書き込み
$C0/0F13 LDA #$03                ;Aに$03をロード
$C0/0F15 STA $210C  [$00:210C]   ;Aを[$00:210C]に書き込み
$C0/0F18 STZ $210D  [$00:210D]   ;[$00:210D]に$00を書き込み
$C0/0F1B STZ $210D  [$00:210D]   ;[$00:210D]に$00を書き込み
$C0/0F1E STZ $210F  [$00:210F]   ;[$00:210F]に$00を書き込み
$C0/0F21 STZ $210F  [$00:210F]   ;[$00:210F]に$00を書き込み
$C0/0F24 STZ $2111  [$00:2111]   ;[$00:2111]に$00を書き込み
$C0/0F27 STZ $2111  [$00:2111]   ;[$00:2111]に$00を書き込み
$C0/0F2A LDA #$FF                ;Aに$FFをロード
$C0/0F2C STA $210E  [$00:210E]   ;Aを[$00:210E]に書き込み
$C0/0F2F STA $210E  [$00:210E]   ;Aを[$00:210E]に書き込み
$C0/0F32 STA $2110  [$00:2110]   ;Aを[$00:2110]に書き込み
$C0/0F35 STA $2110  [$00:2110]   ;Aを[$00:2110]に書き込み
$C0/0F38 STA $2112  [$00:2112]   ;Aを[$00:2112]に書き込み
$C0/0F3B STA $2112  [$00:2112]   ;Aを[$00:2112]に書き込み
$C0/0F3E LDA #$80                ;Aに$80をロード
$C0/0F40 STA $2115  [$00:2115]   ;Aを[$00:2115]に書き込み
$C0/0F43 STZ $2123  [$00:2123]   ;[$00:2123]に$00を書き込み
$C0/0F46 STZ $2124  [$00:2124]   ;[$00:2124]に$00を書き込み
$C0/0F49 STZ $2125  [$00:2125]   ;[$00:2125]に$00を書き込み
$C0/0F4C STZ $2126  [$00:2126]   ;[$00:2126]に$00を書き込み
$C0/0F4F STZ $2127  [$00:2127]   ;[$00:2127]に$00を書き込み
$C0/0F52 STZ $2128  [$00:2128]   ;[$00:2128]に$00を書き込み
$C0/0F55 STZ $2129  [$00:2129]   ;[$00:2129]に$00を書き込み
$C0/0F58 STZ $212A  [$00:212A]   ;[$00:212A]に$00を書き込み
$C0/0F5B STZ $212B  [$00:212B]   ;[$00:212B]に$00を書き込み
$C0/0F5E STZ $212C  [$00:212C]   ;[$00:212C]に$00を書き込み
$C0/0F61 STZ $212D  [$00:212D]   ;[$00:212D]に$00を書き込み
$C0/0F64 STZ $212E  [$00:212E]   ;[$00:212E]に$00を書き込み
$C0/0F67 STZ $212F  [$00:212F]   ;[$00:212F]に$00を書き込み
$C0/0F6A LDA #$30                ;Aに$30をロード
$C0/0F6C STA $2130  [$00:2130]   ;Aを[$00:2130]に書き込み
$C0/0F6F STZ $2131  [$00:2131]   ;[$00:2131]に$00を書き込み
$C0/0F72 LDA #$E0                ;Aに$E0をロード
$C0/0F74 STA $2132  [$00:2132]   ;Aを[$00:2132]に書き込み
$C0/0F77 LDA #$00                ;Aに$00をロード
$C0/0F79 STA $2133  [$00:2133]   ;Aを[$00:2133]に書き込み
$C0/0F7C RTS                     ;サブルーチン戻り

以上から、以下のようにI/Oレジスタアドレスが設定されている。

アドレス名称内容
[$00:2100]INIDISPスクリーン初期化$8F
[$00:2101]OBJSELスプライトサイズ・データ領域選択$63
[$00:2102]OAMADDLOAM下位アドレス$00
[$00:2103]OAMADDHOAM上位アドレスとスプライト優先順位に$00
[$00:2105]BGMODEBG制御レジスタ(BGMode設定)$09
[$00:2106]MOSAICモザイク表示用サイズ・スクリーン設定$00
[$00:2107]BG1SCBG1のBGマップ設定$41
[$00:2108]BG2SCBG2のBGマップ設定$51
[$00:2109]BG3SCBG3のBGマップ設定$48
[$00:210A]BG4SCBG4のBGマップ設定$00
[$00:210B]BG12NBABG1,BG2キャラクタデータ領域設定$00
[$00:210C]BG34NBABG3,BG4キャラクタデータ領域設定$03
[$00:210D]BG1HOFSBG1水平スクロール量$0000
[$00:210F]BG2HOFSBG2水平スクロール量$0000
[$00:2111]BG3HOFSBG3水平スクロール量$0000
[$00:210E]BG1VOFSBG1垂直スクロール量$FFFF
[$00:2110]BG2VOFSBG2垂直スクロール量$FFFF
[$00:2112]BG3VOFSBG3垂直スクロール量$FFFF
[$00:2115]VMAINCビデオポート調整$80
[$00:2123]W12SELBG1,BG2ウィンドウマスク設定$00
[$00:2124]W34SELBG3,BG4ウィンドウマスク設定$00
[$00:2125]WOBJSELスプライトウィンドウマスク設定$00
[$00:2126]WH0ウィンドウ1左座標設定$00
[$00:2127]WH1ウィンドウ1右座標設定$00
[$00:2128]WH2ウィンドウ2左座標設定$00
[$00:2129]WH3ウィンドウ2右座標設定$00
[$00:212A]WBGLOGBGウインドウマスクロジック設定$00
[$00:212B]WOBJLOGスプライトウインドウマスクロジック設定$00
[$00:212C]TMメインスクリーン指定$00
[$00:212D]TSサブスクリーン指定$00
[$00:212E]TMWメインスクリーンウインドウマスク指定$00
[$00:212F]TSWサブスクリーンウインドウマスク指定$00
[$00:2130]CGSWSEL色追加選択$30
[$00:2131]CGADSUB色計算指定$00
[$00:2132]COLDATA固定色のデータ$E0
[$00:2133]SETINIスクリーンモード/ビデオ設定$00

フィールドとの違いは、設定しているI/Oレジスタの種類自体など色々とあるのだが、メインスクリーン指定[$00:212C]やサブスクリーン指定[$00:212D]がいずれも$00で、どのBGもスプライト(OBJ)も非表示なので、あくまでも初期設定に近い。
この後にさらに処理が続き、

$C0/E017 LDA #$13                ;Aに$13(0001 0011)をロード
$C0/E019 STA $212C  [$00:212C]   ;Aを[$00:212C]に書き込み TM メインスクリーン指定 BG1/BG2/OBJ
$C0/E01C LDA #$42                ;Aに$42(0100 0010)をロード
$C0/E01E STA $2107  [$00:2107]   ;Aを[$00:2107]に書き込み BG1SC (BG1設定) 横32x縦64タイル
$C0/E021 LDA #$52                ;Aに$52(0101 0010)をロード
$C0/E023 STA $2108  [$00:2108]   ;Aを[$00:2108]に書き込み BG2SC (BG2設定) 横32x縦64タイル
$C0/E026 LDX #$0200              ;Xに$0200をロード
$C0/E029 STX $0232  [$00:0232]   ;Xを[$00:0233][$00:0232]に書き込み
$C0/E02C STX $0234  [$00:0234]   ;Xを[$00:0235][$00:0234]に書き込み
$C0/E02F JSR $E1FD  [$C0:E1FD]   ;[$C0:E1FD]へジャンプ

$C0/E1FD LDX #$0250              ;Xに$0250をロード
$C0/E200 STZ $00,x  [$00:0250]   ;[$00:0250]に$00を書き込み
$C0/E202 STZ $01,x  [$00:0251]   ;[$00:0251]に$00を書き込み
$C0/E204 STZ $02,x  [$00:0252]   ;[$00:0252]に$00を書き込み
$C0/E206 LDA #$01                ;Aに$01をロード
$C0/E208 STA $03,x  [$00:0253]   ;Aを[$00:0253]に書き込み
$C0/E20A STA $04,x  [$00:0254]   ;Aを[$00:0254]に書き込み
$C0/E20C STA $05,x  [$00:0255]   ;Aを[$00:0255]に書き込み
$C0/E20E RTS                     ;サブルーチン戻り

$C0/E032 JSR $E1A2  [$C0:E1A2]   ;[$C0:E1A2]へジャンプ

$C0/E1A2 LDA $0250  [$00:0250]   ;Aに[$00:0250]($00)をロード
$C0/E1A5 STA $210D  [$00:210D]   ;A($00)を[$00:210D]に書き込み
$C0/E1A8 STZ $210D  [$00:210D]   ;[$00:210D]に$00を書き込み
$C0/E1AB REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/E1AD LDA $0234  [$00:0234]   ;Aに[$00:0235][$00:0234]($0200)をロード
$C0/E1B0 LSR A                   ;Aを論理右シフト (/2)
$C0/E1B1 DEC A                   ;Aをデクリメント -1
$C0/E1B2 SEP #$20                ;MフラグON Aレジスタは8bit幅 A:00FF 
$C0/E1B4 STA $210E  [$00:210E]   ;A($FF)を[$00:210E]に書き込み
$C0/E1B7 XBA                     ;Aレジスタの上位バイトと下位バイトを交換
$C0/E1B8 STA $210E  [$00:210E]   ;A($00)を[$00:210E]に書き込み
$C0/E1BB REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/E1BD LDA $0234  [$00:0234]   ;Aに[$00:0235][$00:0234]($0200)をロード
$C0/E1C0 LSR A                   ;Aを論理右シフト (/2)
$C0/E1C1 DEC A                   ;Aをデクリメント -1
$C0/E1C2 SEP #$20                ;MフラグON Aレジスタは8bit幅 A:00FF 
$C0/E1C4 STA $2110  [$00:2110]   ;A($FF)を[$00:2110]に書き込み
$C0/E1C7 XBA                     ;Aレジスタの上位バイトと下位バイトを交換
$C0/E1C8 STA $2110  [$00:2110]   ;A($00)を[$00:2110]に書き込み
$C0/E1CB RTS                     ;サブルーチン戻り

$C0/E017$C0/E02Fで、タイトル画面用の重要な設定が決まる。

アドレス名称内容
[$00:212C]TMメインスクリーン指定$13 BG1/BG2/OBJ
[$00:2107]BG1SCBG1設定$42 横32×縦64タイル
[$00:2108]BG2SCBG2設定$52 横32×縦64タイル

ここで通常のフィールドと同様、[$00:212C]$13が書き込まれて、BG1・BG2・スプライト(OBJ)がメインスクリーンに表示されることが決まる。
[$00:2107][$00:2108]にそれぞれ$42$52が書き込まれるが、Bit2~7のBGマップ開始アドレスは横に置いておくとして、画面サイズが横32x縦64タイルと設定されたことがわかる。

その後に
[$00:0233][$00:0232]
[$00:0235][$00:0234]
の、各2バイトに$0200が書き込まれているが、これは後に画面のピクセル数(64タイル = 64×8ピクセル = 512 = $0200)を計算するために使う。

$C0/E1FD$C0/E20Eで、以下のように値が書き込まれているが、このアドレスはこの先スクロール量の計算用に使われ、初期値が書き込まれている。

アドレス
[$00:0250]$00
[$00:0251]$00
[$00:0252]$00
[$00:0253]$01
[$00:0254]$01
[$00:0255]$01

さらに、$C0/E1A2$C0/E1CBで、[$00:210E][$00:2110]の初期値が計算・書き込まれる。

  • BG1垂直スクロール量[$00:210E]$00FF(2度書き込み)
  • BG2垂直スクロール量[$00:2110]$00FF(2度書き込み)

であるから、タイトル画面表示のタイミングで、縦方向には$00FFピクセルスクロールした状態で固定ということになる。
BG1は横32×縦64タイルで、タイトル画面時は下に32タイル、つまり$00FFピクセル移動した位置で固定させる、ということである。

HDMA転送設定

HDMA転送はチャネル6とチャネル7が使われており、以下で設定している。

$C0/E1CC LDA #$02                ;Aに$02をロード
$C0/E1CE STA $4360  [$00:4360]   ;Aを[$00:4360]に書き込み
$C0/E1D1 LDA #$0D                ;Aに$0Dをロード
$C0/E1D3 STA $4361  [$00:4361]   ;Aを[$00:4361]に書き込み
$C0/E1D6 LDX #$3C70              ;Xに$3C70をロード
$C0/E1D9 STX $4362  [$00:4362]   ;Xを[$00:4363][$00:4362]に書き込み
$C0/E1DC LDA #$7F                ;Aに$7Fをロード
$C0/E1DE STA $4364  [$00:4364]   ;Aを[$00:4364]に書き込み
$C0/E1E1 STA $4367  [$00:4367]   ;Aを[$00:4367]に書き込み
;
;HDMA チャネル6
;[$00:4360]	GDMA / HDMA設定レジスタ
;[$00:4361]	転送先指定	$0D= BG1HOFS
;[$00:4363][$00:4362]	データ元アドレス(開始位置)	$3C70
;[$00:4364]	データ元バンク	$7F
;[$00:4367]	HDMA間接アドレスバンクバイト	$7F
;
$C0/E1E4 LDA #$02                ;Aに$02をロード
$C0/E1E6 STA $4370  [$00:4370]   ;Aを[$00:4370]に書き込み
$C0/E1E9 LDA #$0E                ;Aに$0Eをロード
$C0/E1EB STA $4371  [$00:4371]   ;Aを[$00:4371]に書き込み
$C0/E1EE LDX #$3C60              ;Xに$3C60をロード
$C0/E1F1 STX $4372  [$00:4372]   ;Xを[$00:4373][$00:4372]に書き込み
$C0/E1F4 LDA #$7F                ;Aに$7Fをロード
$C0/E1F6 STA $4374  [$00:4374]   ;Aを[$00:4374]に書き込み
$C0/E1F9 STA $4377  [$00:4377]   ;Aを[$00:4377]に書き込み
;
;HDMA チャネル7
;[$00:4370]	GDMA / HDMA設定レジスタ	$02
;[$00:4371]	転送先指定	$0E= BG1VOFS
;[$00:4373][$00:4372]	データ元アドレス(開始位置)	$3C60
;[$00:4374]	データ元バンク	$7F
;[$00:4377]	HDMA間接アドレスバンクバイト	$7F
;
$C0/E1FC RTS                     ;サブルーチン戻り

まとめると、

HDMA チャネル6
I/Oアドレス
内容
[$00:4360]GDMA / HDMA設定レジスタ$02
直接テーブルモード 1レジスタ2書き込み
[$00:4361]転送先指定$0D= BG1HOFS
BG1水平スクロール量[$00:210D]
[$00:4363][$00:4362]データ元アドレス(開始位置)$3C70
[$00:4364]データ元バンク$7F
HDMA チャネル7
I/Oアドレス
内容
[$00:4370]GDMA / HDMA設定レジスタ$02
直接テーブルモード 1レジスタ2書き込み
[$00:4371]転送先指定$0E= BG1VOFS
BG1垂直スクロール量[$00:210E]
[$00:4373][$00:4372]データ元アドレス(開始位置)$3C60
[$00:4374]データ元バンク$7F

BG1水平スクロール量[$00:210D]とBG1垂直スクロール量[$00:210E]に関するHDMA転送設定なのだが、タイトル画面だけであれば垂直スクロールはしていない。
デモ画面において何のボタンも押さずに待っていると、最終的に「Welcome to LIVE A LIVE」と「LIVE A LIVE」のロゴが表示され、画面上から下にスクロールし(ここで垂直スクロール量を変更)、タイトル画面となるが、「画面上から下にスクロール」の設定のために垂直スクロール量も設定していたのである。
タイトル画面の雲の横スクロールは、BG1水平スクロール量[$00:210D]で決めている。

HDMAテーブルが書かれている、$7F:3C60~と$7F:3C70~は、上のHDMAチャネル設定の後に計算されて決まる。
HDMAテーブル計算用のサブルーチンが$C0/E231$C0/E387に当たるが、その説明は後として、タイトル画面の初期値として入る$7F:3C60~と$7F:3C70~は以下の通りである(16ビット区切り)。

アドレス
$7F:3C60$7F:3C6F6F FF 00 18 FF 00 58 FF 00 00 FF 00 55 55 55 55
$7F:3C70$7F:3C7F48 00 00 20 00 00 20 00 00 58 00 00 00 55 55 55

水平スクロール量、垂直スクロール量どちらも2バイトの容量を書き込まなければならないため、「1レジスタ2書き込み」の設定になっている。
3バイト区切りでHDMAテーブルが設定されているはずである。
先に、タイトル画面では変動しないはずのBG1垂直スクロール量[$00:210E]を見てみよう。$7F:3C60~から3バイト区切りで見ると以下のようになる。

書き込み
スキャンライン数
[$00:210E]2度書き
$6F$FF, $00
$18$FF, $00
$58$FF, $00
$00$FF, $00

タイトル画面では上の数値のまま$7F:3C60$7F:3C6Bが固定されている。
実質、どのスキャンラインも[$00:210E]$00FFを書き込んでいることになる。
$C0/E1A2$C0/E1CBで書き込まれた初期値と変わらないことがわかる。
(それならタイトル表示の時点でHDMAチャネル7のHDMA転送は止めても良い気がするのだが、続ける理由は筆者にはよくわからない)

一方、BG1水平スクロール量[$00:210D]は初期値が以下のようになる。

書き込み
スキャンライン数
[$00:210D]2度書き
$48$00, $00
$20$00, $00
$20$00, $00
$58$00, $00
$00$00, $00

スキャンライン数から、雲の位置で3層+雲のない層に分けていることがわかる。
この3層を便宜上、以降はエリアA、エリアB、エリアC、雲のない層エリアDとしておく。

エリアスキャンライン数Y座標(ピクセル単位)
A$480~72
B$2073~104
C$20105~136
D$58137~224

137スキャンラインより下エリアDは雲ではない部分でスクロールさせない。
では、$7F:3C70~はどう決めているのか。
横幅は256ピクセルだから、スクロール量は$000$0FFの間でループさせれば、左にBGが移動し、かつ、右端からは左にはみ出した部分が出てくるように見えるはずだ。
スキャンライン数は固定だから、

エリアスキャンライン数[$00:210D]2バイト
A[$7F:3C70]: $48[$7F:3C72][$7F:3C71]: $0000$00FF
B[$7F:3C73]: $20[$7F:3C75][$7F:3C74]: $0000$00FF
C[$7F:3C76]: $20[$7F:3C78][$7F:3C77]: $0000$00FF
D[$7F:3C79]: $58[$7F:3C7B][$7F:3C7A]: $0000固定(スクロールしない)

となるはずで、実質変動するのは[$7F:3C71], [$7F:3C74], [$7F:3C77]だけである。

実際の計算を見てみよう。

$C0/E231 PHB                     ;DBレジスタをスタックにプッシュ
$C0/E232 LDA #$7F                ;Aに$7Fをロード
$C0/E234 PHA                     ;Aレジスタをスタックにプッシュ
$C0/E235 PLB                     ;DBレジスタにスタックからプル
;BG1垂直スクロール量[$00:210E]のHDMAテーブル$7F:3C60~計算
$C0/E236 LDX #$3C60              ;Xに$3C60をロード
$C0/E239 LDA #$6F                ;Aに$6Fをロード
$C0/E23B STA $0000,x[$7F:3C60]   ;A($6F)を[$7F:3C60]に書き込み
$C0/E23E REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/E240 LDA $000234[$00:0234]   ;Aに[$00:0235][$00:0234](0200)をロード
$C0/E244 LSR A                   ;Aを論理右シフト (/2)
$C0/E245 DEC A                   ;Aをデクリメント -1
$C0/E246 STA $0001,x[$7F:3C61]   ;A(00FF)を[$7F:3C62][$7F:3C61]に書き込み
$C0/E249 STA $0004,x[$7F:3C64]   ;A(00FF)を[$7F:3C65][$7F:3C64]に書き込み
$C0/E24C LDA $000234[$00:0234]   ;A(0200)に[$00:0235][$00:0234]をロード
$C0/E250 BMI $05    [$E257]      ;ネガティブフラグが立っているとき[$E257]分岐
$C0/E252 CMP #$0150              ;A(0200)と$0150を減算比較(ステータスレジスタ変更のみ)
$C0/E255 BCS $0C    [$E263]      ;キャリーフラグが立っているとき[$E263]分岐
$C0/E263 SBC #$0150              ;A(0200) - $0150 
$C0/E266 LSR A                   ;A(00B0)を論理右シフト (/2)
$C0/E267 STA $30    [$00:0030]   ;A(0058)を[$00:0031][$00:0030]に書き込み
$C0/E269 STZ $0007,x[$7F:3C67]   ;[$7F:3C68][$7F:3C67]に$0000を書き込み
$C0/E26C LDA $000232[$00:0232]   ;Aに[$00:0233][$00:0232](0200)をロード
$C0/E270 LSR A                   ;A(0200)を論理右シフト (/2)
$C0/E271 DEC A                   ;A(0100)をデクリメント -1
$C0/E272 STA $000A,x[$7F:3C6A]   ;A(00FF)を[$7F:3C6B][$7F:3C6A]に書き込み
$C0/E275 LDA $000232[$00:0232]   ;Aに[$00:0233][$00:0232](0200)をロード
$C0/E279 BMI $52    [$E2CD]      ;ネガティブフラグが立っているとき[$E2CD]分岐
$C0/E27B SEC                     ;キャリーフラグON
$C0/E27C SBC #$0150              ;A(0200) - $0150
$C0/E27F BCC $4C    [$E2CD]      ;キャリーフラグが立っていないとき[$E2CD]分岐
$C0/E281 LSR A                   ;A(00B0)を論理右シフト (/2)
$C0/E282 STA $32    [$00:0032]   ;A(0058)を[$00:0033][$00:0032]に書き込み
$C0/E284 LDA $30    [$00:0030]   ;A(0058)に[$00:0031][$00:0030]をロード
$C0/E286 SEC                     ;キャリーフラグON
$C0/E287 SBC $32    [$00:0032]   ;A(0058) - [$00:0033][$00:0032](0058)
$C0/E289 STA $32    [$00:0032]   ;A(0000)を[$00:0033][$00:0032]に書き込み
$C0/E28B SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/E28D LDA #$70                ;Aに$70をロード
$C0/E28F SEC                     ;キャリーフラグON
$C0/E290 SBC $30    [$00:0030]   ;A(70) - [$00:0030](58)
$C0/E292 STA $0003,x[$7F:3C63]   ;A(18)を[$7F:3C63]に書き込み
$C0/E295 LDA $32    [$00:0032]   ;Aに[$00:0032]をロード
$C0/E297 BEQ $1E    [$E2B7]      ;ゼロフラグが立っているとき[$E2B7]分岐
$C0/E2B7 LDA $30    [$00:0030]   ;Aに[$00:0030](58)をロード
$C0/E2B9 STA $0006,x[$7F:3C66]   ;A(58)を[$7F:3C66]に書き込み
$C0/E2BC LDA $000A,x[$7F:3C6A]   ;Aに[$7F:3C6A](FF)をロード
$C0/E2BF STA $0007,x[$7F:3C67]   ;A(FF)を[$7F:3C67]に書き込み
$C0/E2C2 LDA $000B,x[$7F:3C6B]   ;Aに[$7F:3C6B](00)をロード
$C0/E2C5 STA $0008,x[$7F:3C68]   ;A(00)を[$7F:3C68]に書き込み
$C0/E2C8 STZ $0009,x[$7F:3C69]   ;[$7F:3C69]に$00を書き込み
$C0/E2CB BRA $12    [$E2DF]      ;フラグにかかわりなく常に分岐[$E2DF]
;BG1水平スクロール量[$00:210D]のHDMAテーブル$7F:3C70~計算
$C0/E2DF LDX #$3C70              ;Xに$3C70をロード
$C0/E2E2 LDA $000250[$00:0250]   ;Aに[$00:0250](00)をロード
$C0/E2E6 STA $0001,x[$7F:3C71]   ;A(00)を[$7F:3C71]に書き込み
$C0/E2E9 STZ $0002,x[$7F:3C72]   ;[$7F:3C72]に$00を書き込み
$C0/E2EC STA $0004,x[$7F:3C74]   ;Aを[$7F:3C74](00)に書き込み
$C0/E2EF STZ $0005,x[$7F:3C75]   ;[$7F:3C75]に$00を書き込み
$C0/E2F2 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/E2F4 LDA $000234[$00:0234]   ;Aに[$00:0235][$00:0234](0200)をロード
$C0/E2F8 BMI $7D    [$E377]      ;ネガティブフラグが立っているとき[$E377]分岐
$C0/E2FA CMP #$00D0              ;Aと$00D0を減算比較(ステータスレジスタ変更のみ)
$C0/E2FD BCC $78    [$E377]      ;キャリーフラグが立っていないとき[$E377]分岐
$C0/E2FF SEC                     ;キャリーフラグON
$C0/E300 SBC #$00D0              ;A(0200) - $00D0
$C0/E303 LSR A                   ;A(0130)を論理右シフト (/2)
$C0/E304 STA $30    [$00:0030]   ;A(0098)を[$00:0031][$00:0030]に書き込み
$C0/E306 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/E308 LDA #$E0                ;Aに$E0をロード
$C0/E30A SEC                     ;キャリーフラグON
$C0/E30B SBC $30    [$00:0030]   ;A($E0) - [$00:0030](98)
$C0/E30D CMP #$70                ;Aと$70を減算比較(ステータスレジスタ変更のみ)
$C0/E30F BCC $11    [$E322]      ;キャリーフラグが立っていないとき[$E322]分岐
$C0/E322 STA $0000,x[$7F:3C70]   ;A(48)を[$7F:3C70]に書き込み
$C0/E325 LDA $000251[$00:0251]   ;Aに[$00:0251](00)をロード
$C0/E329 STA $0004,x[$7F:3C74]   ;A(00)を[$7F:3C74]に書き込み
$C0/E32C STZ $0005,x[$7F:3C75]   ;[$7F:3C75]に$00を書き込み
$C0/E32F LDA $30    [$00:0030]   ;Aに[$00:0030]をロード
$C0/E331 CMP #$20                ;A(98)と$20を減算比較(ステータスレジスタ変更のみ)
$C0/E333 BCC $3A    [$E36F]      ;キャリーフラグが立っていないとき[$E36F]分岐
$C0/E335 BEQ $38    [$E36F]      ;ゼロフラグが立っているとき[$E36F]分岐
$C0/E337 SBC #$20                ;A(98) - $20
$C0/E339 STA $30    [$00:0030]   ;A(78)を[$00:0030]に書き込み
$C0/E33B LDA #$20                ;Aに$20をロード
$C0/E33D STA $0003,x[$7F:3C73]   ;A($20)を[$7F:3C73]に書き込み
$C0/E340 LDA $000252[$00:0252]   ;A(00)に[$00:0252]をロード
$C0/E344 STA $0007,x[$7F:3C77]   ;A(00)を[$7F:3C77]に書き込み
$C0/E347 STZ $0008,x[$7F:3C78]   ;[$7F:3C78]に$00を書き込み
$C0/E34A LDA $30    [$00:0030]   ;Aに[$00:0030](78)をロード
$C0/E34C CMP #$20                ;Aと$20を減算比較(ステータスレジスタ変更のみ)
$C0/E34E BCC $17    [$E367]      ;キャリーフラグが立っていないとき[$E367]分岐
$C0/E350 BEQ $15    [$E367]      ;ゼロフラグが立っているとき[$E367]分岐
$C0/E352 SBC #$20                ;A(78) - $20
$C0/E354 STA $0009,x[$7F:3C79]   ;A(58)を[$7F:3C79]に書き込み
$C0/E357 LDA #$20                ;Aに$20をロード
$C0/E359 STA $0006,x[$7F:3C76]   ;A($20)を[$7F:3C76]に書き込み
$C0/E35C STZ $000A,x[$7F:3C7A]   ;[$7F:3C7A]に$00を書き込み
$C0/E35F STZ $000B,x[$7F:3C7B]   ;[$7F:3C7B]に$00を書き込み
$C0/E362 STZ $000C,x[$7F:3C7C]   ;[$7F:3C7C]に$00を書き込み
$C0/E365 BRA $1F    [$E386]      ;フラグにかかわりなく常に分岐[$E386]
$C0/E386 PLB                     ;DBレジスタにスタックからプル
$C0/E387 RTS                     ;サブルーチン戻り

$C0/E083 LDA #$C0                ;Aに$C0をロード
$C0/E085 STA $420C  [$00:420C]   ;Aを[$00:420C]に書き込み HDMA転送
$C0/E088 LDA #$01                ;Aに$01をロード
$C0/E08A STA $0237  [$00:0237]   ;Aを[$00:0237]に書き込み
$C0/E08D JSR $E20F  [$C0:E20F]   ;[$C0:E20F]へジャンプ

BG1水平スクロール量[$00:210D]用のHDMAテーブルである$7F:3C70~の計算は$C0/E2DFからなので、そこから説明する。
処理を追ってみると以下のようになる。

計算内容
[$7F:3C71] = [$00:0250]($00)エリアAスクロール量(下1バイト)
[$7F:3C72] = $00エリアAスクロール量(上1バイト)$00固定
[$7F:3C74] = [$00:0250]($00)エリアBスクロール量(下1バイト)
[$7F:3C75] = $00エリアBスクロール量(上1バイト)$00固定

[$00:0031][$00:0030] = ([$00:0235][$00:0234]($0200) - $00D0)/2 = $0098

計算内容
[$7F:3C70] = $E0 - [$00:0030]($98) = $48エリアAスキャンライン数
[$7F:3C74] = [$00:0251]($00)エリアBスクロール量(下1バイト)計算値
[$7F:3C75] = $00エリアBスクロール量(上1バイト)$00固定

[$00:0030] = [$00:0030]($98) - 20 = $78

計算内容
[$7F:3C73] = $20エリアBスキャンライン数
[$7F:3C77] = [$00:0252]($00)エリアCスクロール量(下1バイト)計算値
[$7F:3C78] = $00エリアCスクロール量(上1バイト)$00固定
[$7F:3C79] = [$00:0030]($78) - $20 = $58エリアDスキャンライン数
[$7F:3C76] = $20エリアCスキャンライン数
[$7F:3C7A] = $00エリアDスクロール量(下1バイト)(スクロールしない)
[$7F:3C7B] = $00エリアDスクロール量(上1バイト)(スクロールしない)
[$7F:3C7C] = $00※HDMA転送終了用の$00

実質、[$00:0250], [$00:0251], [$00:0252]がエリアA, B, Cのスクロール量そのままだということがわかる。
また、ここより前に[$00:0235][$00:0234]に書き込んだ$0200は、エリアのスキャンライン数を決めるための値だったこともわかる。

[$00:0250], [$00:0251], [$00:0252]は、$C0/E1FD$C0/E20Eで初期値が$00と書き込まれているため、スクロール量初期値は$0000である。

BG1水平スクロール量[$00:210D]用のHDMAテーブルが決まった直後の$C0/E083$C0/E08DでHDMA転送を行っており、この後のHBlankのタイミングでHDMA転送を行って、各スキャンラインの水平スクロール量を更新する。

HDMA転送を行った直後、以下処理で[$00:0250], [$00:0251], [$00:0252]が更新される。

$C0/E20F LDX #$0250              ;Xに$0250をロード
$C0/E212 DEC $03,x  [$00:0253]   ;[$00:0253](初期値$01)をデクリメント -1
$C0/E214 BNE $06    [$E21C]      ;ゼロフラグが立っていないとき[$E21C]分岐
;ゼロフラグON
$C0/E216 LDA #$02                ;Aに$02をロード
$C0/E218 STA $03,x  [$00:0253]   ;Aを[$00:0253]に書き込み
$C0/E21A INC $00,x  [$00:0250]   ;[$00:0250](初期値$00)をインクリメント +1
$C0/E21C DEC $04,x  [$00:0254]   ;[$00:0254](初期値$01)をデクリメント -1
$C0/E21E BNE $06    [$E226]      ;ゼロフラグが立っていないとき[$E226]分岐
;ゼロフラグON
$C0/E220 LDA #$04                ;Aに$04をロード
$C0/E222 STA $04,x  [$00:0254]   ;Aを[$00:0254]に書き込み
$C0/E224 INC $01,x  [$00:0251]   ;[$00:0251](初期値$00)をインクリメント +1
$C0/E226 DEC $05,x  [$00:0255]   ;[$00:0255](初期値$01)をデクリメント -1
$C0/E228 BNE $06    [$E230]      ;ゼロフラグが立っていないとき[$E230]分岐
;ゼロフラグON
$C0/E22A LDA #$08                ;Aに$08をロード
$C0/E22C STA $05,x  [$00:0255]   ;Aを[$00:0255](初期値$01)に書き込み
$C0/E22E INC $02,x  [$00:0252]   ;[$00:0252](初期値$00)をインクリメント +1
$C0/E230 RTS                     ;サブルーチン戻り

初期値に$01を書き込んだ[$00:0253][$00:0255]の値により、途中にゼロフラグ分岐がある。
整理してみると下のようになる。

エリアスクロール量スクロール量
エリアA[$00:0250][$00:0253]-1して$00の時、$02を書き込み、[$00:0250]+1
エリアB[$00:0251][$00:0254]-1して$00の時、$04を書き込み、[$00:0251]+1
エリアC[$00:0252][$00:0255]-1して$00の時、$08を書き込み、[$00:0252]+1

結果として、エリアAは毎フレーム、[$00:0253]$02$01$02$01→……を繰り返し、[$00:0250]$02が書き込まれたタイミング、つまり2フレームに1回、[$00:0250]+1してスクロール量が増加する。
同じ理屈で、エリアBは4フレームに1回、[$00:0251]+1してスクロール量が増加する。
エリアCは8フレームに1回、[$00:0252]+1してスクロール量が増加する。
これがエリア毎(スキャンライン毎)にスクロール量が変わる仕組みである。
インクリメント時にキャリー(繰り上がり)は考慮していないのでオーバーフローして$FFの次は$00に戻るのだが、むしろそれで[$00:210D](水平スクロール量)の無限ループが完成するため、問題ない。

以上を繰り返してHDMA転送による多重スクロールを行っている。



このページをシェアする

上へ