FC『スターソルジャー』のちょっと気になるコード

以前アップされた8面までのスコアアタックTASを引き継ごうかと思って色々調べ中です。今のところコードを読んでるだけですが、気になった点について少しメモ。

CHRバンク切り替え

; マッパー3 CHRバンク切り替え
9F88 : BD 44 D0		lda	$D044,x
9F8B : 9D 44 D0		sta	$D044,x

バンク切り替えを行うだけならレジスタに即値を書き込めばいいはずなのに何故こんな冗長なコードなのかと疑問に思いましたが、ちゃんと理由があるようです。NesDevWikiによるとマッパー3にはbus conflictがあり、レジスタに値を書き込む際はROMの同一アドレスと同じ値を書き込まないと挙動がおかしくなることがあるので上記のようなコードになっていると思われます。

衝突判定

;;; 弾とオブジェクトの衝突判定(弾hitbox:(6,6), オブジェクトhitbox:(12,12))
; Read
;   $00	U8	弾座標x
;   $01	U8	弾座標y
;   $02	U8	オブジェクト座標x
;   $03	U8	オブジェクト座標y
; Write
;   C	bit	0:hit, 1:not-hit
CollideBulletObj:
; # x/yいずれについても、減算結果が -12 以上 6 未満の範囲で C == 0 となる
; A = $02 - $00 + 12
; C = A >= 18
; if(C) return
; A = $03 - $01 + 12
; C = A >= 18
B050 : A5 02		lda	$02
B052 : 38		sec
B053 : E5 00		sbc	$00
B055 : 18		clc
B056 : 69 0C		adc	#$0C
B058 : C9 12		cmp	#$12
B05A : B0 0A		bcs	$B066
B05C : A5 03		lda	$03
B05E : 38		sec
B05F : E5 01		sbc	$01
B061 : 18		clc
B062 : 69 0C		adc	#$0C
B064 : C9 12		cmp	#$12
B066 : 60		rts

かなりシンプルなコードで、一見しただけでは何故これでうまくいくのかわからなかったので少しコメントしてみます。
簡単のためx方向についてのみ考えます(y方向も全く同じ考え方)。まず以下のように変数を設定します:

  • x1: 弾座標(左端)
  • w1: 弾の当たり判定幅
  • x2: オブジェクト座標
  • w2: オブジェクトの当たり判定幅

すると、この衝突判定は2区間 (x1,x1+w1), (x2,x2+w2) が重なっているかどうかの判定に帰着します。この2区間が重ならない条件は

x2 >= x1+w1 or x1 >= x2+w2 # 境界条件はとりあえず適当

なので、2区間が重なる条件はその否定

x2 < x1+w1 and x1 < x2+w2

となり、変形すると

-w2 < x2-x1 < w1

となります。ここで w1=6, w2=12 とすれば、これはまさに上記のコードのコメントに記した条件になっています。実際は半開区間での判定となるため、左から当たったときと右から当たったときの境界条件が異なるという些細な問題はありますが、それにしてもうまいコードだなぁと思いました。なお、オブジェクト同士の衝突判定($B067)もhitboxが異なるだけで同様のコードになっています。

以上を踏まえて当たり判定表示スクリプトを書いてみました。こんな感じ: