DirectXプログラミング講座 〜Vol21. 【2Dシューティング】当たり判定〜

自機、敵機、ショット(弾)とシューティングゲームに必要な最低限のオブジェクトが出揃いました。あとは、それらオブジェクトの相互関係、即ち、「弾が敵機に当たったか!?」「自機に敵機がぶつかったか!?」を判断するための当たり判定のコードを作ります。

今回の章で、シューティング理論の基礎部分すべてが完了することになります。


今回のサンプルプロジェクト

コーディング作業

前回のVol20. 【2Dシューティング】敵機を出現させようで作成したコードに手を加える形で作業を進めます。赤色で示すコードが前回からの変更点または追加点です。

HitTest関数のコーディング

パラメータpos1、size1及びpos2、size2には、2つの物体(自機と敵機、または弾と敵機など…)の位置と大きさが引き渡されます。その2つの物体が2D空間上で重なった場合に、HitTest関数は1を返します。重ならなかった場合は0を返します。

この関数は、fight.sbpの先頭コードの下にでも挿入しておきましょう(※fight.sbp内のグローバル領域であればどこでも大丈夫です)。

'------------------
' 衝突判定用の関数
'------------------
Function HitTest(
    ByRef pos1 As POINTAPI,
    ByRef size1 As SIZE,
    ByRef pos2 As POINTAPI,
    ByRef size2 As SIZE) As Long
    If pos1.x<pos2.x+size2.cx and pos1.x+size1.cx>pos2.x and _
        pos1.y<pos2.y+size2.cy and pos1.y+size1.cy>pos2.y Then
        HitTest=1
    Else
        HitTest=0
    End If
End Function

CPlane::GetHitPositionAndSizeのコーディング

当たり判定の際に、自機の位置と大きさを取得するための関数を定義します。

尚、自機の大きさ(width、height)は32*32というようになっていますが、この大きさを当たり判定用として返してしまうと、敵機をちょっとかすめただけでも当たったと判断されるような、少し不自然な動きをしてしまいます。それを回避するために、実際の大きさの70%を当たり判定用の領域として返しようにします。

例えば、左の画像で説明しますと、赤で囲まれた領域が当たり判定に使われます。かすめただけでは当たりと判定されないように、小さめにしておくのがコツです。



'---------------
' 自機クラス
'---------------
Class CPlane
    '位置・高さ・幅
    x As Long
    y As Long
    width As Long
    height As Long

    '移動スピード
    speed As Long

    '傾き
    RudderSituation As Single


    '自機が画面からはみ出さないようにチェックする関数
    Sub EreaOutCheck()
        If x-width/2<0 Then
            x=width/2
        End If
        If x+width/2>SCREEN_WIDTH Then
            x=SCREEN_WIDTH-width/2
        End If

        If y-height/2<0 Then
            y=height/2
        End If
        If y+height/2>SCREEN_HEIGHT Then
            y=SCREEN_HEIGHT-height/2
        End If
    End Sub

Public

    'コンストラクタ
    Sub CPlane()
        width=32
        height=32

        '初期位置
        x=SCREEN_WIDTH/2
        y=SCREEN_HEIGHT*0.8

        '初期スピード
        speed=5
    End Sub

    'デストラクタ
    Sub ~CPlane()
    End Sub


    '位置と大きさを取得する関数
    Sub GetHitPositionAndSize(ByRef pos As POINTAPI, ByRef size As SIZE)
        '機体のヒットポジションは小さめにする
        Dim TempWidth As Long, TempHeight As Long
        TempWidth=width*0.7
        TempHeight=height*0.7

        pos.x=x-TempWidth/2
        pos.y=y-TempHeight/2
        size.cx=TempWidth
        size.cy=TempHeight
    End Sub


    '描画
    Sub Draw()
        Dim TextureX As Long, TextureY As Long
        TextureX=32*(Fix(RudderSituation)+2)
        TextureY=0

        pImage_CharaMap1->DrawStretch(
            x-width/2    +SCREENBASE_X,
            y-height/2   +SCREENBASE_Y,
            width,
            height,

            TextureX,
            TextureY,
            32,
            32)
    End Sub

    '自機を移動させる関数
    Sub MoveUp()
        y=y-speed
        EreaOutCheck()
    End Sub
    Sub MoveDown()
        y=y+speed
        EreaOutCheck()
    End Sub
    Sub MoveLeft()
        x=x-speed
        EreaOutCheck()

        If RudderSituation>-2 Then
            '左方向に移動中は自機の傾きをマイナス値にする
            RudderSituation=RudderSituation-1/speed
        End If
    End Sub
    Sub MoveRight()
        x=x+speed
        EreaOutCheck()

        If RudderSituation<2 Then
            '右方向に移動中は自機の傾きをプラス値にする
            RudderSituation=RudderSituation+1/speed
        End If
    End Sub
    Sub NoMove()
        '移動していないときは自機の傾きを0に近づける
        If RudderSituation<0 Then
            RudderSituation=RudderSituation+1/speed
        ElseIf RudderSituation>0 Then
            RudderSituation=RudderSituation-1/speed
        End If
    End Sub

    Sub Shot()
        '弾を撃つ
        Dim pShot As *CShot

        pShot=New CShot(x,y-width/2)
    End Sub
End Class

CShot::GetHitPositionAndSizeのコーディング

当たり判定の際に、ショット(弾)の位置と大きさを取得するための関数を定義します。弾については、余裕領域を考えなくても良いので、そのままの大きさを返すようにします。

'--------------
' ショット(弾)クラス
'--------------
Class CShot
    x As Long
    y As Long
    width As Long
    height As Long

    speed As Long

    Sub EreaOutCheck()
        'はみ出しチェック
        If y-height/2<0 Then
            '画面をはみ出したので、自爆させる
            Delete VarPtr(This)
        End If
    End Sub

Public
    Sub CShot(sx As Long, sy As Long)
        width=4
        height=16

        speed=15

        '初期位置
        x=sx
        y=sy

        AddShot(VarPtr(This))
    End Sub

    Sub ~CShot()
        DeleteShot(VarPtr(This))
    End Sub


    '位置と大きさを取得する関数
    Sub GetHitPositionAndSize(ByRef pos As POINTAPI, ByRef size As SIZE)
        pos.x=x-width/2
        pos.y=y-height/2
        size.cx=width
        size.cy=height
    End Sub


    '描画
    Sub Draw()
        pImage_CharaMap2->DrawStretch(
            x-width/2    +SCREENBASE_X,
            y-height/2   +SCREENBASE_Y,
            width,
            height,

            6,
            48,
            width,
            height)

        y=y-speed
        EreaOutCheck()
    End Sub
End Class

CEnemie::GetHitPositionAndSizeのコーディング

当たり判定の際に、敵機の位置と大きさを取得するための関数を定義します。自機(CPlane)のときと同様、ヒットポジションを小さめにセットしておきます。

'--------------
' 敵機クラス
'--------------
Class CEnemie
    '位置
    x As Long
    y As Long
    width As Long
    height As Long

    'スピード
    speed As Long

    Sub EreaOutCheck()
        'はみ出しチェック
        If y>SCREEN_HEIGHT Then
            '画面をはみ出したので、自爆させる
            Delete VarPtr(This)
        End If
    End Sub

Public
    Sub CEnemie()
        width=24
        height=24

        speed=1

        '初期位置
        x=Rnd()*SCREEN_WIDTH
        y=0

        AddEnemie(VarPtr(This))
    End Sub

    Sub ~CEnemie()
        DeleteEnemie(VarPtr(This))
    End Sub


    '位置と大きさを取得する関数
    Sub GetHitPositionAndSize(ByRef pos As POINTAPI, ByRef size As SIZE)
        '機体のヒットポジションは小さめにする
        Dim TempWidth As Long, TempHeight As Long
        TempWidth=width*0.7
        TempHeight=height*0.7

        pos.x=x-TempWidth/2
        pos.y=y-TempHeight/2
        size.cx=TempWidth
        size.cy=TempHeight
    End Sub


    '描画
    Sub Draw()
        pImage_CharaMap1->DrawStretch(
            x-width/2    +SCREENBASE_X,
            y-height/2   +SCREENBASE_Y,
            width,
            height,

            0,
            160,
            width,
            height)

        y=y+speed
        EreaOutCheck()
    End Sub
End Class

InputActionProc関数のコーディング

ゲームループから毎秒60回(60FPSの場合)呼び出されるInputActionProc関数では、常に当たり判定を行う状況を作り出します。このプログラムでは、自機・敵機・弾の動きを常に監視し、互いに衝突したかどうかを調べています。

敵機に弾が当たったときは、弾と敵機を消滅させます。

自機と敵機が衝突してしまったときは、敵機を消滅させ、ビープ音を鳴らします。通常のゲームであれば、自機を破壊してしまって、ゲームオーバーにしてしまっても良いでしょう。

Sub InputActionProc()
    ' TODO: この位置に入力に関するコードを記述してください。
    '       (キーボード、マウス、ジョイパッド、ジョイスティックなどによる入力)

    ' メモ - キャラクタの移動など、状況進行(アクション)を意味するコードを
    '        記述することもできます。

    Dim KeyState[255] As Byte
    pInKey->GetState(KeyState)

    'ESCキーが押されたときは終了する
    If KeyState[DIK_ESCAPE] and &H80 Then
        PostQuitMessage(0)
    End If

    If KeyState[DIK_UP] and &H80 Then
        '方向キー(上)が押されているとき
        pMyPlane->MoveUp()
    ElseIf KeyState[DIK_DOWN] and &H80 Then
        '方向キー(下)が押されているとき
        pMyPlane->MoveDown()
    End If

    If KeyState[DIK_LEFT] and &H80 Then
        '方向キー(左)
        pMyPlane->MoveLeft()
    ElseIf KeyState[DIK_RIGHT] and &H80 Then
        '方向キー(右)
        pMyPlane->MoveRight()
    Else
        '左右キーが押されていないとき
        pMyPlane->NoMove()
    End If

    ShotTime=ShotTime+1
    If KeyState[DIK_SPACE] and &H80 Then
        'スペースキーが押された、弾を発射
        If ShotTime>TempTime_Shot+3 Then
            pMyPlane->Shot()

            TempTime_Shot=ShotTime
        End If
    End If

    '一定間隔で敵機を生成
    TempTime_Enemie=TempTime_Enemie+1
    If (TempTime_Enemie mod 20)=0 Then
        Dim pEnemie As *CEnemie
        pEnemie=New CEnemie
    End If


    '-----------------------
    ' 弾と敵機の当たり判定
    '-----------------------
    Dim i As Long, i2 As Long
    Dim pos1 As POINTAPI, size1 As SIZE
    Dim pos2 As POINTAPI, size2 As SIZE
    For i=0 To ELM(MAX_SHOTS)
        If pShotsArray[i] Then
            For i2=0 To ELM(MAX_ENEMIES)
                If pEnemiesArray[i2] Then
                    pShotsArray[i]->GetHitPositionAndSize(pos1,size1)
                    pEnemiesArray[i2]->GetHitPositionAndSize(pos2,size2)
                    If HitTest(pos1,size1,pos2,size2) Then
                        'ヒットしたときは弾、敵機ともに破棄
                        Delete pShotsArray[i]
                        Delete pEnemiesArray[i2]
                        Exit For
                    End If
                End If
            Next
        End If
    Next


    '-------------------------
    ' 敵機と自機の当たり判定
    '-------------------------
    For i=0 To ELM(MAX_SHOTS)
        If pEnemiesArray[i] Then
            pMyPlane->GetHitPositionAndSize(pos1,size1)
            pEnemiesArray[i]->GetHitPositionAndSize(pos2,size2)
            If HitTest(pos1,size1,pos2,size2) Then
                'ヒットしたときは敵機を破棄(同時にビープ音を鳴らす)
                Delete pEnemiesArray[i]
                MessageBeep(0)
                Exit For
            End If
        End If
    Next
End Sub

実行してみよう

敵機に向かって弾を発射してみてください。敵機を倒すことができるようになったはずです。シューティング理論の基本はここでおしまい。あとは、このシューティングテンプレートを拡張してオリジナルのゲームを作るだけです。

次回からは、このテンプレートをどのように拡張すればよいのか、効果音や爆発アニメーションを追加しながら解説していきます☆



講座インデックスへ戻る ©2005 Discoversoft