スクリプト言語の HSP については、公式ホームページの「HSPTV!」をどうぞ。
なお、このページはシューティング・ゲームのミニ講座シリーズの1つです。
今回は第5回「当たり判定」について説明します。(戻る)
当たり判定とは
まず最初に「当たり判定」とは、キャラクタ同士の衝突判定の事です。
今回は、ミニ講座であるため敵機弾、アイテム、背景などの当たり判定は考えないモノとします。
そうなると登場するキャラクタは、自機、自機弾、敵機の3つです。
この3つは、次のようなときに衝突しますよね。
- 敵機が移動した時に、自機と衝突
- 敵機が移動した時に、自機弾と衝突
ここでの注意点は、自機弾は、複数個の衝突判定が必要という事です。
そこで分かりやすくユーザ定義関数を作成します。
用意すれば良さそうな衝突判定の関数は次の2つになります。
- 自機の1つと衝突判定を行う(FightCrash)
- 自機弾の全てと衝突判定を行う(TamaCrash)
上記のカッコ内は、ユーザ定義関数の名前です。
この2つを用意することで「当たり判定」の処理を手軽に書けます。
foreach enemyF if enemyF(cnt){ if FightCrash(enemyX(cnt),enemyY(cnt)):enemyF(cnt)=0:continue ;自機と衝突 if TamaCrash(enemyX(cnt),enemyY(cnt)):enemyF(cnt)=0:continue ;自機弾と衝突 } loop
どうですか?
上記のように書くことで当たり判定が、スッキリ見やすくなりましたね。
なお、FightCrash 関数も TamaCrash 関数も衝突したら 1 を戻します。
この 1 が返されたならば、敵機も消滅しなければならないので enemyF(cnt)=0 と有無フラグに 0 をセットしてます。
その後は、次の繰り返し処理にジャンプさせるために continue 命令を使ってます。
敵機の当たり判定
それでは、当たり判定の部分をどこに書けばよいでしょうか。一般的には、ゲームループの移動処理が終わった後に書きます。
しかし、今回はサブルーチンの個数を削減するために描画部に移動処理を書いてますね。
そこで、ゲームループには書かずに描画部の敵機移動と敵機描画の間に書くことにします。
*EnemyDraw foreach enemyF if enemyF(cnt){ enemyY(cnt)+=3:if(enemyY(cnt)>400) :enemyF(cnt)=0:continueif FightCrash(enemyX(cnt),enemyY(cnt)) :enemyF(cnt)=0:continue if TamaCrash(enemyX(cnt),enemyY(cnt)) :enemyF(cnt)=0:continuepos enemyX(cnt),enemyY(cnt) color $00,$FF,$FF:mes "Ж" } loop return
これで次の2つは、描画部に組み込んだことになります。
- 敵機が移動した時に、自機と衝突
- 敵機が移動した時に、自機弾と衝突
自機のサブルーチン
続いて自機の処理部ですが、ゲームループに自機の移動&描画、自機弾の発射を書いてますね。
この部分を分かりやすくサブルーチンにしましょう。
*Main screen 0,600,400,SCREEN_FIXEDSIZE font MSGOTHIC,50 randomize repeat redraw 0stick key,%11111 gosub *EnemyBirth gosub *FightDraw gosub *EnemyDraw gosub *TamaDrawredraw 1 await (1000/60) loop stop*FightDraw if(key&1):x-=5:if(x<0):x=0 if(key&2):y-=5:if(y<0):y=0 if(key&4):x+=5:if(x>550):x=550 if(key&8):y+=5:if(y>350):y=350 if(key&16):gosub *TamaBirth color $00,$00,$00:boxf color $00,$FF,$00:pos x,y:mes "山" return
当たり判定の考え
当たり判定には、色々なタイプがあります。
- 矩形+矩形
- 矩形+点
- 円+円
- 円+点
- 点+点
- 差分
上記の6つが代表ですが、それ以外も多数ありますね。
そして、一般的な当たり判定は、矩形+矩形だと思います。
しかし、今回のミニ講座では、理解しやすい差分を使います。
上記の図は、これから衝突しようとしてる2つのキャラクタです。
これから緑色が青色に向かって、衝突しようとしてます。
このとき、キャラクタの重なり具合を順に追って考えてみましょう。
タイミング(1)
このタイミングでは、2つのキャラクタは衝突してませんね。
差分の|x2-x1|の距離が(size1/2)+(size2/2)よりも小さくないため「衝突してない」と判断できます。
タイミング(2)
このタイミングでは、2つのキャラクタは衝突してます。
差分の|x2-x1|の距離が(size1/2)+(size2/2)よりも小さくなったので「衝突した」と判断できます。
タイミング(3)
このタイミングでも、2つのキャラクタは衝突してます。
差分の|x2-x1|の距離が(size1/2)+(size2/2)よりも小さくなったので「衝突した」と判断できます。
タイミング(4)
このタイミングでも、2つのキャラクタは衝突してます。
差分の|x2-x1|の距離が(size1/2)+(size2/2)よりも小さくなったので「衝突した」と判断できます。
タイミング(5)
このタイミングでは、2つのキャラクタは衝突してません。
差分の|x2-x1|の距離が(size1/2)+(size2/2)よりも小さくないため「衝突してない」と判断できます。
まとめ
まとめると緑色(x2)から青色(x1)を引いた絶対値を求めます。
上記の|x2-x1|と表現されてる部分が、数学上で絶対値を求めることを意味してます。
そして青色のサイズ(size1)を2で割った値と緑色のサイズ(size2)を2で割った値を足した距離を求めます。
その後に差分の絶対値と距離を比較して、絶対値が距離よりも小さければ「衝突した」と判断できます。
また、ミニ講座では size1 も size2 も同じ 50 ドットですから、距離=(size1/2)+(size2/2)=(50/2)+(50/2)=25+25=50となります。
決して size1 の 50 ドットとか、size2 の 50 ドットが2つのキャラクタの距離になる訳ではありません。ご注意。
それでは、当たり判定の条件式を整理します。
- |x2-x1| < 50 … 衝突してる
- |x2-x1| = 50 … 衝突してない
- |x2-x1| > 50 … 衝突してない
差分の絶対値が、距離である 50 ドットよりも小さければ、衝突してると判断すれば良いわけです。
それでは、縦軸の当たり判定も載せます。
- |y2-y1| < 50 … 衝突してる
- |y2-y1| = 50 … 衝突してない
- |y2-y1| > 50 … 衝突してない
見れば分かると思いますが、横軸と縦軸は同じ考えで、当たり判定(衝突判定)を行えば良いわけです。
それでは、自機の衝突判定(FightCrash)、自機弾の衝突判定(TamaCrash)をそれぞれ作成しましょう。
自機の衝突判定
#defcfunc FightCrash int _x_,int _y_ if(abs(x-_x_)<50)and(abs(y-_y_)<50){ fight-- return 1 } return 0
上記のユーザ定義関数 FightCrash は、敵機の座標を引数で受け取ります。
そして、自機の座標との差分を取り、その絶対値が距離である 50 ドットよりも小さい場合は、衝突したと判定します。
その後は、自機の残機数を1つ減らして、衝突したことを意味する 1 を返します。
衝突してない場合は 0 を返します。
自機弾の衝突判定
#defcfunc TamaCrash int _x_,int _y_ n=0 foreach tamaF if tamaF(cnt){ if(abs(tamaX(cnt)-_x_)<50)and(abs(tamaY(cnt)-_y_)<50){ tamaF(cnt)=0 n=1 break } } loop return n
上記のユーザ定義関数 TamaCrash は、敵機の座標を引数で受け取ります。
そして、自機弾の座標との差分を取り、その絶対値が距離である 50 ドットよりも小さい場合は、衝突したと判定します。
その後は、自機弾の有無フラグを 0 として、衝突したことを意味する 1 を返します。
衝突してない場合は 0 を返します。
当たり判定
それでは第5回目の「当たり判定」ソースを紹介します。
//------------------------------------------------------------------------------ // シューティング・ゲームのミニ講座 for HSP(Ver.3.3.2) //============================================================================== // 第5回「当たり判定」by 科学太郎 //------------------------------------------------------------------------------ //-------------------------------------- // メイン部 //-------------------------------------- *Init ;弾丸の管理 dim tamaF,10 dim tamaX,10 dim tamaY,10 ;敵機の管理 dim enemyF,20 dim enemyX,20 dim enemyY,20 *Main screen 0,600,400,SCREEN_FIXEDSIZE font MSGOTHIC,50 randomize repeat redraw 0 stick key,%11111 gosub *EnemyBirth gosub *FightDraw gosub *EnemyDraw gosub *TamaDraw redraw 1 await (1000/60) loop stop //-------------------------------------- // 自機の描画 //-------------------------------------- *FightDraw if(key&1):x-=5:if(x<0):x=0 if(key&2):y-=5:if(y<0):y=0 if(key&4):x+=5:if(x>550):x=550 if(key&8):y+=5:if(y>350):y=350 if(key&16):gosub *TamaBirth color $00,$00,$00:boxf color $00,$FF,$00:pos x,y:mes "山" return //-------------------------------------- // 弾丸の発生 //-------------------------------------- *TamaBirth if(tamaTrigg):tamaTrigg--:return foreach tamaF if(tamaF(cnt)==0){ tamaF(cnt)=1 tamaX(cnt)=x tamaY(cnt)=y break } loop tamaTrigg=8 return //-------------------------------------- // 弾丸の描画 //-------------------------------------- *TamaDraw foreach tamaF if tamaF(cnt){ tamaY(cnt)-=8:if(tamaY(cnt)<-50):tamaF(cnt)=0:continue pos tamaX(cnt),tamaY(cnt) color $FF,$FF,$00:mes ":" } loop return //-------------------------------------- // 敵機の発生 //-------------------------------------- *EnemyBirth if(enemyCycle):enemyCycle--:return foreach enemyF if(enemyF(cnt)==0){ enemyF(cnt)=1 enemyX(cnt)=rnd(600/50)*50 enemyY(cnt)=-50 break } loop enemyCycle=30 return //-------------------------------------- // 敵機の描画 //-------------------------------------- *EnemyDraw foreach enemyF if enemyF(cnt){ enemyY(cnt)+=3:if(enemyY(cnt)>400) :enemyF(cnt)=0:continue if FightCrash(enemyX(cnt),enemyY(cnt)) :enemyF(cnt)=0:continue if TamaCrash(enemyX(cnt),enemyY(cnt)) :enemyF(cnt)=0:continue pos enemyX(cnt),enemyY(cnt) color $00,$FF,$FF:mes "Ж" } loop return //-------------------------------------- // 自機の衝突判定 //-------------------------------------- #defcfunc FightCrash int _x_,int _y_ if(abs(x-_x_)<50)and(abs(y-_y_)<50){ fight-- return 1 } return 0 //-------------------------------------- // 自機弾の衝突判定 //-------------------------------------- #defcfunc TamaCrash int _x_,int _y_ n=0 foreach tamaF if tamaF(cnt){ if(abs(tamaX(cnt)-_x_)<50)and(abs(tamaY(cnt)-_y_)<50){ tamaF(cnt)=0 n=1 break } } loop return n //------------------------------------------------------------------------------ // End of lesson-5.hsp //------------------------------------------------------------------------------
これで「当たり判定」は終わります。
次は敵機を破壊した時の「爆発アニメ」を紹介します。(次を読む)
※コメント投稿者のブログIDはブログ作成者のみに通知されます