PCにUnityで移植して気付いたスターソルジャーの仕様とバグ

↓この話に関連する話題。 u-mid.hateblo.jp

このASGコンテストに向けて検証も兼ねて実際にUnityでスターソルジャーを移植してみて(当時PC上で全16面遊べる状態、今はサウンド以外移植完了)、「スターソルジャーってこういう仕様だったんだなぁ」と思ったことが色々とあったのでツイッターでちょくちょくアウトプットしてたんですが、一連のツイートの内容をここにまとめておきます。


その1:スプライトの遅延

地上物破壊を実装した時「見た目FC版と同じだけど何かが違う、なんというか…かっちりしている」という謎の違和感。 調べてみて理由が判明、FC版では背景のスクロールと爆発のスプライトにズレが。

FC版の場合、横移動のボタン入力してから背景の横スクロールに1フレーム遅延(1フレーム=1/60秒。60フレームで1秒)、自機の横移動に2フレの遅延があった。処理順の関係で、入力→自機移動→スプライト位置変更→スプライト更新が1フレ以内に終わらず遅延になる。スプライト2フレ遅延は地上物爆発に限らず全てのスプライトで起こっている。地上物爆発の横スクロールは背景より更に2フレ遅延するため、合計3フレ遅延。

昔から「スターソルジャーのスターブレインってなんかコアが本体よりズレてない?」という疑問があったが、背景とスプライトの遅延差の都合があったのかと納得。

これなら背景スクロールも敢えて更に1フレ遅延させた方が動きがしっかり同期できそう。

その2:レーザー

レーザーは、弾(レーザー)自体は普通の弾が縦に連なってるのと同様で、敵に当たればダメージを与えて消える。レーザー発射中にレーザーOFFに切り替えると普通の弾に戻る。

普通の弾との違いは生成のされ方で、発射中毎フレーム新たに生成されている。1つ前のフレームのレーザーは上書きされ全て消える。敵に当たって消えても次のフレームには新たに生成されるため毎フレーム継続的にダメージを与えることができる。16x16ドットの敵には1フレーム2ダメージ与えるが、当たる位置次第で1フレ3ダメ与える。ただし空中ザコ敵は1フレームに1ダメージまでしか反映されない。
レーザーの当たり判定はこんな感じ。

レーザーの発射位置(始点)は、左右は自機に追尾するが上下は追尾しない。レーザーの長さが上限(弾15個分)まで伸びたら、その後は長さはそのまま発射位置が上に移動する。発射位置が画面外まで移動したら次のレーザーが撃てる。

その3:誘導弾

誘導弾は8フレームに1回、自機狙い方向に向かって22.5度ずつ方向転換する。22.5度というのは敵弾や敵の移動方向が16方向なため(360度÷16方向=22.5度)。こちらの解析ページ参照。

taotao54321.github.io

方向転換する8フレームのタイミングは誘導弾ごとのカウントではなく全ての誘導弾で同時。31回自機狙い処理をするとそれ以上は自機狙いにならない(誘導終了)。つまり誘導弾の自機狙いは3秒ちょっと継続する(検証してないけど242~249フレーム?)。

誘導弾の動きがフラフラしているのは、方向転換が毎フレームではなく8フレに1回22.5度ずつなのと、弾の飛ぶ方向によって速度に差があるため。1フレの増分である0~2の範囲内の整数なので精度が低く、22.5度の方向(速度2.37)と45度の方向(速度1.41)で1.58倍の速度差がある。この速度のバラツキは普通の自機狙い弾でも同様。

試しに誘導弾の方向による速度差を緩和(精度を8倍に)してみたけど、まだ少しカクカクさが残った。方向転換を毎フレームにしてなめらかに方向転換させる必要もあるみたい。


その4:ラザロ

ラザロは4つのパーツがそれぞれHPを持ってて、それがリセットされるタイミングが3回ある。登場時と合体開始時と合体時。

まず登場時はHP256。レーザーで撃つ位置を調整しつつ1フレ3ダメージ与え続けると256に届くようだ(レーザーの仕様については「その2:レーザー」の項を参照)。1フレ2ダメージだけではHP256を削りきれない。
前述の解析をした方による、実際にレーザーで合体開始前に倒す様子。


合体開始時、左右から真ん中に移動し始めた瞬間に、上の2つのパーツがHP16、下の2つがHP256になる。左右から真ん中に移動してる間に与えたダメージも有効。合体と言ってもパーツが一緒に動いてるだけで、HPは4つ別々のまま。ということは、前方向に2発撃つ状態の場合、真ん中で撃つと前方向2発の弾が左右のパーツそれぞれに当たってしまう。左右どちらかに寄って片方のパーツに2つの弾両方を当てれば真ん中で撃つより2倍早く倒せる。

なお、弾がちょうどラザロの真ん中に当たった場合は判定順序の関係で左のパーツにダメージが入るため、右寄りは位置調整の難易度が高い。

位置取りはなるべく左寄りで。スコア表示の4桁目と5桁目をラザロの真ん中の目安にすると良い。


ちなみに、パワーアップアイテム1つ以上取得済み&前方向に2発撃つ状態で、自機の横位置は片方に寄って縦位置はちょうどラザロの間に調整すると、押しっぱなし自動連射でラザロが合体前に倒せる。

ただし縦移動が1フレ分のズレも許されないシビアさ且つ地上物で邪魔されたりするので、実用は厳しい。

1つに合体すると4つのパーツ全てがHP16になる。1つになろうとHPは4つ別々のままなので、1つのパーツを集中的に撃った方が早く倒せる。ラザロの弾を避けようとラザロの周りをぐるぐる回りながら攻撃すると、4つのパーツに満遍なくダメージを与える感じになり妙に長期戦になるので注意。


その5:スターブレイン・ビッグスターブレイン

スターブレインのコアのHPは33。降りてくる途中まで8フレーム毎に何度もHPが33に全快するので、その間ダメージを与えても無効。
ビッグスターブレインもHP33で、砲台がどれか残っている間はコアに攻撃しても即全快する。砲台のHPは17。


本体と自機の当たり判定はこんな感じ(イメージ図。実際の判定処理はhitbox形式ではない)。

スターブレインがわりと見た目通りな一方、ビッグスターブレインの上の方の当たり判定があまり大きくないのは、自機が上にいる時に本体が上に移動してきても当たってない扱いにするため?

本体が降りてきてる間は本体と自機の間に当たり判定は無い(コアや砲台には判定あり)。降りきった時から本体の当たり判定が有効になり、この判定処理は自機の無敵チェックをしてないため自機が無敵状態(バリアが解けた直後とか)でも当たれば1ミスになる。


スターブレインは降りてきた後左右に移動するが、登場時(曲が変わり"ATTACK STAR BRAIN"と表示された時)に自機が真ん中より右にいれば左に、左にいれば右に移動する。左に移動する場合は横1往復(真ん中→左端→真ん中→右端→真ん中、と移動した後逃亡)、右の場合は横1.5往復で逃走する。ビッグスターブレインだとそれぞれ4往復・4.5往復。
最初の移動方向によって逃走までの時間が変わるのは、「真ん中を左に移動しながら通った回数」をカウントして、スターブレインは2回、ビッグスターブレインは5回になったら逃亡するという処理なため。降りてきて左に移動開始する場合もその時点で1回にカウントされるので、スターブレインが左に行った場合は1往復で逃亡する。

その6:ジェリコ

ジェリコ(目が2つの大きめな空中敵)について。
見た目は左目と右目の2体だが、1つの目がラザロ同様4つのパーツでできてて、1つ1つがHPを持ってる。HPは全て17。

どれか1つのパーツがHP0になったら全てのパーツ(両目)が爆発する、これもラザロ同様。なのでラザロと同じく1つのパーツを集中攻撃する方が早く倒せる。

降りてきてから6秒経過すると上の方へ帰っていくが、その上への移動中は全パーツのHPが毎フレーム全快するので倒せない。

その7:リューク

リューク(撃つと増える敵)について。
HPは8。ダメージを受けHPが1減った時HPが奇数の場合にコピーを生み出す(1体増える)。HPはコピー元と同じ。

コピー生成時は生成されたコピーが8ドット左上に現れ、コピー元は8ドット右に移動する。これにより1体が左右2体に分裂したような見え方になる。2体の上下位置が8ドットずらしてあるのは、同じ上下位置だとFCで横16ドットのスプライトが横に4つより多く並ぶと消える制限にあっという間に達するためか。

スターソルジャーでは空中の敵と空中の爆発を合わせた上限数は12個であり、その上限に達している状態ではリュークもそれ以上増えない。レーザーによる攻撃で増やすと上限に達するため増殖数を抑えられる。

リュークはしばらく自機を追尾し続けた後縦スクロールと同じ速度で下へ移動していく。追尾時間は、増える前の1体目のリュークは255フレーム(4.25秒)だが、増えた分のリュークは追尾時間カウントの未初期化により追尾時間が決まっていない。具体的には、全部で12個分ある空中敵情報のメモリ領域のうちの1つを、新たに増えたリュークで使い始める時、その領域を最後に使っていた敵の種類が何だったかで追尾時間が変わる。ディダだと198フレーム、ルダンだとたった7フレーム、リュークなら残り追尾時間を引き継ぐ。

増えたリュークの情報の未初期化はボスかどうかのフラグでも起こっていて、ボスフラグが残ったリュークが無敵になるバグがあるそうで。

taotao54321.github.io

ボスフラグありリュークがHPが5の場合は、1フレームで一気に5ダメージ与えることができないため倒せない。HPが1の場合は倒せる、その際他のボスフラグONの敵(ラザロ・ジェリコ・スターブレイン)同様画面上の敵が全滅する。HP3だと通常弾では倒せないがレーザーで1度に3ダメ与えると倒せる。

前出の解析情報のページにあるとおり、ジェリコ→ルダン→リュークという順番で来る場面で起こりやすい。ルダン8体+リューク1体目の画面上の合計が8体未満になるようにルダンを2体以上素早く倒す&リュークを素早く増やすだけで起こる。

この動画は、どの敵が空中敵情報の何番目のメモリ領域を使っているか&ボスフラグがONかどうかの様子。

ジェリコで8つのパーツ分ボスフラグがONになり、ルダンのような普通の空中敵が出るとそこのボスフラグがOFFになるが、ルダンを即撃破して8体目の枠まで使用させないことによりボスフラグON状態が残っている。

スターブレインのコアのボスフラグが影響するケースもある。

コアが使う空中敵情報は12番目固定で、2種類の敵編隊が同時に出た上即撃破されない状況が来る(画面上の合計が12に届く)までボスフラグが残る。そこで別の敵編隊と一緒にリュークが出てリュークが増えると12番目に来ることが。

なおリュークが単体で出てきた時にレーザーで攻撃してもリュークだけで一気に合計12体まで増えてバグることがあるが、12体目まで増えた個体は大抵HPが1か3なため即撃破して、バグの結果(ボスフラグONの敵がHP0になったら画面上の敵全滅=12体全てのリュークが爆発する)に気づかれない。


ラザロもボスフラグを使うが、パーツ数は4つなので4体分しかボスフラグを使わず、その後の空中ザコ敵が4体以上出てきた時点でボスフラグも全てクリアされるのでバグは起こらない。

他にも、リュークが上から出てきたと同時に攻撃が当たり続けるようにレーザーを撃つと、位置によっては分裂するたびにコピー先が左上に8ドット移動を繰り返した結果上端からはみ出て下から出てくるというバグ?もある。通常は下の画面外判定で消えるが、その判定位置を飛び越えるとそのまま下から出てくる。

その8:ワープ時のスクロール

隠し要素であるワープに関して昔から気になることが。ワープする時に背景がフェードアウトするけど、その時背景スクロールが1回ガクッとズレたり何度もズレが継続したりすることがある。

これはワープマークに触れてワープを開始する時の縦スクロール位置によって、この現象が起きるかどうかが変化する。
FCの縦解像度は240で、上へスクロールする場合はスクロールのY座標も0から239まで行って1周し0に戻る。下スクロールなら239から0まで行って1周。
一回だけズレる現象の原因は、ワープ時スクロールのY座標を2ずつ加算して下にスクロールさせてる時、240になったら0に戻すべき所を240以上まで加算してY座標を設定してしまい、スクロールが16ドット戻るため。なのでこの現象はワープ時にスクロールのY座標が240を超える位置だった時に起こる。

一回16ドット戻った後に何度も8ドット戻り続ける場合がある。これはまた別の原因。スクロールのY座標が255を超える時に偶数だと起こる、奇数だと起こらない、つまり確率1/2。

以下具体的な処理の中身の話(長くなるので飛ばしてもOK)。

フェードアウト処理の中には、予めスクロール速度と目標のスクロールY座標を設定し、目標Y座標に届かないうちは設定された速度で縦スクロールするという処理も含まれている。この処理はスターブレインの逃走時・撃破時のフェードアウト&スクロールで使われていて、それをワープ時にも使っている。
ただワープ時の場合、この処理のためのスクロール速度と目標位置の設定がどうもおかしい。速度は+10とやたら速く、目標位置は設定自体されてない。
速すぎるスクロールを調整するためなのか、ワープ中の処理に縦スクロール(スクロールのY座標の設定)を毎フレーム-8する処理がある。結果、実際のスクロール速度は +10 - 8 = +2 になっている。
これで解決と思いきや、今度はフェードアウト処理内での「目標位置までスクロールしてたらスクロール停止」が影響する。目標位置は未設定で0になっている。速度2でスクロールしていって(8bitなので255の次は0に戻る)スクロールのY座標が目標位置の0になった時だけ、フェードアウト処理内で+10しない。でも、ワープ中の処理の-8はそのままなので、+10-8で速度2だったスクロールがこの時だけ速度-8、つまり8ドット戻ってY座標は248になる。その後も+2し続けるのでまたY座標が0になり8ドット戻って248、をずっと繰り返す。これが戻り続ける原因。
Y座標が奇数の場合は+2しても奇数のまま253→255→1→3と目標Y座標である0にならないため8ドット戻りは起きない。

あとスターブレイン逃走時には目標位置設定が144になるので、逃走後にワープすると8ドット戻りが別位置(0ではなく144)で起こる。

なんだかこのワープ時のスクロール処理、ちゃんと修正する時間が無かったのかプログラムの記述が妙。

以下更に具体的な、プログラムの中身についての話。

ワープの効果音(IDは0x0A=10)をセットすると同時に、そのIDの値である10をフェードアウト中のスクロール速度にもそのままセットしている。

80B9  A9 0A    LDA  #$0A   ; 0x0A(ワープ効果音ID) -> A
80BB  85 8C    STA  $8C    ; A(中身は0x0A) -> $8C(低優先度効果音)
80BD  85 75    STA  $75    ; A(中身は0x0A) -> $75(スクロール速度)

コード自体は一見IDとスクロール速度がちょうど同じだったから一緒に代入したようにも見えるが、スクロール速度としては10は速すぎる。それに、一緒に設定しなくてはいけないスクロール目標位置が設定されてない。 しかもワープ処理内でも毎フレーム-8の速度で縦スクロールさせている。

80D9  A5 14    LDA  $14    ; $14(スクロールY) -> A
80DB  18       CLC         ; 加算前にキャリーフラグをクリア
80DC  69 F8    ADC  #$F8   ; A + 0xF8(-8) -> A
80DE  85 14    STA  $14    ; A -> $14(スクロールY)

これで実際のスクロール速度は10-8で2になってはいるが、意図的にフェードアウト中のスクロール速度を10にセットしたのかに疑問が残る。本来はワープ処理内だけでスクロールさせればいいはず。試しにフェードアウト中のスクロール速度を10から0にし、ワープ処理内のスクロール速度を-8から2にすると、バグが起こらなくなり他の問題も特に無かった(これとは別の問題でスクロールYが240以上の時-240する必要はあるけど)。

これだけだと意図せずフェードアウト中のスクロール速度を10に設定してしまった事に気付かずにとりあえず辻褄を合わせるべくワープ処理内でも-8の速度でスクロールさせたようにも見えるが(ちゃんと調査・修正する時間が無かったとか?)、別のなんらかの理由があったのだろうか。

その9:爆発の効果音

空中の敵を倒してると爆発の効果音に種類があることに気付く。ノイズチャンネルの音程を可視化してみると、音程の高さが違う。

そして連射せずに倒すと必ず「音程が、高い所から一気に低くなりそこで止まる」という鳴り方になる。

これは新たに効果音が鳴るとすでに鳴ってる効果音の更新がされなくなるという仕様が原因。プログラム側で鳴り方を更新するという処理が、最新の効果音1つだけにしか適用されないため。
空中の敵の爆発音は実際は1種類しかない。前述の「音程が、高い所から一気に低くなりそこで止まる」が本来の鳴り方。だが音程が低くなる途中で弾発射の効果音が鳴ると爆発音の音程変化が止まる。
爆発音が鳴ってから発射音が鳴るまでの時間で音程の高さの止まる位置が変わる。
連射が速いほど音程が低くなる前に高さが変化しなくなり音程が高くなりがち。 弾発射が単発だと爆発音が鳴った後に発射音を鳴らさないので、爆発音が最後まで音程変化して本来の鳴り方をする。

地上物の爆発音も本来は「高い音の後低い音が鳴る」という鳴り方だけど、連射してると高い音が鳴った後に発射音により爆発音の更新が止まり、低い音が鳴らない。

他に更新が止まって鳴り方が変わる効果音には、耐久力のある物にダメージを与えた時の音(キィンという音)があるが、そうなる機会が少なく(発射音が鳴るとそちらに上書きされダメージ効果音自体が消える)、変化も小さい(少しの音程の上下が無くなるだけ)、なので目立たない。

ソフト(プログラム)側ではなくハード(ファミコン)側の、自動的に音量や音程を変化させる機能を使ってる部分はこの仕様は影響しない(発射音等)。
鳴ってる間別の新たな効果音が鳴らないというタイプの効果音(ボーナス音やパワーアップ音等)も影響はない。






以上、スターソルジャーの仕様やらバグやらでした。
昔からの疑問がいろいろと氷解しました。

動画を多用したけど、これならむしろこの記事内容を1本の動画にまとめた方が見やすいかも、とも思ったり。

次は多分ASGコンテストに応募した話の続き。