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

スクロール

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

ここではゲーム画面における背景(BG)のスクロール(画面の上下左右移動)について説明する。
ゲーム画面を見ているとわかるとおり、背景やスプライトを必要に応じて動かしているが、広いマップを移動する場合、操作キャラ(のスプライト)は中央付近に設置して足踏みのモーションをさせ、背景だけスクロールさせることでマップ移動を表現することはよくある。
スプライトの座標はOAMで決めるが、背景は32×32タイル(256×256ピクセル)を1単位として、本作ではマップによるが大半は64×32タイルをBGマップに従い作成し、その中で256×224ピクセルが実際にゲーム画面として表示されている。

ここではフィールドにおける背景スクロールについて説明する。
本作においては、画面のスクロールは実質、フィールドマップにおける背景のスクロールしかない。
メニュー画面や戦闘画面では背景はスクロールしない(動かない)からである。
(厳密には、戦闘だと敵キャラが背景扱いだから動くのだが、それぞれの敵のタイルがBGマップで座標指定して動くだけでスクロールは関係ない)

また、本作は基本的にBGMode1で、フィールドだとBG1・BG2に背景、BG3にセリフウィンドウなどを表示する。
よって、ここではBG1・BG2の背景のスクロールの話をメインに説明していく。
だいたいの場合で、画面手前からBG3→BG2→スプライト→BG1と重ねていくことなどは、背景グラフィックと効果 > フィールドでのサブルーチンで説明したので参照していただきたい。

通常はBG1とBG2を同量だけスクロールさせるが、背景ごとにスクロール量を変えることで距離感(遠近感)を出している場面も幾つかある。
当コンテンツでは多重スクロールという名称で説明をする。
詳細は多重スクロールの項目で説明している。

また、スクロールの仕組みの説明の補足として、最後に無限ループとスクロールについても説明している。
次ページのHDMA転送による多重スクロールも合わせて御覧いただきたい。

背景のスクロールの値はI/Oポートアドレス$00:210D$00:2114で決めている。
なお、BGMode7(画面の回転などが可能なモード)だと異なる値の設定になるが、今回は通常のゲームプレイ画面、BGMode1での話とする。

アドレス名前内容
$00:210DBG1HOFSBG1水平スクロール量
$00:210EBG1VOFSBG1垂直スクロール量
$00:210FBG2HOFSBG2水平スクロール量
$00:2110BG2VOFSBG2垂直スクロール量
$00:2111BG3HOFSBG3水平スクロール量
$00:2112BG3VOFSBG3垂直スクロール量
$00:2113BG4HOFSBG4水平スクロール量
$00:2114BG4VOFSBG4垂直スクロール量

この「スクロール量」というのは、「そのマップに入った地点からどれくらいスクロールしたか」という意味のスクロール量である。
そのマップに扉から入ったとしたら、扉から右に1マス(16ピクセル)移動すれば水平スクロール量が16になるし、さらに右に1マス移動したら水平スクロール量が32になる。
そのマップに入る他の方法として、扉とは別の場所に階段があったとして、階段から出入りしたら階段位置を基準としてスクロール量を設定する。
というように、基準が少々ややこしい。

フィールドにおける移動だと、$00:210D$00:2110のBG1・BG2のスクロール量を操作して背景を動かすことになる。
本作はフィールドで、斜め移動はできないから、水平・垂直スクロール量が同時に変動することは多分ない(はず)。
イベントなどでも斜めに背景が移動することはなかったはず。
左右に移動すれば水平スクロール量が変動するし、上下に移動すれば垂直スクロール量が変動する。

このスクロール量の設定が少々厄介である。
上の8種のアドレスは、2度書き込みをして、2バイト(16ビット)の値を設定する必要がある。
ただし実際に使われるのは、1回目に書き込んだ8ビットと、2回目に書き込んだ下2ビットの合計10ビット($000$3FF、10進数なら0~1023)。
1回目に書き込んだ8ビットは下位8ビット、2回目は上位2ビット、と読むことになる。

以上のように数値を書き込む都合で、水平方向なら右、垂直方向なら下が正の方向になり、0から増加していくことになるのだが、逆の場合は1023から減少していくことになる。

フィールド移動時は以下サブルーチンで$00:210D$00:2110のBG1・BG2のスクロール量を書き換える。

$C0/53C0 LDA $0166  [$00:0166]   ;Aに[$00:0166]をロード
$C0/53C3 BNE $57    [$541C]      ;ゼロフラグが立っていないとき[$541C]分岐
$C0/53C5 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/53C7 LDA $0A38  [$00:0A38]   ;Aに[$00:0A39][$00:0A38]をロード
$C0/53CA LSR A                   ;Aを論理右シフト (/2)
$C0/53CB LSR A                   ;Aを論理右シフト (/2)
$C0/53CC LSR A                   ;Aを論理右シフト (/2)
$C0/53CD LSR A                   ;Aを論理右シフト (/2)
$C0/53CE STA $30    [$00:0030]   ;Aを[$00:0031][$00:0030]に書き込み
$C0/53D0 LDA $0A3C  [$00:0A3C]   ;Aに[$00:0A3D][$00:0A3C]をロード
$C0/53D3 LSR A                   ;Aを論理右シフト (/2)
$C0/53D4 LSR A                   ;Aを論理右シフト (/2)
$C0/53D5 LSR A                   ;Aを論理右シフト (/2)
$C0/53D6 LSR A                   ;Aを論理右シフト (/2)
$C0/53D7 DEC A                   ;Aをデクリメント -1
$C0/53D8 STA $32    [$00:0032]   ;Aを[$00:0033][$00:0032]に書き込み
$C0/53DA LDA $0A3A  [$00:0A3A]   ;Aに[$00:0A3B][$00:0A3A]をロード
$C0/53DD LSR A                   ;Aを論理右シフト (/2)
$C0/53DE LSR A                   ;Aを論理右シフト (/2)
$C0/53DF LSR A                   ;Aを論理右シフト (/2)
$C0/53E0 LSR A                   ;Aを論理右シフト (/2)
$C0/53E1 STA $34    [$00:0034]   ;Aを[$00:0035][$00:0034]に書き込み
$C0/53E3 LDA $0A3E  [$00:0A3E]   ;Aに[$00:0A3F][$00:0A3E]をロード
$C0/53E6 LSR A                   ;Aを論理右シフト (/2)
$C0/53E7 LSR A                   ;Aを論理右シフト (/2)
$C0/53E8 LSR A                   ;Aを論理右シフト (/2)
$C0/53E9 LSR A                   ;Aを論理右シフト (/2)
$C0/53EA DEC A                   ;Aをデクリメント -1
$C0/53EB STA $36    [$00:0036]   ;Aを[$00:0037][$00:0036]に書き込み
$C0/53ED SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/53EF LDA $30    [$00:0030]   ;Aに[$00:0030]をロード
$C0/53F1 STA $210D  [$00:210D]   ;Aを[$00:210D]に書き込み
$C0/53F4 LDA $31    [$00:0031]   ;Aに[$00:0031]をロード
$C0/53F6 STA $210D  [$00:210D]   ;Aを[$00:210D]に書き込み
$C0/53F9 LDA $32    [$00:0032]   ;Aに[$00:0032]をロード
$C0/53FB STA $210E  [$00:210E]   ;Aを[$00:210E]に書き込み
$C0/53FE LDA $33    [$00:0033]   ;Aに[$00:0033]をロード
$C0/5400 STA $210E  [$00:210E]   ;Aを[$00:210E]に書き込み
$C0/5403 LDA $34    [$00:0034]   ;Aに[$00:0034]をロード
$C0/5405 STA $210F  [$00:210F]   ;Aを[$00:210F]に書き込み
$C0/5408 LDA $35    [$00:0035]   ;Aに[$00:0035]をロード
$C0/540A STA $210F  [$00:210F]   ;Aを[$00:210F]に書き込み
$C0/540D LDA $36    [$00:0036]   ;Aに[$00:0036]をロード
$C0/540F STA $2110  [$00:2110]   ;Aを[$00:2110]に書き込み
$C0/5412 LDA $37    [$00:0037]   ;Aに[$00:0037]をロード
$C0/5414 STA $2110  [$00:2110]   ;Aを[$00:2110]に書き込み
$C0/5417 LDA #$01                ;Aに$01をロード
$C0/5419 STA $0166  [$00:0166]   ;Aを[$00:0166]に書き込み
$C0/541C RTS                     ;サブルーチン戻り

上を整理してみると、

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

であるから、[$00:0A38][$00:0A3F]が2バイト区切りでBG1水平スクロール量・BG1垂直スクロール量・BG2水平スクロール量・BG2垂直スクロール量の計算に関わる数値だということがわかる。
垂直スクロールだけ、なぜか-1されているが、

注 : 多くのゲームで垂直スクロール位置は 0 ではなく -1 に設定されている。これは、SNES が OBJ データをスキャンライン毎に 1 つ前の位置を参照するためである。
PPU/BG - SNES Spec - atwiki(アットウィキ)

という事情があるかららしい。

では、[$00:0A38][$00:0A3F]はいったい何なのか。
マップ切り替え時、以下のような処理が入る。

$C0/4F80 LDA $0114  [$00:0114]   ;Aに[$00:0114]をロード
$C0/4F83 BNE $0B    [$4F90]      ;ゼロフラグが立っていないとき[$4F90]分岐
$C0/4F85 LDX #$0000              ;Xに$0000をロード
$C0/4F88 STX $0A38  [$00:0A38]   ;Xを[$00:0A39][$00:0A38]に書き込み
$C0/4F8B STX $0A3C  [$00:0A3C]   ;Xを[$00:0A3D][$00:0A3C]に書き込み
$C0/4F8E BRA $30    [$4FC0]      ;フラグにかかわりなく常に分岐[$4FC0]
$C0/4FC0 LDA $0117  [$00:0117]   ;Aに[$00:0117]をロード
$C0/4FC3 BNE $13    [$4FD8]      ;ゼロフラグが立っていないとき[$4FD8]分岐
$C0/4FC5 LDA $0116  [$00:0116]   ;Aに[$00:0116]をロード
$C0/4FC8 BNE $19    [$4FE3]      ;ゼロフラグが立っていないとき[$4FE3]分岐
$C0/4FCA LDX $0A38  [$00:0A38]   ;Xに[$00:0A39][$00:0A38]をロード
$C0/4FCD STX $0A3A  [$00:0A3A]   ;Xを[$00:0A3B][$00:0A3A]に書き込み
$C0/4FD0 LDX $0A3C  [$00:0A3C]   ;Xに[$00:0A3D][$00:0A3C]をロード
$C0/4FD3 STX $0A3E  [$00:0A3E]   ;Xを[$00:0A3F][$00:0A3E]に書き込み
$C0/4FD6 BRA $5E    [$5036]      ;フラグにかかわりなく常に分岐[$5036]
$C0/5036 RTS                     ;サブルーチン戻り

$C0/4F85$C0/4F8B[$00:0A39][$00:0A38], [$00:0A3D][$00:0A3C]$0000が書き込まれる。
さらに、[$00:0117][$00:0116]の値次第だが、$C0/4FCA$C0/4FD3[$00:0A3B][$00:0A3A], [$00:0A3F][$00:0A3E]にも$0000が書き込まれる。
[$00:0117][$00:0116]$00以外が入っていて、別処理になる場合は後で紹介するが、背景が多重スクロールする場合である)

以上から、マップ切り替え直後の[$00:210D][$00:2110]の値は、以下のようになる。
2度書きだから2バイト分(16ビット)の値である。

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

といっても、実際には先程書いた通りに16ビットの中で下10ビットしか使わないため、

アドレス内容
[$00:210D]BG1水平スクロール量$000
[$00:210E]BG1垂直スクロール量$3FF
[$00:210F]BG2水平スクロール量$000
[$00:2110]BG2垂直スクロール量$3FF

これがマップ切り替え直後の初期値である。
では、この状況で右にダッシュ移動してみるとどうなるか。
(ここではおぼろ丸以外のキャラの通常Bダッシュとする)

$C0/0CAC LDA $0167  [$00:0167]   ;Aに[$00:0167]をロード
$C0/0CAF REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/0CB1 LSR A                   ;Aを論理右シフト (/2)
$C0/0CB2 STA $30    [$00:0030]   ;Aを[$00:0031][$00:0030]に書き込み
$C0/0CB4 BCS $09    [$0CBF]      ;キャリーフラグが立っているとき[$0CBF]分岐
$C0/0CBF LDA $01AE  [$00:01AE]   ;Aに[$00:01AF][$00:01AE] (マップ上移動速度)をロード
$C0/0CC2 STA $32    [$00:0032]   ;Aを[$00:0033][$00:0032]に書き込み
$C0/0CC4 LSR A                   ;Aを論理右シフト (/2)
$C0/0CC5 STA $34    [$00:0034]   ;Aを[$00:0035][$00:0034]に書き込み
$C0/0CC7 LSR $30    [$00:0030]   ;[$00:0031][$00:0030]を論理右シフト (/2)
$C0/0CC9 BCS $46    [$0D11]      ;キャリーフラグが立っているとき[$0D11]分岐
$C0/0D11 LDX #$0A20              ;Xに$0A20をロード
$C0/0D14 STX $30    [$00:0030]   ;Xを[$00:0031][$00:0030]に書き込み
$C0/0D16 LDX #$0A38              ;Xに$0A38をロード
$C0/0D19 LDY #$0A40              ;Yに$0A40をロード
$C0/0D1C LDA $0000,y[$00:0A40]   ;Aに[$00:0A41][$00:0A40]をロード
$C0/0D1F CLC                     ;キャリーフラグクリア
$C0/0D20 ADC $32    [$00:0032]   ;A + [$00:0033][$00:0032]
$C0/0D22 STA $0000,y[$00:0A40]   ;Aを[$00:0A41][$00:0A40]に書き込み
$C0/0D25 LDA $00,x  [$00:0A38]   ;Aに[$00:0A39][$00:0A38]をロード
$C0/0D27 CLC                     ;キャリーフラグクリア
$C0/0D28 ADC $32    [$00:0032]   ;A + [$00:0033][$00:0032]
$C0/0D2A STA $00,x  [$00:0A38]   ;Aを[$00:0A39][$00:0A38]に書き込み
$C0/0D2C INX                     ;Xをインクリメント +1
$C0/0D2D INX                     ;Xをインクリメント +1
$C0/0D2E SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/0D30 LDA $0117  [$00:0117]   ;Aに[$00:0117]をロード
$C0/0D33 BNE $35    [$0D6A]      ;ゼロフラグが立っていないとき[$0D6A]分岐
$C0/0D35 LDA $0116  [$00:0116]   ;Aに[$00:0116]をロード
$C0/0D38 BEQ $25    [$0D5F]      ;ゼロフラグが立っているとき[$0D5F]分岐
$C0/0D5F REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/0D61 LDA $00,x  [$00:0A3A]   ;Aに[$00:0A3B][$00:0A3A]をロード
$C0/0D63 CLC                     ;キャリーフラグクリア
$C0/0D64 ADC $32    [$00:0032]   ;A + [$00:0033][$00:0032]
$C0/0D66 STA $00,x  [$00:0A3A]   ;Aを[$00:0A3B][$00:0A3A]に書き込み
$C0/0D68 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/0D6A LDA $0114  [$00:0114]   ;Aに[$00:0114]をロード
$C0/0D6D BEQ $53    [$0DC2]      ;ゼロフラグが立っているとき[$0DC2]分岐
$C0/0DC2 RTS                     ;サブルーチン戻り

ポイントは$C0/0CBFでロードされる[$00:01AF][$00:01AE]で、[$00:01A9][$00:01A8]と同値なのだが、マップ上移動速度による値が入る。
2バイト値だが、上1バイト([$00:01A9])は$00なので実質下1バイトの[$00:01AE]の値しか使わない。

[$00:01AE]状態
$00移動しない
$10通常歩行
$20ダッシュ(おぼろ丸以外主人公時)
$40ダッシュ(おぼろ丸主人公時)

というように値が入る。
ダッシュ時は以下の処理で値が入っている。

$C0/08A0 LDX #$0020              ;Xに$0020をロード
$C0/08A3 BRA $03    [$08A8]      ;フラグにかかわりなく常に分岐[$08A8]
$C0/08A8 STX $01A8  [$00:01A8]   ;Xを[$00:01A9][$00:01A8]に書き込み
$C0/08AB STX $01AE  [$00:01AE]   ;Xを[$00:01AF][$00:01AE]に書き込み

ダッシュの時の値$0020は、$C0/0CC2で、[$00:01AF][$00:01AE][$00:0033][$00:0032]にコピーされる。
さらに、$C0/0D25$C0/0D2Aで、

[$00:0A39][$00:0A38] = [$00:0A39][$00:0A38] + [$00:0033][$00:0032] ($0020)

と、元の[$00:0A39][$00:0A38]$0020(ダッシュ時の値)を加算する。
$C0/0D61$C0/0D66で、

[$00:0A3B][$00:0A3A] = [$00:0A3B][$00:0A3A] + [$00:0033][$00:0032] ($0020)

と、こちらも$0020(ダッシュ時の値)を加算する。
実質、[$00:0A38][$00:0A3A]の値だけが変動したことになり、他の値は変化しない。
横(右)、つまり水平方向にだけ移動したからであり、垂直方向には変化がないからである。

以上が1フレームでの処理である。
[$00:210D][$00:2110]はどうなるのか。

  • [$00:0A39][$00:0A38]($0020)/$10 = $0002[$00:210D]2度書き
  • [$00:0A3D][$00:0A3C]($0000)/$10 - 1 = $FFFF[$00:210E]2度書き
  • [$00:0A3B][$00:0A3A]($0020)/$10 = $0000[$00:210F]2度書き
  • [$00:0A3F][$00:0A3E]($0000)/$10 - 1 = $FFFF[$00:2110]2度書き

よって、

アドレス内容
[$00:210D]BG1水平スクロール量$002
[$00:210E]BG1垂直スクロール量$3FF
[$00:210F]BG2水平スクロール量$002
[$00:2110]BG2垂直スクロール量$3FF

BG1とBG2の水平スクロール量が+2された。垂直方向には移動していないので値は変わらない。
おぼろ丸以外のダッシュだと、1フレームで2ピクセル背景が動く、ということである。
つまり、

[$00:01AE]状態
$00移動しない
$10通常歩行
$20ダッシュ(おぼろ丸以外主人公時)
$40ダッシュ(おぼろ丸主人公時)

[$00:01AE]に入る値は、上1桁が1フレームで移動可能なピクセル数ということである。
1マスが16ピクセルだから、通常歩行だと1マス移動に16フレーム、ダッシュ(おぼろ丸以外)だと8フレーム、おぼろ丸ダッシュだと4フレーム、ということである。
同時に、背景のスクロール量でもある。

ゲームでは、十字キーで移動した場合、最低でも1マス移動するので、1マスのダッシュ移動なら、

アドレス内容
[$00:210D]BG1水平スクロール量$010
[$00:210E]BG1垂直スクロール量$3FF
[$00:210F]BG2水平スクロール量$010
[$00:2110]BG2垂直スクロール量$3FF

となるまで毎フレーム+2され、8フレーム経過すると[$00:210D][$00:210F]$010まで増えて移動終了となる。
これが基本的なスクロール量設定の仕組みである。

また、上の結果だと、BG1とBG2のスクロール量は同一である。
最初に述べた通り、大半のマップは画面手前からBG3→BG2→スプライト→BG1と重ねる。
BG1が一番下で、キャラの手前に設置したい壁や樹木などはBG2に置き、間にキャラのスプライトを挟んで、一番手前にセリフウィンドウなどのBG3、というのが一般的である。
このため、キャラが移動した時、通常、BG1とBG2はスクロール量が同じである。
BG1とBG2でスクロール量が異なったら、キャラのみならず、壁や樹木も移動しているように見えてしまうはずだ。

多重スクロール

実際には、本作において、BG1とBG2でスクロール量を変えて距離感(遠近感)を出しているマップが幾つかある。
わかりやすいのは、高い場所で遠景が見渡せる場所である。
キャラを操作可能なマップなら、功夫編の大志山山頂、中世・最終編のルクレチア城テラス、勇者の山山小屋マップや山頂、魔王山山頂などが該当する。
これらのマップでは、BG1に実際にキャラが移動するマップを置き、BG2はその後ろ(一番下)に置いて遠景とし、BG2はBG1よりスクロール量を少なくすることで、BG2が遠くにあることが表現できる。
このように、背景ごとにスクロール量を変えて距離感(遠近感)を演出する方法はレトロゲームにおいて多重スクロールと呼ばれることが多いようだが、きちんとした専門用語かは筆者にはわからない。とりあえず本コンテンツでは多重スクロールとしておく。

多重スクロールを使っているマップのほとんどは、諸々の事情でBG2のサイズを大きめに設定しているようである(64×64タイル)。
BGマップ設定のサブルーチン$C0/0FB1$C0/1007で、BG1のBGマップ設定[$00:2107]と、BG2のBGマップ設定[$00:2108]が書き込まれているが、この時、[$00:0114]の値がBG1のマップサイズ、[$00:0116]の値がBG2のマップサイズに関わってくる(詳細は背景グラフィックと効果 > フィールドでのサブルーチン)。
[$00:0116]の値は多重スクロールのマップかどうかの判定値でもある。

なお、同じBGの中であっても、HDMA転送を利用することで、複数箇所のスクロール量を変更することもできる。
その例がSF編ラストバトル前にOD-10が語りかけてきた時のラスタースクロール効果だったり、ゲームのタイトル画面で雲の速度を少しずつ変える効果だったりするのだが、HDMA転送を使ったスクロールについては次ページとする。
ここでは、BG1とBG2でスクロール量を変える場合の多重スクロールの例として、ルクレチア城テラスの場合を紹介する。

まず、ルクレチア城テラスに入った時のマップ切り替え処理を見てみる。
つまり、$C0/4F80~の処理である。
多重スクロールがないマップでは、[$00:0A38][$00:0A3F]$00で初期化されていた。
だが、ルクレチア城テラスだと以下のようになる。

$C0/4F80 LDA $0114  [$00:0114]   ;Aに[$00:0114]をロード
$C0/4F83 BNE $0B    [$4F90]      ;ゼロフラグが立っていないとき[$4F90]分岐
$C0/4F85 LDX #$0000              ;Xに$0000をロード
$C0/4F88 STX $0A38  [$00:0A38]   ;Xを[$00:0A39][$00:0A38]に書き込み
$C0/4F8B STX $0A3C  [$00:0A3C]   ;Xを[$00:0A3D][$00:0A3C]に書き込み
$C0/4F8E BRA $30    [$4FC0]      ;フラグにかかわりなく常に分岐[$4FC0]
$C0/4FC0 LDA $0117  [$00:0117]   ;Aに[$00:0117]($00)をロード
$C0/4FC3 BNE $13    [$4FD8]      ;ゼロフラグが立っていないとき[$4FD8]分岐
$C0/4FC5 LDA $0116  [$00:0116]   ;Aに[$00:0116]($03)をロード
$C0/4FC8 BNE $19    [$4FE3]      ;ゼロフラグが立っていないとき[$4FE3]分岐
;ゼロフラグOFF
$C0/4FE3 STZ $30    [$00:0030]   ;[$00:0030]に$00を書き込み
$C0/4FE5 LDA $0128  [$00:0128]   ;Aに[$00:0128](00)をロード
$C0/4FE8 STA $31    [$00:0031]   ;Aを[$00:0031]に書き込み
$C0/4FEA STZ $32    [$00:0032]   ;[$00:0032]に$00を書き込み
$C0/4FEC LDA $012A  [$00:012A]   ;Aに[$00:012A](00)をロード
$C0/4FEF STA $33    [$00:0033]   ;Aを[$00:0033]に書き込み
$C0/4FF1 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/4FF3 LDA $0A40  [$00:0A40]   ;Aに[$00:0A41][$00:0A40](0C00)をロード
$C0/4FF6 SEC                     ;キャリーフラグON
$C0/4FF7 SBC $30    [$00:0030]   ;A(0C00) - [$00:0031][$00:0030](0000)
$C0/4FF9 LSR A                   ;Aを論理右シフト (/2)
$C0/4FFA STA $30    [$00:0030]   ;A(0600)を[$00:0031][$00:0030]に書き込み
$C0/4FFC LDA $0A42  [$00:0A42]   ;Aに[$00:0A43][$00:0A42](0700)をロード
$C0/4FFF SEC                     ;キャリーフラグON
$C0/5000 SBC $32    [$00:0032]   ;A(0700) - [$00:0033][$00:0032](0000)
$C0/5002 LSR A                   ;Aを論理右シフト (/2)
$C0/5003 STA $32    [$00:0032]   ;A(0380)を[$00:0033][$00:0032]に書き込み
$C0/5005 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/5007 LDA $0116  [$00:0116]   ;Aに[$00:0116]をロード
$C0/500A CMP #$01                ;Aと$01を減算比較(ステータスレジスタ変更のみ)
$C0/500C BEQ $10    [$501E]      ;ゼロフラグが立っているとき[$501E]分岐
$C0/500E CMP #$02                ;Aと$02を減算比較(ステータスレジスタ変更のみ)
$C0/5010 BEQ $19    [$502B]      ;ゼロフラグが立っているとき[$502B]分岐
$C0/5012 LDX $30    [$00:0030]   ;Xに[$00:0031][$00:0030]($0600)をロード
$C0/5014 STX $0A3A  [$00:0A3A]   ;Xを[$00:0A3B][$00:0A3A]に書き込み
$C0/5017 LDX $32    [$00:0032]   ;Xに[$00:0033][$00:0032]($0380)をロード
$C0/5019 STX $0A3E  [$00:0A3E]   ;Xを[$00:0A3F][$00:0A3E]に書き込み
$C0/501C BRA $18    [$5036]      ;フラグにかかわりなく常に分岐[$5036]
;処理合流
$C0/5036 RTS                     ;サブルーチン戻り

多重スクロールするマップでは、[$00:0116]$00以外、おそらく$01$03、だいたいの場合で$03が入るようである(通常は$00)。
このため、多重スクロールするマップだと$C0/4FC8でゼロフラグが立たずに、$C0/4FE3に処理が進む。
$C0/4FC8までは共通なので、[$00:0A39], [$00:0A38], [$00:0A3D], [$00:0A3C]$00が書き込まれることは変わらない。
つまり、後で処理されるBG1水平スクロール量[$00:210D]と、BG1垂直スクロール量[$00:210E]の初期値は、他のマップと変わらない。

その後の処理を追ってみると、

  • [$00:0A3B][$00:0A3A] = ([$00:0A41][$00:0A40]-[$00:0128]*$100)/2
  • [$00:0A3F][$00:0A3E] = ([$00:0A43][$00:0A42]-[$00:012A]*$100)/2

となっている。
つまり、BG2水平スクロール量[$00:210F]を決める[$00:0A3B][$00:0A3A]、BG2垂直スクロール量[$00:2110]を決める[$00:0A3F][$00:0A3E]の計算方法が異なる。
多重スクロールのマップだと、BG2は一番下で、BG1よりスクロール量を少なく設定する都合から、座標位置を合わせる都合で計算方法が変わるようである。

なお、[$00:0A41][$00:0A40][$00:0A43][$00:0A42]は、スクロール量計算に必要な、マップに入った位置に対する相対座標ではなく、背景(BG)の一定の位置を原点にした相対座標のようである。
そこからマップ開始位置(おそらく[$00:0128][$00:012A])を引いて、スクロール量が半分だから2で割る、というような計算だと思われる。

結果として上のように、[$00:0A3B][$00:0A3A] = $0600, [$00:0A3F][$00:0A3E] = $0380と設定されたため、ルクレチア城テラスに入った時のスクロール量は以下のようになる。

  • [$00:0A39][$00:0A38]/$10 = $0000/$10[$00:210D]2度書き
  • [$00:0A3D][$00:0A3C]/$10 - 1 = $0000/$10 - 1[$00:210E]2度書き
  • [$00:0A3B][$00:0A3A]/$10 = $0600/$10[$00:210F]2度書き
  • [$00:0A3F][$00:0A3E]/$10 - 1 = $0380/$10 - 1[$00:2110]2度書き
アドレス内容
[$00:210D]BG1水平スクロール量$000
[$00:210E]BG1垂直スクロール量$3FF
[$00:210F]BG2水平スクロール量$060
[$00:2110]BG2垂直スクロール量$037

ということが、$C0/53C0$C0/541Cの処理で決まる。
このように、多重スクロールなしのマップとはBG2スクロール量がまったく変わる。

さて、ここで右にダッシュ移動するとどうなるか。
多重スクロールなしのマップなら、BG1水平スクロール量とBG2水平スクロール量が同じ量だけ増加するが、多重スクロールありだと増加量が変わるはずだ。

$C0/0CAC LDA $0167  [$00:0167]   ;Aに[$00:0167]をロード
$C0/0CAF REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/0CB1 LSR A                   ;Aを論理右シフト (/2)
$C0/0CB2 STA $30    [$00:0030]   ;Aを[$00:0031][$00:0030]に書き込み
$C0/0CB4 BCS $09    [$0CBF]      ;キャリーフラグが立っているとき[$0CBF]分岐
$C0/0CBF LDA $01AE  [$00:01AE]   ;Aに[$00:01AF][$00:01AE]($0020)をロード
$C0/0CC2 STA $32    [$00:0032]   ;Aを[$00:0033][$00:0032]に書き込み
$C0/0CC4 LSR A                   ;Aを論理右シフト (/2)
$C0/0CC5 STA $34    [$00:0034]   ;Aを[$00:0035][$00:0034](0010)に書き込み
$C0/0CC7 LSR $30    [$00:0030]   ;[$00:0031][$00:0030]を論理右シフト (/2)
$C0/0CC9 BCS $46    [$0D11]      ;キャリーフラグが立っているとき[$0D11]分岐
$C0/0D11 LDX #$0A20              ;Xに$0A20をロード
$C0/0D14 STX $30    [$00:0030]   ;Xを[$00:0031][$00:0030]に書き込み
$C0/0D16 LDX #$0A38              ;Xに$0A38をロード
$C0/0D19 LDY #$0A40              ;Yに$0A40をロード
$C0/0D1C LDA $0000,y[$00:0A40]   ;Aに[$00:0A41][$00:0A40](0C00)をロード
$C0/0D1F CLC                     ;キャリーフラグクリア
$C0/0D20 ADC $32    [$00:0032]   ;A + [$00:0033][$00:0032](0020)
$C0/0D22 STA $0000,y[$00:0A40]   ;Aを[$00:0A41][$00:0A40]に書き込み
$C0/0D25 LDA $00,x  [$00:0A38]   ;Aに[$00:0A39][$00:0A38]をロード
$C0/0D27 CLC                     ;キャリーフラグクリア
$C0/0D28 ADC $32    [$00:0032]   ;A + [$00:0033][$00:0032](0020)
$C0/0D2A STA $00,x  [$00:0A38]   ;Aを[$00:0A39][$00:0A38]に書き込み
$C0/0D2C INX                     ;Xをインクリメント +1
$C0/0D2D INX                     ;Xをインクリメント +1
$C0/0D2E SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/0D30 LDA $0117  [$00:0117]   ;Aに[$00:0117]をロード
$C0/0D33 BNE $35    [$0D6A]      ;ゼロフラグが立っていないとき[$0D6A]分岐
$C0/0D35 LDA $0116  [$00:0116]   ;Aに[$00:0116]をロード
$C0/0D38 BEQ $25    [$0D5F]      ;ゼロフラグが立っているとき[$0D5F]分岐
;ゼロフラグOFF(多重スクロールマップ)
$C0/0D3A CMP #$03                ;Aと$03を減算比較(ステータスレジスタ変更のみ)
$C0/0D3C BEQ $14    [$0D52]      ;ゼロフラグが立っているとき[$0D52]分岐
;ゼロフラグON
$C0/0D52 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/0D54 LDA $00,x  [$00:0A3A]   ;Aに[$00:0A3B][$00:0A3A]をロード
$C0/0D56 CLC                     ;キャリーフラグクリア
$C0/0D57 ADC $34    [$00:0034]   ;A + [$00:0035][$00:0034](0010)
$C0/0D59 STA $00,x  [$00:0A3A]   ;Aを[$00:0A3B][$00:0A3A]に書き込み
$C0/0D5B SEP #$20                ;MフラグON Aレジスタは8bit幅
$C0/0D5D BRA $0B    [$0D6A]      ;フラグにかかわりなく常に分岐[$0D6A]
$C0/0D6A LDA $0114  [$00:0114]   ;Aに[$00:0114]をロード
$C0/0D6D BEQ $53    [$0DC2]      ;ゼロフラグが立っているとき[$0DC2]分岐
$C0/0DC2 RTS                     ;サブルーチン戻り

先に述べた通り、$C0/0CBFで呼び出す[$00:01AF][$00:01AE]は移動速度によって変動する値であり、おぼろ丸以外のダッシュなら$0020である。
この値は[$00:0033][$00:0032]にコピーされるのだが、さらに、$C0/0CC5にて、÷2された値が[$00:0035][$00:0034]に入る。
おぼろ丸以外のダッシュの時、

  • [$00:0033][$00:0032] = $0020
  • [$00:0035][$00:0034] = $0010

ということである。
その後の処理を追っていくと、BG1のスクロール量の計算$C0/0D25$C0/0D2Aは通常マップと変わらず、[$00:0A39][$00:0A38]を+$0020している。
その後は$C0/0D35により[$00:0116]の値で分岐する。つまり多重スクロールのマップかどうかで分岐し、[$00:0116]$03の時は、

[$00:0A3B][$00:0A3A] = [$00:0A3B][$00:0A3A] + [$00:0035][$00:0034] ($0010)

BG2のスクロール量を決める[$00:0A3B][$00:0A3A]は、BG1の半分である$0010しか加算されない。
つまり、

[$00:0116]$03に設定されたマップでは、BG2のスクロール量をBG1の1/2とする。

ということがわかる。
これはルクレチア城テラスだけではなく、勇者の山や魔王山山頂、功夫編の大志山山頂でも同一である。
左右移動だけでなく上下移動でもスクロール量の半減は変わらない。

以上で、[$00:0A39][$00:0A38]+$0020[$00:0A3B][$00:0A3A]+$0010された。
後は、$C0/53C0$C0/541Cの処理で、[$00:210D][$00:2110]を更新し画面をスクロールする。
先に述べたが、$C0/53C0$C0/541Cの処理をまとめると、

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

このようになっていた。
よって、BG1水平スクロール量[$00:210D]は通常のダッシュ同様、1フレームで+2である。
だが、BG2水平スクロール量[$00:210F]は、増加量が半分となる。つまり1フレームで+1となる。
これが、多重スクロールでBG毎にスクロール量を変動させる仕組みである。

ところで、通常歩行だと、スクロール量はダッシュの半分である。
歩行の場合、[$00:0A39][$00:0A38]+$0010[$00:0A3B][$00:0A3A]+$0005となる。

  • BG2水平スクロール量[$00:210F] = [$00:0A3B][$00:0A3A]/$10
  • BG2垂直スクロール量[$00:2110] = [$00:0A3F][$00:0A3E]/$10 - 1

だから、元の値によっては、+$0005されてもBG2のスクロール量が変化しないフレームがある。
もっと具体的にいえば、多重スクロールマップでの歩行時、BG1スクロール量は1フレームで+1だが、BG2スクロール量は2フレームで+1である。2フレームに1回しかスクロールしない、ということになる。

なお、多重スクロールするマップは、画面手前から、

BG3→スプライト→BG1→BG2(遠景)

という順に画面を構成している。
多重スクロールしないマップでは、

BG3→BG2(樹木や壁など)→スプライト→BG1

としていたが、多重スクロールするマップで、スプライト(キャラ)の前に樹木などを置きたい時はどうするのか?
この場合は、

BG3→BG1(優先度1)→スプライト→BG2(遠景)

という方法で、該当部分のBG1タイルのみ、スプライト手前に設置している。
ルクレチア城テラスなら、手すりの部分が該当する。

余談

$C0/0CAC$C0/0DC2の処理は途中で、多重スクロールのマップかどうかを[$00:0116]の値で判断し分岐する。
$C0/0CAC$C0/0DC2のサブルーチン全体を見ると、[$00:0116]に入る値は$00$03ということではなく、$01$02も入ることがわかる。
途中から処理を記すと、

$C0/0D3A CMP #$03                ;Aと$03を減算比較(ステータスレジスタ変更のみ)
$C0/0D3C BEQ $14    [$0D52]      ;ゼロフラグが立っているとき[$0D52]分岐
;ゼロフラグOFF([$00:0116]が$00でも$03でもない)
$C0/0D3E CMP #$01                ;Aと$01を減算比較(ステータスレジスタ変更のみ)
$C0/0D40 BEQ $09    [$0D4B]      ;ゼロフラグが立っているとき[$0D4B]分岐
;ゼロフラグOFF([$00:0116]が$00でも$03でも$01でもない)
$C0/0D42 LDA $0167  [$00:0167]   ;Aに[$00:0167]をロード
$C0/0D45 CMP #$02                ;Aと$02を減算比較(ステータスレジスタ変更のみ)
$C0/0D47 BCC $21    [$0D6A]      ;キャリーフラグが立っていないとき[$0D6A]分岐
$C0/0D49 BRA $07    [$0D52]      ;フラグにかかわりなく常に分岐[$0D52]
;$C0/0D40でゼロフラグON([$00:0116]が$01)
$C0/0D4B LDA $0167  [$00:0167]   ;Aに[$00:0167]をロード
$C0/0D4E CMP #$02                ;Aと$02を減算比較(ステータスレジスタ変更のみ)
$C0/0D50 BCS $18    [$0D6A]      ;キャリーフラグが立っているとき[$0D6A]分岐
$C0/0D52 REP #$20                ;Aを16bit幅に変更、Mフラグをクリア
$C0/0D54 LDA $00,x  [$00:0A3A]   ;Aに[$00:0A3A]をロード

$02が入るのは西部編オープニングなどで、荒野を馬にのったサンダウンが駆けていくシーン。
BG1が1フレームに2ピクセル、BG2が1フレームに1ピクセルずつマイナスされて左移動していくため、多重スクロールの仕組みとしてはルクレチア城テラスなどと変わらない。
ここが特殊な処理扱いなのは左右でループするマップだからであろう。
イベント関連で多重スクロールが使われているシーンは多く、$01$02で処理しているようである(未確認)。

補足 無限ループとスクロール

通常のフィールドやマップは、移動すると壁にぶつかったり他のマップへの出入り口に到達して、どこかでスクロールが止まる。
だが、本作の一部のマップは上下や左右がいつまでも無限にスクロールする。
原始編のポゴの夢の花畑や(実際には100マス移動でイベントが始まったりするため、プレイヤーが無限に移動することはできない)、最終編でオルステッド以外を主人公にしたときに飛ばされる異次元空間、知のダンジョンの横方向無限ループフロアなど。
こういうマップでは、ループする時に上下左右に新たにマップをくっつけるような処理をする必要はない。
例えばBG1なら[$00:2107]で背景サイズを決めて、縦32×横32タイルで並べるとか、本作のほとんどのマップで採用されている縦32×横64タイルにするなど設定をする。
この時に、例えば縦32×横64なら、真下にミラーリングで縦32×横64がもうひとつ並ぶので、上下に関してはおなじ背景がふたつ並ぶ。
さらに、スクロール量は$000$3FF、10進数01023(ピクセル)、つまり縦64×横64タイルであり、01023が無限ループする。
つまり、壁など障害物がない限り、真っ平らのマップなら、上下左右移動すると背景が無限スクロールすることになる。

例えば最終編・異次元空間だと、縦64×横32タイルのマップであり、上下左右が無限ループする。
100マス歩いた時に出現する階段以外の障害物はないから、階段を避ければ上下左右ひたすら移動することで水平スクロール量・垂直スクロール量が01023で無限ループする。
横方向で考えると、水平スクロール量が$000の時、

実際には上図の中で、枠で囲った範囲、つまり垂直スクロール量で指定した縦224ピクセル部分だけがゲーム画面に表示される。
水平スクロール量を256ピクセルのちょうど中央あたりの$080に設定すると、

このように元の画像を横方向につなげて、その途中がゲーム画面に表示されるようなイメージと考えていただきたい。
元背景画像の右から$80$FFピクセル(右半分)は、これといって特別な設定をしなくても、ゲーム画面左側に表示される。
これが無限ループの基本的な仕組みになる。

次ページでは、無限ループを利用したHDMA転送による多重スクロールを紹介する。



このページをシェアする

上へ