■カスタムFCEU
ディレイスクロールをより簡単に起こすためには、
私が作成したディレイスクロール専用のカスタムFCEUを用いる。
下記の説明はrev5に基づいたものである。
ディレイスクロールカスタムFCEU rev.5 ダウンロード
ディレイスクロール支援LuaScript rev.7 for
FCEUX
2.1.2 ダウンロード
カスタムFCEUで行っていた処理をLuaでFCEUXで動作可能にしたもの。
メインルーチンの処理量計算や、音声の自動計算を搭載している。
X->X座標(Xスピード)
Y->Y座標(Yスピード)
PreNMI->メインルーチン前のNMIの処理量
MAIN ->メインルーチンの処理量
Total ->PreNMI+MAIN。9000よりやや小さいときディレイスクロール。
PRESCRL->Totalが大きいほど小さい値になる。
この値が4-13のとき音が良ければディレイ前画面スクロール。
「-」のときスクロール不可
NEXTSCRL->Totalが大きいほど小さい値になる。
この値が4-13のとき音が良ければディレイ次画面スクロール。
「-」のときスクロール不可
$14Set->Totalが大きい値ほど小さい値になる。
この値が4-13のとき音によりディレイ左端変化。
「-」のときディレイ左端変化不可。
MadSE->ディレイスクロールの条件1を満たしたとき、生じた狂ったYの値。
この結果がスクロール条件を満たしている必要あり。
SE->毎フレーム音声処理で出力されるYの値(そのフレームで生じると予想されるMadSEの値)
ディレイスクロールテスト用LuaScript for FCEUX
2.1.2 ダウンロード
レジスタに干渉してディレイスクロールを意図的に起こすスクリプト
セレクトで使用モードを切り替え
切り替えたからといって、元々対象となるディレイスクロールが
不可能な場所では使用することはできないようにセッティングしてある。
NONE:何もしない
PRE:ディレイ前画面スクロール
NEXT:ディレイ次画面スクロール
DEATH:ディレイ死、ディレイ梯子テク
■スクロール処理
スクロールの処理は簡単に表すと、以下のような流れになっている
詳細なトレースログはこちら
■ディレイスクロール発生の流れ
スクロール判定を呼び出している3処理の流れを確認してみる。
通常は以下の図の通り、「$38」の値によってYレジスタの値が決定され、
それによりスクロールするかどうかの判定などが行われている。
しかし、スクロール判定の中の、「バンク切り替え処理」でNMIが起きると、Yレジスタの値が狂うことがあるのだ。
■ディレイスクロールの原理図説
NMIとは、ファミコンで1/60秒ごとに必ず実行されるサブルーチンのことである。
(ロックマンでは、音声の同期処理に使われているようだ。)
以下はスクロールの瞬間のフレーム処理を簡単に表した図だ。
スクロールの瞬間のフレームに、NMIの処理や、メインルーチンの処理が多めの処理数のとき、
NMI待ち処理の前のスクロール処理中にNMIが発生することがある。
この位置が絶妙だと、スクロール判定に使われる値が狂うことがあるのだ。
■ディレイスクロール発生
スクロール判定を拡大してみると以下のような処理を行っている。
特定の箇所でNMIが起きると、Yの値がくるって出力されるというわけだ。
■ディレイスクロール発生の条件
つまり、ディレイスクロールがおきる条件は以下の通りである。
$C7A5から、NMI発生までの処理数を示したSCRLの値を調整する必要がある。
また、ディレイスクロールを起こす場合は、サウンドコードの戻り値である、Yレジスタの値ResSEの値を調整する必要がある。
パターン1 使用可能テク:ディレイスクロール、ディレイ死、ディレイ左端変化、ディレイスクロール先変化、ディレイ梯子テク
条件1 スクロール処理中に$C7A8直前のバンク切り替え処理中($C006〜$C019)でNMIが起きる
条件2 サウンドコードの戻り値から決定したAレジスタがスクロール判定の条件を満たす
SCRL の値を4-13に調整することが必要になる。
ResSE の値がスクロール条件を満たしている必要がある。
パターン2 使用可能テク:ディレイ死、ディレイ左端変化、ディレイスクロール先変化、ディレイ梯子テク
条件1 スクロール処理中の$C7A8直後のバンク切り替え処理($C006〜$C019)でNMIが起きる
SCRL の値を25-34に調整することが必要になる。
■ディレイスクロール試行(適当に調整してみる場合)
カスタムFCEU rev.5を用い、「sample4.lua」を読み込む。
ディレイスクロールとしては惜しいリプレイをいろいろ試行して出すところから始める。
SCRLの値を60以下にするのが目安である。
SCRLが61とかなり惜しい例が出た。
SCRLの欄に(+48)と表示されているのが判る。
これは、あと48処理分ディレイが足りないことを意味する。
そこで、(+48)と表示される2フレーム前に「スタート」を押してみる。
すると、SCRLの欄に(+1)という文字が見えた。
これはあと1処理追加すれば
$C006〜$C016の間でNMIが起きるという条件1を満たす惜しい例である。
このように最後の微調整はボタンを押すことによって行うことが多い。
(特にスタートボタンは40程度処理を増加させるのでかなり有効である)
スタート+左を押してみたところ、SCRLの欄に(0!)と表示され、パターン1の条件1を満たしたことがわかる。
しかし、実際には右スクロールはおきなかった。
これは、NMIによって呼び出された音楽コードの戻り値が運悪く「$0B」であり、
スクロール判定に使われるAレジスタの値RetSE(B400,Y)が$80となったためである。
つまりこの例は条件2を満たしていないことになる。
RetSEが満たすべきビットマスクは以下の通りである。
スクロール処理 | ビットマスク |
ディレイ下スクロール(次画面) ディレイ上スクロール(前画面) |
$40 |
ディレイ下スクロール(前画面) ディレイ上スクロール(次画面) |
$80 |
ディレイ右スクロール | $20 |
RetSEが「FF」ならば、これらの全てのスクロール条件を満たすことになる。
ディレイ死や、ディレイ梯子のときは逆にビットマスクを満たさないようにすれば良い。
では、サウンドコードSEの戻り値を調べよう。
ロックマン2では1フレームごとに必ず音楽コードが呼ばれており、
その値がカスタムFCEUではSEのところに表示される。
ディレイスクロールのときに呼び出されるサウンドコード戻り値も同じ処理を使っているので、
毎フレーム呼び出されるSEをメモっておけばある程度推定できるというわけだ。
ジャンプ音を混ぜた場合はフレームに関係なく戻り値が決定することが判った。
フレーム数 | 音楽コードの戻り値SE |
285〜303 | $1C |
304〜327 | $0B |
328〜338 | $1C |
ジャンプ音を混ぜた場合 | $00 |
$01 | |
$0B | |
$0F |
次に音楽コードの戻り値SEとその結果生じるAレジスタRetSEの対照表を見てみよう。
他のステージの対照表はカスタムFCEU rev.5の中に入っているので参照してほしい。
クラッシュマンステージのSEとAレジスタRetSEの対照表 | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
対照表からスクロール判定に使われるAレジスタの値RetSEが決定する。
フレーム数 | 音楽コードの戻り値SE | Aレジスタの値RetSE |
285〜303フレーム | $1C | $FF |
304〜327フレーム | $0B | $80 |
328〜338フレーム | $1C | $FF |
ジャンプ音を混ぜた場合 | $00 | $80 |
$01 | $80 | |
$0B | $80 | |
$0F | $20 |
今回の場合
右スクロールの条件(20のビットマスクを持つ)を満たすのは、
285〜303フレーム、328〜338フレームに「$1C」が返ってくる場合(音を混ぜなかった場合)と
ジャンプ音によって音楽コードが「$0F」を返した場合である。
ジャンプ音のほうはフレームを合わせる調整が難しいので、285フレームから303フレームの間に
ディレイ右スクロールが成功するように敵の動きを調整すると良さそうだ。
301フレーム目にSCRLが条件1を満たし
無事RetSEがFFとな
った結果条件2も満たされディレイ右スクロールが成功した。
このようにディレイスクロールでは、多くの場合はSCRLが小さくなるよう工夫した後に、
ボタン押しやロックマンの挙動を微妙に変更したり、狙うフレームを変更して調整という方法が一般的である。
■ディレイスクロールのための処理量調整
ディレイの調整のため処理量を調整する各種処理を頭に入れておく必要がある。
処理 | 処理内容 | |
NMI | NMI | カスタムFCEU rev.5ではNMIの欄に処理数が表示される。 処理量が800〜2000と音楽の処理により毎フレームかなりの変動を見せる。 |
メインルーチン | ロックマンの動き | あまり処理量に差異はでないが、微調整に使う。 スクロールの瞬間に振り向いたり、梯子に引き込まれる瞬間は処理量の増加が大きい。 |
ボタン押し | スタートボタンで処理量を40前後プラスすることができる。 また、ショットが打てないときでもショットボタンを押したり、 左右同時押しをかましたりすると、若干の増加が可能。 10〜50処理量程度のディレイが足りないような惜しい例のときの調整に良く使う。 |
|
敵の動き | 処理量が少ないときは非常に重要。 処理量が少ないときは、ディレイスクロールを成功させようとするフレームを NMIが重いフレームとし、更にそのフレームに敵の重い処理を重ねることになる。 重たいフレームはだいたい1フレームしかないので、 武器切り替えや、画面に入るタイミングの調整、画面端に触れるタイミング等で調整することになる。 現行のTASに使われている例では以下のような重いフレームを狙った例がある。 ・ウサギの振り向き+人参発射 ・2匹の蝙蝠が動き出す瞬間 ・メカドラゴンがブロックを蹴落とす瞬間 |
|
マップの読み込みなど | バブルマンステージのディレイ下スクロールでは、 画面右端のシャッターを丁度表示するタイミングで やや処理が重たくなるのを利用しており、 このタイミングにちょうどスクロール判定フレームを合わせている。 ジャストスクロールと呼ばれる処理量増加テクである。 |
|
敵を倒す | 敵を倒す処理が起きた瞬間は多少重たくなる。 更に、そのときにドロップされるアイテムの種類が 1UPや大エネルギーなどだと、更に少し重たくなる。 バブルマンステージのディレイ下スクロールでは、 ディレイ下スクロールする瞬間にメタルブレードにより 敵撃破+大エネルギー2個が出現している。 |
|
オブジェクト設置 | アイテム1号は1個につき500程度のディレイを追加できる有望なアイテムである。 その他の武器はあまり重くないが、場所によっては有効な場合もあるだろう。 |
■各種ディレイテク別解説
ディレイスクロール以外にも各種テクニックが存在するのでまとめておく。
スクロール判定は上図説の通り、2回走っていることが多い。
それぞれバンク切り替え処理が走るので、ディレイスクロール下、上スクロールでは前画面または次画面へ行くことができる場合がある。
また、通常時に下または上スクロールで前画面へ戻ることができる箇所の場合、左端読み込み処理の前に呼び出されるバンク切り替え処理にNMIを重ねることで
前画面へ戻ったときに左端が変化する。
テク名 | テクの概要 | 要求されるRetSEのビットマスク | 通常時のスクロール判定 | テク使用時のスクロール判定 | ||||
ディレイ右スクロール | 通常は右スクロールしない箇所で画面右端に触れる ディレイにより次画面へ右スクロール |
$20 | 前画面:常にNG 次画面:NG |
前画面:常にNG 次画面:OK(ディレイ) |
||||
ディレイシャッタースクロール | 通常は右スクロールしない箇所で画面左端シャッターに触れる ディレイにより次画面へ右スクロール |
$20 | 前画面:常にNG 次画面:NG |
前画面:常にNG 次画面:OK(ディレイ) |
||||
ディレイ下スクロール | 画面下が穴のとき、画面下に触れる ディレイにより前画面または、次画面へ下スクロール |
$80 | $40 | 前画面:NG 次画面:NG(穴判定) |
前画面:OK(ディレイ) 次画面:NG |
前画面:NG 次画面:OK(ディレイ) |
||
ディレイ上スクロール | 画面ずらしや新梯子などで上スクロールできない箇所の梯子に触れる ディレイにより前画面へ上スクロール |
画面ずらしや新梯子などで上スクロールできない箇所の梯子に触れる ディレイにより次画面へ上スクロール |
$80 | $40 | 前画面:NG 次画面:NG |
前画面:OK(ディレイ) 次画面:NG |
前画面:NG 次画面:OK(ディレイ) |
|
ディレイ死 | 画面下が前画面へ下スクロールできる箇所のとき、画面下に触れる ディレイにより穴判定され死亡する |
画面下が次画面へ下スクロールできる箇所のとき、画面下に触れる ディレイにより穴判定され死亡する |
$40のビットマスクを満たさない値 | $80のビットマスクを満たさない値 | 前画面:NG 次画面:OK |
前画面:OK 次画面:NG |
前画面:NG 次画面:NG(ディレイ)(穴判定) |
前画面:NG(ディレイ) 次画面:NG(穴判定) |
ディレイ左端変化(例) | 上スクロールか下スクロールで前画面に戻る ディレイにより前画面に戻ったとき左端($14)が変化する |
特になし | 前画面:OK 左端の読み込み処理:通常 |
前画面:OK 左端の読み込み処理:(ディレイ) |
||||
ディレイスクロール先変化(例) | 上スクロールか下スクロールで次画面に進む ディレイにより前画面に戻る |
$80 | 前画面:NG 次画面:OK |
前画面:OK(ディレイ) | ||||
ディレイ梯子テク(例) | 1:画面外の梯子近くへジャンプ 2:Y座標が244以上、$F9がFFになるのを待つ 3:上を押した瞬間ディレイにより上スクロール信号を消すとロックマンの座標が10程度になる 4:次のフレームで何も押さないと10、20、30とロックマンが落下していく 5:座標が30〜40になったときに上を押す 6:すると梯子テクのように画面上にロックマンが登場する |
$40のビットマスクを満たさない値 | $80のビットマスクを満たさない値 | 前画面:NG 次画面:OK |
前画面:OK 次画面:NG |
前画面:NG 次画面:NG(ディレイ)(上スクロール判定なし) |
前画面:NG(ディレイ) 次画面:NG(上スクロール判定なし) |
|
次画面へ進む上梯子のとき | 前画面へ進む上梯子のとき |
■ディレイスクロールを定量的に起こす方法
ここまで読んでくれた人は、ディレイスクロールについてかなり理解してくれたはずだ。
ここからは、FinalFighterや、ピロ彦氏が使っている完全な方法を紹介する。
TASでは、処理量が少なくともディレイスクロールを狙わないといけないケースが多い。
ディレイが少ないところでは、このように定量的に求めないとディレイスクロールを起こすのは非常に難しい。
0:当該の箇所で起きるスクロール判定の種類を理解する
・次画面へ行けるか
・前画面へ行けるか(&画面左端決定処理)
1:TASでスクロール判定が最初に走るフレームをメモる
このスクロールの場合は45360frame
2:同じフレーム付近で右端へ行く
同じステージ、同じフレームで敵やオブジェクトのない、
スクロール判定の走る画面右端へ行き、右押しっぱなしにする(改造コードとか使ってもおk)
3:NMI値をトレースする
1で調べたメモ付近のフレーム数になったら、
sample5.luaを読み込み、各フレームのNMIの値をdumpする。
dumpフォルダに各フレームのNMI値が出力される。これにより、各フレームのNMIの処理数を知ることができる。
※なお、各種値のファイル出力機能はカスタムFCEU専用の機能である
この例では改造コードでY座標を20、X座標を240に固定して右を押しっぱなしにしてスクロール判定を呼び出しながら計測している
出力結果は、以下のような感じになる。
(フレーム数) cfed_d0d3=NMI値
(72891) cfed_d0d3=988
(72892) cfed_d0d3=982
(72893) cfed_d0d3=997
(72894) cfed_d0d3=981
(72895) cfed_d0d3=981
(72896) cfed_d0d3=1125
(72897) cfed_d0d3=988
(72898) cfed_d0d3=1012
(72899) cfed_d0d3=995
(72900) cfed_d0d3=979
(72901) cfed_d0d3=979
(72902) cfed_d0d3=1227
(72903) cfed_d0d3=986
4:狙うフレーム近辺のRetSEを事前に
算出しておく。
やってはいけない行為や狙っては行けないフレームを事前に調べておく目的である。
@フレームごとのSE値を調べる
フレーム数 | 音楽コードの戻り値SE |
285〜303 | $1C |
304〜327 | $0B |
328〜338 | $1C |
ジャンプ音を混ぜた場合 | $00 |
$01 | |
$0B | |
$0F |
A次に音楽コードの戻り値SEとその結果生じるAレジスタRetSEの対照表を見てRetSEを算出しておく
クラッシュマンステージのSEとAレジスタRetSEの対照表 | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
フレーム数 | 音楽コードの戻り値SE | Aレジスタの値RetSE |
285〜303フレーム | $1C | $FF |
304〜327フレーム | $0B | $80 |
328〜338フレーム | $1C | $FF |
ジャンプ音を混ぜた場合 | $00 | $80 |
$01 | $80 | |
$0B | $80 | |
$0F | $20 |
たとえば、ウッドマンステージでは、H溜めを4フレーム以上した状態で
ディレイ上スクロール(次画面)をしようとしても音楽コードの戻り値が武器音により変化しているため必ず失敗する。
そのため、ジャンプ音による処理数加算を使うべきなのである。
5:まず重い動きをしてみて200以下くらいの値が出るのを狙う
良く使うのは以下のようなものである
・敵の重い行動を狙う
・ジャンプ音最大加算
・ショット音
・アイテム1号の設置
6:現在の行動の処理量を算出する
・1フレーム前のダンプ結果のNMIを
現在のSCRL値とあわせて計算することで、現在の
メインルーチンの大まかな処理量を知ることができる。
これにより、そのフレームがどれくらい重い状態にあるかを調べることができる
10000 - ( 1フレーム前のダンプ結果のNMI
+ SCRL
) = メインルーチンの処理量
7:NMIの値から狙うフレームを決定する。
・SCRLの値が「+500」で、500処理数が足りなかった場合、NMIが500大きいフレームを狙うとほとんどの場合成功する
・重さがかなり足りない場合は、NMIが1200以上のような非常に重いフレームを狙う
・近隣のフレームにNMIが大きいフレームがあると、あまり負荷をかけなくても成功する
・NMIの値は、1フレーム前の値で考える(スクロールによって異なるが。)
以下の例では上を押したフレーム数によってどれだけの処理量の違いが出ているかを示した図である。
フレーム数 | NMI値 | 上を押したフレームごとの結果 | ||
27519 | 929 | ↑ | ||
27520 | 913 | ↑ | ||
27521 | 913 | SCRL=1612 | ↑ | |
27522 | 1296 | SCRL=1596 | ||
27523 | 999 | SCRL=1216 |
27521フレームに上を押すと、1296の処理量のNMIの処理量がのることで、
27519や27520フレームに上を押した場合と比べて、SCRL値が400ほど減少しているのがわかる。
これは、NMIが913よりも400ほど大きい1296という処理量だからである。
8:狙うフレームを決定したら、フレームをずらして同じ行動をする。
敵の動きやアイテムの動きが変化してしまうと、
処理量が変化してしまうので、変化させたくない場合にこの方法を使う
敵の動きを変化させない調整は以下のようなものである。
・スタートの間隔で調整する
スタート→メニュー切り替え→スタート→操作1→操作2…
スタート→メニュー切り替え→(1フレ待機)→スタート→操作1→操作2…
スタート→メニュー切り替え→(2フレ待機)→スタート→操作1→操作2…
・画面に入るのを数フレーム遅らす
スクロール→次画面表示→操作1→操作2→…スクロールポジションへ移動
(1フレ待機)→スクロール→次画面表示→操作1→操作2→…スクロールポジションへ移動
(2フレ待機)→スクロール→次画面表示→操作1→操作2→…スクロールポジションへ移動
・敵が動きだす位置に行くのを数フレーム遅らす
敵が見える前→敵が見える位置へ移動
敵が見える前→(1フレ待機)→敵が見える位置へ移動
敵が見える前→(2フレ待機)→敵が見える位置へ移動
敵が少なかったりする場合はほとんどの場合、重いのは一瞬なので
画面に入るのを遅らせることで、メインの処理に重いNMIを重ねることでき、
合計10000処理、つまりSCRL=0に近づけるわけである。
NMIは完全にフレーム数固定で処理数が多いか少ないかが決定するのを利用しているのである。
9:SCRL値で、極小の値が出たら微調整する
SCRLが60〜10 または、1〜3程度の値がでたら、 最後のフレーム付近でスタートボタン押しやAB同時押しなどで微調整するとだいたい成功するはずである。
10:スクロール先が違った場合の対処
次画面スクロールをしたかったのに前画面スクロールしてしまった場合は更に40程度の処理量を追加すると次画面スクロールになる