プチコン3号 16日目 グラフィックスの当たり判定

今回と次回で、14日目の「グラフィックスで描いた弾む円」と15日目の「弾むスプライト」を組み合わせたゲームを作ってみます。
プレイヤーが円を避けながら左右に走って、降ってくる星を集めるゲームです。
円にぶつかったらゲームオーバーです。

pc16-1.jpg

これには、グラフィックスで描いている円と、スプライトで動かしているプレーヤーキャラクタとの衝突判定が必要になりますので、今回はその部分を作りました。

スプライト同士の衝突はSPHITSP命令がありますが、グラフィックスでの衝突判定は、以下の2つの方法が考えられます。

(1)幾何学的判定
円同士の衝突の場合は、

2つの円の中心間の距離 < 2つの円の半径の和

となっていれば2つの円が重なっていることが言えますので、これで判定できます。
この場合は、プレイヤーキャラクタの衝突判定領域を円で近似する必要があります。
円と四角形の衝突も、計算が面倒そうですが、幾何学的な判定は可能です。

(2)画像による判定
これは実際に描かれたグラフィックスから、衝突判定したい領域の画像を切り出し、その画像に対象の画像が含まれているかどうかで判定する方法です。
直感的には判りやすいですが、実際には対象の画像かどうかは「色」で判断するしかありません。従ってグラフィックスに複雑な画像が描かれている場合は、誤判定が起こりやすくなります。

pc16-2.jpg

一般的には、画像による判定は、画面が複雑になればなるほど困難になりますし、ハードウェアに依存する度合いも高くなるので、可能なら幾何学的に判定するほうが良いでしょう。

しかし今回は、画像による判定を行いました。
グラフィックスには円の画像しか描かれていませんので、単純に領域内のピクセルが「黒か黒でないか」だけで衝突の有無を判定できるからです。

SmileBASICには、このような目的にGSPOIT命令やGSAVE命令が使えます。
GSPOIT命令は、指定した座標のピクセルの色を読み取ります。
GSAVE命令は、指定した矩形の領域の画像を配列にコピーします。
GSAVE命令では1ピクセルが配列の1要素に入りますので、たとえば16×16ピクセルの領域の画像は、サイズが256の配列にコピーされます。
画像を配列に変換することで、より複雑な処理をすることができます。

今回は、ある領域を指定して衝突判定をしますので、GSAVE命令を使います。
調べたい領域のピクセルを配列に変換し、次に配列の要素の中に0(黒のピクセル)でないものがあるかどうかを調べます。

それではプログラムです。
14日目のプログラムとかなり重複がありますので、追加・修正部分に全て色をつけました。

NMAX=9:G=0.1:SCW=400:SCH=240
DIM X[NMAX+1],Y[NMAX+1],R[NMAX+1]
DIM V[NMAX+1],V0[NMAX+1],DX[NMAX+1],C[NMAX+1]
FOR I=0 TO NMAX
R[I]=10+RND(40)
X[I]=R[I]+RND(SCW-2*R[I]-20)+10
V0[I]=-(4+RND(20)/10)
Y[I]=SCH-R[I]-16
V[I]=V0[I]
DX[I]=(RND(2)*2-1)*(RND(3)+1)
C[I]=RND(&HFFFFFF) OR &HFF1F1F1F
NEXT

ACLS
GPRIO 1
MANX=200
SPSET 0,1128
SPCOL 0,12,16
SPANIM 0,"I",9,1128,9,1129,9,1130,9,1131,0

N=5
HITX=6:HITY=7
HITW=16-HITX*2:HITH=16-HITY
DIM PXL[HITW*HITH]
@LOOP
GSAVE MANX+HITX,224+HITY,HITW,HITH,PXL,1
HIT=0
FOR I=O TO HITW*HITH-1
HIT=HIT OR PXL[I]
NEXT
IF HIT!=0 THEN GOTO @EXIT

STICK OUT SX,SY
MANX=MANX+SX*4
MANX=MIN(384,MAX(0,MANX))
SPOFS 0,MANX,224,0

GCLS
FOR I=0 TO N
GCIRCLE X[I],Y[I],R[I],C[I]
GPAINT X[I],Y[I],C[I],C[I]
V[I]=V[I]+G
IF Y[I]+V[I] > SCH-R[I] THEN
V[I]=V0[I]
Y[I]=SCH-R[I]
ENDIF
INC Y[I],V[I]
INC X[I],DX[I]
IF X[I] > SCW-R[I] || X[I] < 1+R[I] THEN
DX[I]=-DX[I]
ENDIF
NEXT
VSYNC 1
GOTO @LOOP
@EXIT

ちょっと長いですが、今回のメインは青い部分です。

赤字の部分は変数名の変更や細かい修正です。
たとえば、円の中心座標は、スタート時にいきなりスプライトと重なってしまわないように、16ピクセル上に動かしています。
「GPRIO 1」はグラフィックス画面のZ軸の指定です。スクリーン(Z=0)のすぐ後にしています。
また、円が弾む際の画面下端との接触処理もより厳密にしています。

紫の部分は、プレイヤーのキャラクタを表示するスプライトの初期設定と、スティックでの操作です。
ここは3日目に説明しましたので省略します。
スプライトがグラフィックス画面のすぐ手前に表示されるよう、SPOFSでZ値を0に指定しています。

グラフィックスの当たり判定関連の部分は、青字にしています。
ループの外側は初期設定です。

HITX=6:HITY=7
HITW=16-HITX*2:HITH=16-HITY

それぞれの変数の意味は下図の通りです。
スプライトの16×16ピクセルの領域のうち、青枠で囲った部分だけを当たり判定の対象にします。

pc16-4.jpg

青枠の部分が狭いほど、当たり判定が甘く(当りにくく)なります。
画面下端付近では円の落下速度が速いため、やや甘い当たり判定にしてあります。

その次のDIM文で宣言している配列PXLを、画像を読み込むために使います。
実際の読み込みはループの開始直後、

GSAVE MANX+HITX,224+HITY,HITW,HITH,PXL,1

の部分です。
パラメータは、矩形領域の左上のXY座標、矩形領域の幅、高さ、読込先の配列、色変換指定です。
色変換指定は今回のプログラムでは、1(変換あり)でも0(変換なし)でも動作には違いはありません。

配列に読み込んだデータはFOR~NEXTループで全部(といっても4×9=36個ですが)0かどうかチェックします。
今回は、ループでデータ全てのORを求め、その結果が0かどうかをループの後で判定しています。
(OR演算はビット演算で、少なくとも一方が1なら結果は1になります。0 OR 0=0、1 OR 0 = 0 OR 1 = 1 OR 1 = 1です。)

円と衝突したと判定した場合はプログラム末尾へジャンプしていますので、こうなります。

pc16-5.jpg

これで当たり判定ができたので、次回は星を降らせる処理や効果音などを追加してゲームを完成させたいと思います。

コメント