プログラミングのメモ帳(C/C++/HSP)

日々のプログラミングで気づいた点や小技集を紹介します。(Windows 10/XP/Vista、VC2017、HSP)

[HSP]第5回 当たり判定 (シューティング・ゲームのミニ講座)

2013年12月01日 05時28分05秒 | HSP講座

スクリプト言語の HSP については、公式ホームページの「HSPTV!」をどうぞ。


なお、このページはシューティング・ゲームのミニ講座シリーズの1つです。
今回は第5回「当たり判定」について説明します。(戻る)

当たり判定とは

まず最初に「当たり判定」とは、キャラクタ同士の衝突判定の事です。
今回は、ミニ講座であるため敵機弾、アイテム、背景などの当たり判定は考えないモノとします。
そうなると登場するキャラクタは、自機、自機弾、敵機の3つです。
この3つは、次のようなときに衝突しますよね。

  1. 敵機が移動した時に、自機と衝突
  2. 敵機が移動した時に、自機弾と衝突

ここでの注意点は、自機弾は、複数個の衝突判定が必要という事です。
そこで分かりやすくユーザ定義関数を作成します。
用意すれば良さそうな衝突判定の関数は次の2つになります。

  1. 自機の1つと衝突判定を行う(FightCrash)
  2. 自機弾の全てと衝突判定を行う(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: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

これで次の2つは、描画部に組み込んだことになります。

  1. 敵機が移動した時に、自機と衝突
  2. 敵機が移動した時に、自機弾と衝突

自機のサブルーチン

続いて自機の処理部ですが、ゲームループに自機の移動&描画、自機弾の発射を書いてますね。
この部分を分かりやすくサブルーチンにしましょう。

*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

当たり判定の考え

当たり判定には、色々なタイプがあります。

  1. 矩形+矩形
  2. 矩形+点
  3. 円+円
  4. 円+点
  5. 点+点
  6. 差分

上記の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
//------------------------------------------------------------------------------

これで「当たり判定」は終わります。
次は敵機を破壊した時の「爆発アニメ」を紹介します。(次を読む)

←前へ] [目次] [次へ→

コメント    この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« [HSP]第4回 敵機の表示 (シ... | トップ | [HSP]第6回 爆発アニメ (シ... »
最新の画像もっと見る

コメントを投稿

HSP講座」カテゴリの最新記事