基礎知識
戦闘関係
先にスクロールをお読みいただきたい。
スクロールの解説の中で、多重スクロールについて説明した。
多重スクロールでは、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~72 | 4ピクセル/8フレーム |
| 73~104 | 2ピクセル/8フレーム |
| 105~136 | 1ピクセル/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] | OAMADDL | OAM下位アドレス | $00 |
[$00:2103] | OAMADDH | OAM上位アドレスとスプライト優先順位に | $00 |
[$00:2105] | BGMODE | BG制御レジスタ(BGMode設定) | $09 |
[$00:2106] | MOSAIC | モザイク表示用サイズ・スクリーン設定 | $00 |
[$00:2107] | BG1SC | BG1のBGマップ設定 | $41 |
[$00:2108] | BG2SC | BG2のBGマップ設定 | $51 |
[$00:2109] | BG3SC | BG3のBGマップ設定 | $48 |
[$00:210A] | BG4SC | BG4のBGマップ設定 | $00 |
[$00:210B] | BG12NBA | BG1,BG2キャラクタデータ領域設定 | $00 |
[$00:210C] | BG34NBA | BG3,BG4キャラクタデータ領域設定 | $03 |
[$00:210D] | BG1HOFS | BG1水平スクロール量 | $0000 |
[$00:210F] | BG2HOFS | BG2水平スクロール量 | $0000 |
[$00:2111] | BG3HOFS | BG3水平スクロール量 | $0000 |
[$00:210E] | BG1VOFS | BG1垂直スクロール量 | $FFFF |
[$00:2110] | BG2VOFS | BG2垂直スクロール量 | $FFFF |
[$00:2112] | BG3VOFS | BG3垂直スクロール量 | $FFFF |
[$00:2115] | VMAINC | ビデオポート調整 | $80 |
[$00:2123] | W12SEL | BG1,BG2ウィンドウマスク設定 | $00 |
[$00:2124] | W34SEL | BG3,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] | WBGLOG | BGウインドウマスクロジック設定 | $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] | BG1SC | BG1設定 | $42 横32×縦64タイル |
[$00:2108] | BG2SC | BG2設定 | $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]の初期値が計算・書き込まれる。
[$00:210E]:$00FF(2度書き込み)[$00:2110]:$00FF(2度書き込み)であるから、タイトル画面表示のタイミングで、縦方向には$00FFピクセルスクロールした状態で固定ということになる。
BG1は横32×縦64タイルで、タイトル画面時は下に32タイル、つまり$00FFピクセル移動した位置で固定させる、ということである。
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:3C6F | 6F FF 00 18 FF 00 58 FF 00 00 FF 00 55 55 55 55 |
$7F:3C70~$7F:3C7F | 48 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 | $48 | 0~72 |
| B | $20 | 73~104 |
| C | $20 | 105~136 |
| D | $58 | 137~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転送による多重スクロールを行っている。