基礎知識
戦闘関係
コンピュータゲームでは、プレイヤーが何らかの操作をしなければゲームが進行しない。
スーパーファミコンのゲームでいえば、プレイヤーがコントローラのボタンを押さなければゲームは進行しない。
ここでは、ゲームにおける基本中の基本、コントローラのボタンを押したかどうか、ボタン判定について説明する。
本作におけるプログラムの紹介だが、おそらくスーパーファミコンのゲームならどれも似たような処理でボタン判定をしているはずである。
スーパーファミコンのコントローラ周りの説明は以下も参照にさせていただいた。
上サイトなどでも記述があるが、ゲームのコントローラをジョイパッド(Joypad)とも呼ぶ。意味は同じ。
ライブ・ア・ライブにおいて、ゲーム操作に必要なのはコントローラ1個(1P用のコネクタに接続)のみである。
コントローラのボタンは12個。
ボタン判定の仕組みだが、スーパーファミコン内部には、「Auto Joypad Read(自動ジョイパッド読み取り)」という機能があり、これを利用している。
$00:4200が、割り込み有効化レジスタというアドレスにあたり、8ビットの値の一番下のビットに1を入れると自動ジョイパッド読み取り機能をONにしてくれる。
アドレス$00:4212に入る値が、自動ジョイパッド読み取り機能のON・OFF状態を記録しており、8ビットの値の一番下に1の時、自動ジョイパッド読み取り機能がON、0ならOFFを表す。
自動ジョイパッド読み取り機能がONの間、$00:4218~$00:4219の2バイトの領域に、コントローラ1の何のボタンが押されているかを出力する仕組みである。
ちなみにコントローラ2の状態は$00:421A~$00:421Bの2バイトに出力されるそうだが、本作においては特に関係ない。
自動ジョイパッド読み取り機能はONにしてから一定時間有効で、その時間は4224マスターサイクルである。4224マスターサイクル経過後、自動的にOFFになる。
スーパーファミコンの中の水晶発振器という部品に電圧をかけると振動することを利用して電気信号を送信する仕組みがあるのだが、1秒間に2,100万回以上というとんでもない回数で振動している(21.4772700MHz)。この振動した回数4224回分の時間が4224マスターサイクルで、4224/21477270秒にあたる。
……数字だけ見てもよくわからないが、0.0002秒くらいであり、1/60秒を単位とするフレームで表すと0.01フレームくらいである。
とにかく、ほんの短時間しかボタンが押されているか判定しないことになるが、この判定を1フレーム毎に行えば、ボタンを押して1フレーム以内にボタンを押しているかどうか判断してくれることになる。
本作をプレイしていて、ボタンの反応が遅いと感じることはまずないだろう。
$00:4218~$00:4219の2バイトに出力される値は以下のようになっている。
$00:4218が下位バイト、$00:4219が上位バイト。
ボタンを同時押しした場合は合計値が出力される。
| ボタン | 16進数2バイト | 2進数16ビット |
|---|---|---|
| R | $0010 | %0000 0000 0001 0000 |
| L | $0020 | %0000 0000 0010 0000 |
| X | $0040 | %0000 0000 0100 0000 |
| A | $0080 | %0000 0000 1000 0000 |
| 右 | $0100 | %0000 0001 0000 0000 |
| 左 | $0200 | %0000 0010 0000 0000 |
| 下 | $0400 | %0000 0100 0000 0000 |
| 上 | $0800 | %0000 1000 0000 0000 |
| スタート | $1000 | %0001 0000 0000 0000 |
| セレクト | $2000 | %0010 0000 0000 0000 |
| Y | $4000 | %0100 0000 0000 0000 |
| B | $8000 | %1000 0000 0000 0000 |
例えば右ボタンを押した場合、$00:4218に$00、$00:4219に$01が出力される。
右方向にダッシュをする時は、Bボタンと右ボタンを同時押しするから、$0100と$8000を加算し、$00:4218に$00、$00:4219に$81が出力される。
といった仕組みである。
上の通りに$00:4218の下1桁にはボタンに対応した数値がない。
よって、ボタンの状態は、$00:4219・$00:4218の2バイト(16進数4桁)の中で上3桁で表されるということがわかる。
ボタンの出力先は上の通り$00:4218~$00:4219なのだが、この値は他アドレスにもコピーされて各種判定に使われる。
| フィールド | $00:0000 | $00:0001$00:020A |
|---|---|---|
| 戦闘 | $00:0344$00:0348 | $00:0345$00:0349 |
| メニュー画面 | $00:057D | $00:057E |
| R | $10 | - |
| L | $20 | - |
| X | $40 | - |
| A | $80 | - |
| 右 | - | $01 |
| 左 | - | $02 |
| 下 | - | $04 |
| 上 | - | $08 |
| スタート | - | $10 |
| セレクト | - | $20 |
| Y | - | $40 |
| B | - | $80 |
つまり、[$00:4219][$00:4218]の2バイトは以下のようにコピーされる。
| 元アドレス | [$00:4219] | [$00:4218] |
|---|---|---|
| フィールド | [$00:0001]- | [$00:0000][$00:020A] |
| 戦闘 | [$00:0345][$00:0349] | [$00:0344][$00:0348] |
| メニュー画面 | [$00:057E] | [$00:057D] |
実際の判定のサブルーチンだが、フィールド・戦闘・メニュー画面でアドレスが異なる。
「自動ジョイパッド読み取り機能がONの間、$00:4218~$00:4219の値を読み取る」という処理自体は変わらない。
本作では、フィールドでのサブルーチンはバンク$C0、戦闘中のサブルーチンはバンク$C1、メニュー画面のサブルーチンはバンク$C2に収納されている。
アドレスの値が近い方が呼び出しに時間がかからないので、フィールド・戦闘・メニュー画面でそれぞれボタン判定サブルーチンを用意しているようだ。
基本の処理は以下の通り。
;ループ処理開始 LDA $4212 [$00:4212] ;Aに[$00:4212]をロード AND #$01 ;Aと$01で論理積 BNE (ループ頭) ;ゼロフラグが立っていないときループ頭に戻る ;ループ処理ここまで ;以降16bitモードのAレジスタか、 LDA $4218 [$00:4218] ;Aに[$00:4219][$00:4218]をロード ;または16bitモードのXレジスタで処理 LDX $4218 [$00:4218] ;Xに[$00:4219][$00:4218]をロード
[$00:4212]の一番下1ビットに1が入っている時は、[$00:4219][$00:4218]にボタンの押されている状態を出力し続けている。
上に記した通り、自動ジョイパッド読み取り機能は4224マスターサイクル経過すると自動終了だが、その時に[$00:4212]の一番下1ビットが0となって、自動ジョイパッド読み取り機能終了寸前のボタンの状態が[$00:4219][$00:4218]に残る。
自動ジョイパッド読み取り機能終了のタイミングのボタンの状況を読み出すのが上のサブルーチンになる。
ループ処理で、[$00:4212]と$01の論理積を取り、[$00:4212]の下1ビットが1ならゼロフラグが立たないのでループ頭に戻る。
このループは、[$00:4212]の下1ビットが0になるまで、つまり自動ジョイパッド読み取り機能がOFFになるまで続く。
自動ジョイパッド読み取り機能がOFFになると[$00:4219][$00:4218]をロードして、ボタン状況を読み出し、各種判定を行うことになる。
判定中、何のボタンも押されていないのなら、[$00:4219][$00:4218]には$0000が入るが、何かのボタンが押されていたら$0000以外が入る。
右ボタンを押せば、$00:4218に$00、$00:4219に$01が出力され、状況により右ボタンに対応した処理を行うことになる。
もし[$00:4219][$00:4218]が$0000、つまり何のボタンも押されなかったとしても、何もしないままということはない。
フィールドであったら何のボタンも押されていなくても、1フレームに1回、フィールド乱数を生成しているし、幕末編のフィールドなどだったらタイマーイベントがタイマー残り時間を-1して、鐘を鳴らしたりもする。ランダム移動する街の人の移動処理も行う。
以下、フィールド・戦闘・メニュー画面それぞれのサブルーチンをざっと紹介しておく。
フィールドにいる時のボタン判定用サブルーチンは以下の通り。
フィールド上でも、メニュー画面を開いている時とは別になる。
$C0/B83E LDA #$01 ;Aに$01をロード ;ボタン判定ループ処理 $C0/B840 BIT $4212 [$00:4212] ;Aと[$00:4212]で論理積(ステータスフラグ変更のみ) $C0/B843 BNE $FB [$B840] ;ゼロフラグが立っていないとき[$B840]分岐 ;ボタン判定ループ処理ここまで $C0/B845 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C0/B847 LDA $4218 [$00:4218] ;Aに[$00:4219][$00:4218]をロード $C0/B84A BIT #$000F ;Aと$000Fで論理積(ステータスフラグ変更のみ) $C0/B84D BEQ $03 [$B852] ;ゼロフラグが立っているとき[$B852]分岐 ;ゼロフラグOFF $C0/B84F LDA #$0000 ;Aに$0000をロード ;ゼロフラグON $C0/B852 TAX ;Aの値をXレジスタに転送 $C0/B853 EOR $00 [$00:0000] ;Aと[$00:0001][$00:0000]で排他的論理和XOR $C0/B855 STX $00 [$00:0000] ;Xを[$00:0001][$00:0000]に書き込み $C0/B857 AND $00 [$00:0000] ;Aと[$00:0001][$00:0000]で論理積 $C0/B859 STA $04 [$00:0004] ;Aを[$00:0005][$00:0004]に書き込み $C0/B85B STA $08 [$00:0008] ;Aを[$00:0009][$00:0008]に書き込み
まず$C0/B83Eで$01をロードし、[$00:4212]と論理積を取って、$C0/B843でゼロフラグ判定をし、ゼロフラグが立っていないなら$C0/B840にジャンプする。
つまり、ゼロフラグが立つまで、$C0/B840と$C0/B843をループすることになる。
$C0/B83Eで$01= %0000 0001 をロードしたから、$01と[$00:4212]の論理積でゼロフラグが立つのは、[$00:4212]の下1ビットが0の時。
先に説明したとおり、[$00:4212]の下1ビットは、自動ジョイパッド読み取り機能がONかOFFかの状態を示す。下1ビットが1ならON、0ならOFF。
ボタン判定ループ処理を抜けるのは、自動ジョイパッド読み取り機能がOFFになったタイミングになる。
自動ジョイパッド読み取り機能がONの間は[$00:4219][$00:4218]にボタンの状態を出力し続けるので、ループを抜けた$C0/B847でロードする[$00:4219][$00:4218]は、自動ジョイパッド読み取り機能がOFFになる直前のボタンの状態である。
$C0/B84A~では、[$00:4219][$00:4218]と$000Fで論理積を取ってゼロフラグ判定をしている。
ボタンの状況は、[$00:4219][$00:4218]の16進数4桁の値の中で、上3桁で表されるため、[$00:4219][$00:4218]と$000Fで論理積を取ると上3桁の値がすべて0となり、通常は$0000となってゼロフラグが立つはずである(おそらく)。
よって$C0/B84Dのゼロフラグ判定が立たないことは多分ない。次の$C0/B852へジャンプする。
ゼロフラグが立っている場合はAに$0000をロードして進む。
フィールドでは、[$00:4219][$00:4218]の値が[$00:0001][$00:0000]と同一なのだが、ここから[$00:0001][$00:0000]へのコピーが開始になっている。
Aに入っている[$00:4219][$00:4218]をXレジスタにコピーする。
このXは$C0/B855で[$00:0001][$00:0000]に書き込まれるので、[$00:4219][$00:4218]→Xレジスタ→[$00:0001][$00:0000]である。
これで入力されたボタンがほぼリアルタイムに[$00:0001][$00:0000]に反映されることになる。
$C0/B853~の処理は、ひとつ前のボタンの状況から、ボタンを押しっぱなしか、それとも新たに更新されたか、という判定用の計算である。
$C0/B853は、[$00:4219][$00:4218]と[$00:0001][$00:0000]の排他的論理和の計算になる。
排他的論理和は、2進数で各桁を比較した時、どちらかが1なら1を、それ以外は0を返す論理演算である。
この時点の[$00:0001][$00:0000]は、ひとつ前のボタンの状況が入っている。
たとえば、直前まで右ボタンを押していたら[$00:0001][$00:0000] = $0100 = %0000 0001 0000 0000である。
今回の判定では右ボタンを押すのを止めていれば、[$00:4219][$00:4218] = $0000 = %0000 0000 0000 0000である。
排他的論理和を取ると右ボタンの1だけ残り、%0000 0001 0000 0000 = $0100である。
一方、右ボタンを押しっぱなしだったとしたら、右ボタンの1と1で排他的論理和は0となり、%0000 0000 0000 0000 = $0000である。
要するに、直前までのボタンの状況と変化がない場合は排他的論理和が$0000、変化がある場合はそれ以外の値になる。
$C0/B857は、この排他的論理和の結果と、[$00:0001][$00:0000](現在のボタンの状況)の論理積をとって、[$00:0005][$00:0004]及び、[$00:0009][$00:0008]に書き込む。
直前までのボタンの状況と変化がない場合、$0000と[$00:0001][$00:0000](現在のボタンの状況)の論理積になるから、必ず$0000である。
それ以外なら新たに変化があったボタンの状態が[$00:0005][$00:0004]及び、[$00:0009][$00:0008]に入る。
ここまででとりあえず押したボタンの値の読み取りと保存は完了となる。
以降はどのボタンを押したか、またはボタンを離したか、という判定になる……と思いきや、実は次に以下のような処理がある。
$C0/B85D LDA $421A [$00:421A] ;Aに[$00:421B][$00:421A]をロード $C0/B860 BIT #$000F ;Aと$000Fで論理積(ステータスフラグ変更のみ) $C0/B863 BEQ $03 [$B868] ;ゼロフラグが立っているとき[$B868]分岐 ;ゼロフラグOFF $C0/B865 LDA #$0000 ;Aに$0000をロード ;ゼロフラグON $C0/B868 TAX ;Aの値をXレジスタに転送 $C0/B869 EOR $02 [$00:0002] ;Aと[$00:0003][$00:0002]で排他的論理和XOR $C0/B86B STX $02 [$00:0002] ;Xを[$00:0003][$00:0002]に書き込み $C0/B86D AND $02 [$00:0002] ;Aと[$00:0003][$00:0002]で論理積 $C0/B86F STA $06 [$00:0006] ;Aを[$00:0007][$00:0006]に書き込み $C0/B871 STA $0A [$00:000A] ;Aを[$00:000B][$00:000A]に書き込み
最初にも書いたが、コントローラ2の状態は$00:421A~$00:421Bの2バイトに入る。
上の通り、実はコントローラ2のボタンの状態を、コントローラ1のボタンの状況とほぼ同じ判定で各アドレスに書き込んでいるのだ。
つまり、[$00:0003][$00:0002]はコントローラ2のボタンの状態が常に反映されている。
といっても、本作でコントローラ2を使う場面はない。
[$00:0003][$00:0002]を何かの処理に使うかどうかも筆者にはわからない。
開発中はコントローラ2を何かに使う予定があったのか、それともスーパーファミコンのゲームの共通処理という意味で形式的に入れておいたのか、そこまではわからない。
(基本的に1人用のRPGでコントローラ2を使う作品はさほどないが、「ライブ・ア・ライブ」と同時期のスクウェアのゲームだと、「聖剣伝説2」や「ファイナルファンタジー4」で、戦闘時仲間キャラをコントローラ2で操作できるという仕組みがあった)
なお、コントローラ2の判定は、戦闘やメニュー画面のボタン判定用サブルーチンにも残されている。
[$00:0001]→[$00:020A]のコピーは上処理の直後に行われている。
$C0/0926 LDA $01 [$00:0001] ;Aに[$00:0001]をロード $C0/0928 STA $020A [$00:020A] ;Aを[$00:020A]に書き込み
戦闘中のボタン判定用サブルーチンは以下の通り。
;ボタン判定ループ処理 $C1/8AD8 LDA $4212 [$00:4212] ;Aに[$00:4212]をロード $C1/8ADB AND #$01 ;Aと$01で論理積 $C1/8ADD BNE $F9 [$8AD8] ;ゼロフラグが立っていないとき[$8AD8]分岐 ;ボタン判定ループ処理ここまで $C1/8ADF LDX #$0000 ;Xに$0000をロード $C1/8AE2 LDA $4218 [$00:4218] ;Aに[$00:4218]をロード $C1/8AE5 BIT #$0F ;Aと$0Fで論理積(ステータスフラグ変更のみ) $C1/8AE7 BNE $03 [$8AEC] ;ゼロフラグが立っていないとき[$8AEC]分岐 $C1/8AE9 LDX $4218 [$00:4218] ;Xに[$00:4219][$00:4218]をロード $C1/8AEC STX $44 [$00:0344] ;Xを[$00:0345][$00:0344]に書き込み $C1/8AEE LDX #$0000 ;Xに$0000をロード $C1/8AF1 LDA $421A [$00:421A] ;Aに[$00:421B][$00:421A]をロード $C1/8AF4 BIT #$0F ;Aと$0Fで論理積(ステータスフラグ変更のみ) $C1/8AF6 BNE $03 [$8AFB] ;ゼロフラグが立っていないとき[$8AFB]分岐 $C1/8AF8 LDX $421A [$00:421A] ;Xに[$00:421B][$00:421A]をロード $C1/8AFB STX $46 [$00:0346] ;Xを[$00:0347][$00:0346]に書き込み $C1/8AFD RTS ;サブルーチン戻り (中略) $C1/8B15 LDX $44 [$00:0344] ;Xに[$00:0344]をロード $C1/8B17 STX $48 [$00:0348] ;Xを[$00:0348]に書き込み $C1/8B19 LDX $46 [$00:0346] ;Xに[$00:0346]をロード $C1/8B1B STX $4A [$00:034A] ;Xを[$00:034A]に書き込み $C1/8B1D RTS ;サブルーチン戻り
$C1/8AD8~$C1/8ADDのループで、[$00:4212]の下1ビットが0になるまで(自動ジョイパッド読み取り機能がOFFになるまで)ループを行う。
後の判定の方法はフィールドとだいたい同じで、[$00:4218]のみで$0Fと論理積を取ってゼロフラグ判定してから、現在のボタンの状況[$00:4219][$00:4218]を[$00:0345][$00:0344]に書き込む。
[$00:0345][$00:0344]は更に、$C1/8B15~で[$00:0349][$00:0348]にもコピーされる。
なお$C1/8AF1~$C1/8AFBはコントローラ2の処理である。
メニュー画面でのボタン判定用サブルーチンは以下の通り。
$C2/5178 PHP ;ステータスレジスタをスタックへプッシュ $C2/5179 SEP #$20 ;MフラグON Aレジスタは8bit幅 ;ボタン判定ループ処理 $C2/517B LDA #$01 ;Aに$01をロード $C2/517D AND $004212[$00:4212] ;Aと[$00:4212]で論理積 $C2/5181 BNE $F8 [$517B] ;ゼロフラグが立っていないとき[$517B]分岐 ;ボタン判定ループ処理ここまで $C2/5183 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C2/5185 LDA $004218[$00:4218] ;Aに[$00:4219][$00:4218]をロード $C2/5189 STA $7D [$00:057D] ;Aを[$00:057E][$00:057D]に書き込み
判定方法はフィールドや戦闘時と同様である。
[$00:4219][$00:4218]は[$00:057E][$00:057D]に書き込まれる。
ただし、メニュー画面の方ではコントローラ2判定はないようだ。