プチコン3号 23日目 シューティングゲームの当たり判定とマスク値設定

前回までに、自機と自機の弾、敵機と敵の弾をそれぞれ生成・更新できるようになりました。
今回は当たり判定を追加します。

当たり判定はSPHITSP命令で行えます。
この命令は以前にも11日目に迷路ゲームで使用しました。
このときは、2つのスプライトが衝突しているか否かの判定でした。

今回は、SPHITSP命令のもう一つの機能である「衝突しているスプライトを検索する」機能を使います。
この機能を使う場合は、SPHITSPのパラメータにはスプライト管理番号を1つだけ渡します。
そのスプライトに衝突している別のスプライトがあれば、その管理番号が返り値として得られます。
衝突していなければ、-1が返ります。

これだけだと、処理する必要がない衝突まで検索されてしまいます。
たとえば自機と自機の弾の衝突は検出する意味がありません。

無用な衝突検出を避けるには、スプライトの衝突判定条件に「マスク値」を追加します。
マスク値はSPCOL命令のパラメータで、スプライトごとに設定することができます。

SPHITSPで衝突したスプライトを検索する際に、2つのスプライトのマスク値のANDを取り、0でないスプライトだけが衝突検出の対象になります。
具体的なマスクの設定の例を下図に示します。

pc23-1.jpg

画面に登場するスプライトと、当たり判定の要不要を表の形で表しました。
自機は敵機または敵の弾と当たり判定を行いますが、自機の弾との判定は不要です。
自機の弾は、敵機とのみ当たり判定を行います。

マスク値は、ボタン入力の判定と同じように2進数で考える必要があります。
上の図では、マスク値を2進数で書いています。
2進数の01は10進数の1、2進数の10は10進数では2です。

敵機のマスク値が11で、自機のマスク値が01、自機の弾のマスク値が10なら、

11 AND 01 = 01
11 AND 10 = 10

ですから、敵機は自機とも自機の弾とも当たり判定されることになります。

マスク値の具体的な決め方は、以下のように考えると良いでしょう。

(1)衝突判定の対象が1番多いオブジェクトを選ぶ。(複数ある場合は、その中からどれか1つ選ぶ)
(2)(1)のオブジェクトと衝突しないオブジェクトで、衝突判定の対象が1番多いオブジェクトを選ぶ。
(3)(1)と(2)に衝突しないオブジェクトで、・・・(以下同じ)

今回はオブジェクトが4種類しかありませんが、上記の方法でやってみると

(1)の候補は自機、または敵機。ここでは自機としましょう。
(2)の候補は自機の弾のみ。
(3)もうありません。

そして、上記で選び出したオブジェクトに、それぞれ独立なマスク値を与えます。
マスク値は32ビットですので、以下のように独立なマスクが32個あります。

0000 0000 0000 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0010
         :
0100 0000 0000 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000

どのオブジェクトをどのマスク値にするか制約はありません。
ここでは、自機が末尾01、自機の弾が末尾10としましょう。

上記で選ばれなかったオブジェクトは、独立なマスク値を持ちません。
それらのオブジェクトのマスク値は、

「当たり判定が必要な全てのオブジェクトのマスク値のOR」

で求めます。

たとえば敵機は自機、自機の弾のいずれとも当たり判定が必要ですので、

 01 OR 10 = 11

がマスク値になります。
敵の弾は、自機とのみ当たり判定しますので、マスク値は01になります。

なお、(1)のところで自機ではなく敵機を選ぶことも可能です。
その場合は(2)で敵機の弾が選ばれ、この2つに独立なマスク値を与えることになります。

前回までのプログラムに衝突判定の処理を追加すると、以下のようになります。
まず独立なマスク値を定義します。

VAR _MASK_MY:_MASK_MY     = &H01
VAR _MASK_SHOT:_MASK_SHOT = &H02

&Hは、「16進数」を表します。

次に、各オブジェクトについてSPCOL命令で衝突判定領域とマスク値の設定を追加します。
SPCOLのパラメータは

 管理番号, Xオフセット, Yオフセット, 幅, 高さ, スケール対応, マスク

です。

DEF MY_NEWSHIP
VAR SP
SP=SP_NEWOBJECT(_MY)
IF SP != _S_NULL THEN
SPCHR SP,3311'3299
SPOFS SP,_SCW>>1,200
SPCOL SP,-6,-6,12,12,TRUE,_MASK_MY
ENDIF
END
DEF NEW_SHOT X,Y,SPEED
VAR SP
SP=SP_NEWOBJECT(_SHOT)
IF SP != _S_NULL THEN
SPCHR SP,3353
SPOFS SP,X,Y
SPANIM SP,"XY+",-SPEED,0,-240,1
SPCOL SP,-1,-8,2,16,TRUE,_MASK_SHOT
ENDIF
END
DEF UPDATE_ENEMY_C SELF
VAR SP
IF MAINCNT MOD 20 != 0 THEN RETURN
SP=SP_NEWOBJECT(SELF)
IF SP != _S_NULL THEN
SPCHR SP,3251
SPOFS SP,50+RND(_SCW-100),-8
SPANIM SP,"XY+",-180,0,256,1
SPANIM SP,"I+",5,0,5,1,5,2,0
SPCOL SP,-6,-8,12,16,TRUE,_MASK_MY OR _MASK_SHOT
ENDIF
END
DEF NEW_ESHOT X,Y,DESTX,DESTY,TICK
VAR SP
SP=SP_NEWOBJECT(_ESHOT)
IF SP != _S_NULL THEN
SPCHR SP,3372
SPOFS SP,X,Y
SPANIM SP,"XY+",-TICK,DESTX,DESTY,1
SPANIM SP,"I+",5,0,5,1,5,2,5,3,0
SPCOL SP,-2,-2,4,4,TRUE,_MASK_MY
ENDIF
END

これで各スプライトの当たり判定が有効になりました。
当たり判定処理は、自機側で行うことも敵機側で行うこともできます。
どちらかと言えば、自機・自機の弾を中心に考えるほうがプログラムを書きやすいと思います。
以下の例では、自機および自機の弾のUPDATE処理内で行っています。

まず自機の弾の当たり判定処理です。

DEF UPDATE_SHOT SELF
VAR X,Y,HIT
SPOFS SELF OUT X,Y
IF Y < 0 THEN SP_FREE SELF:RETURN

 HIT=SPHITSP(SELF)
IF HIT == -1 THEN RETURN
SP_FREE HIT
SP_FREE SELF
END

衝突する相手は敵機のみですので、衝突していたらその相手と弾自身を単に廃棄しています。
厳密には、他に「自機の弾同士の衝突」があり得ます。
連射速度が非常に速い場合などは、これを避けるために

IF SP_CLASS(HIT) == _SHOT THEN RETURN

という処理が必要になります。

次に自機の当たり判定です。
動作確認のために、とりあえず敵や敵弾に当たったらその都度、画面に「*」をプリントするようにしました。
実際にはこの部分に、自機がやられた場合の処理を入れます。

DEF UPDATE_MY SELF
VAR SX,SY,X,Y,B,HIT
STICK OUT SX,SY
SPOFS SELF OUT X,Y
X=MIN(_SCW-8,MAX(X+SX*8,8))
Y=MIN(_SCH-8,MAX(Y-SY*8,100))
SPOFS SELF,X,Y,1

B=BUTTON(1)
IF B AND 16 THEN
NEW_SHOT X,Y,60
ENDIF

HIT=SPHITSP(SELF)
IF HIT == -1 THEN RETURN
PRINT "*";
END

コメント