今回は当たり判定についてです。

STGを作ったことがある人には「あぁ、ピタゴラスの定理で判定したらすぐだな」とピンと来るかもしれませんが、

速いショットを撃たせる場合、それだけじゃすまない場合があります。

もし当たり判定についてピタゴラスの定理で判定する方法がわからなければ

ゲームプログラミングの館s11章で確認しておいて下さい。→リンク

当たり判定の有効範囲が10だとして、弾のスピードが100だとどうなるでしょう。

弾は100ずつ進むのに、当たり判定はその都度10ずつしか判定しないので、

弾が高速に移動する場合、調査していないエリアが出てきてしまうのです。

よって敵が自機弾をすり抜ける事があります。

そんなことの無いように、自機ショットが通った所は少しずつ調査し、漏れがないようにしないといけません。

なので、ちょっとメンドクサイですが、以下のように実装してみましょう。

out_judge_cshot関数では渡されたi番の自機ショットデータとs番の敵データの当たりを判定するのですが、

>if(cshot[i].cnt>0){

つまり一度でも軌道計算された弾なら一度1フレーム前の位置まで戻してから

弾と敵との当たり判定の合計分ずつ現在の位置に近づけていき、その都度、毎回当たっていないか調査していきましょう。

それを実装したのが以下です。


---- out.cpp を変更 ----

#include "../include/GV.h"

#define ENEMY_RANGE_MAX 4
#define CSHOT_RANGE_MAX 2

//敵の当たり判定範囲
int enemy_range[ENEMY_RANGE_MAX]={16,30,16,50};
//自機ショットの当たり判定範囲
int cshot_range[CSHOT_RANGE_MAX]={6,};

//当たったかどうかを判定する
int out_judge_cshot(int i,int s){
        int j;
        if(cshot[i].cnt>0){//ショットの軌道が1度でも計算されていたら
                double x=cshot[i].x-enemy[s].x;//敵と自機ショットとの距離
                double y=cshot[i].y-enemy[s].y;
                //オーバーフロー対策
                if(cshot[i].knd>=CSHOT_RANGE_MAX || enemy[s].knd>=ENEMY_RANGE_MAX)
                        printfDx("out_judge_cshot内オーバーフロー");
                //敵の当たり判定と自機ショットの当たり判定の合計範囲
                double r=cshot_range[cshot[i].knd]+enemy_range[enemy[s].knd];
                //中間を計算する必要があれば
                if(cshot[i].spd>r){
                        //1フレーム前にいた位置を格納する
                        double pre_x=cshot[i].x+cos(cshot[i].angle+PI)*cshot[i].spd;
                        double pre_y=cshot[i].y+sin(cshot[i].angle+PI)*cshot[i].spd;
                        double px,py;
                        for(j=0;j<cshot[i].spd/r;j++){//進んだ分÷当たり判定分ループ
                                px=pre_x-enemy[s].x;
                                py=pre_y-enemy[s].y;
                                if(px*px+py*py<r*r)
                                        return 1;
                                pre_x+=cos(cshot[i].angle)*r;
                                pre_y+=sin(cshot[i].angle)*r;
                        }
                }
                if(x*x+y*y<r*r)//当たり判定内なら
                        return 1;//当たり
        }
        return 0;
}

//敵が死ぬかどうかの決定
void enemy_death_judge(int s){
        int i;
        se_flag[8]=1;//敵に当たった音
        if(enemy[s].hp<0){//敵のHPが0未満になったら
                enemy[s].flag=0;//敵を消滅させる
                se_flag[1]=1;//敵のピチュり音
                for(i=0;i<SHOT_MAX;i++){//敵総数分
                        if(shot[i].flag!=0){//登録されている弾幕データがあれば
                                if(s==shot[i].num){//その敵が登録した弾幕があれば
                                        shot[i].flag=2;//それ以上弾幕を続けないフラグを立てる
                                        break;
                                }
                        }
                }
        }
}

//当たり判定メイン
void out_main(){
        int i,s;
        for(i=0;i<CSHOT_MAX;i++){//自機ショット総数
                if(cshot[i].flag>0){
                        for(s=0;s<ENEMY_MAX;s++){//敵総数
                                if(enemy[s].flag>0){
                                        if(out_judge_cshot(i,s)){//自機ショットと敵が当たっていれば
                                                cshot[i].flag=0;//その自機ショットを消す
                                                enemy[s].hp-=cshot[i].power;//弾の持つパワー分HPを減らす
                                                enemy_death_judge(s);//敵が死ぬかどうかを決める
                                        }
                                }
                        }
                }
        }
}


out_mainで自機ショット総数と敵総数を総当りで判定させます。

ではこれに伴っていつものように以下変更します。



---- main.cpp 以下の部分を変更 ----

            case 100://通常処理
                calc_ch();   //キャラクタ計算
                ch_move();   //キャラクタの移動制御
                cshot_main();//自機ショットメイン
                enemy_main();//敵処理メイン
                shot_main(); //ショットメイン
                out_main();  //当たり計算
                graph_main();//描画メイン
                stage_count++;
                break;

---- function.h に以下を追加 ----

//out.cpp
        GLOBAL void out_main();


---- load.cpp の load関数に以下を追加 ----

        sound_se[1]=LoadSoundMem("dat/se/enemy_death.wav");
        sound_se[2]=LoadSoundMem("dat/se/cshot.wav");
        sound_se[8]=LoadSoundMem("dat/se/hit.wav");
        ChangeVolumeSoundMem( 50, sound_se[0] ) ;//各素材の再生ボリュームを設定
        ChangeVolumeSoundMem(128, sound_se[1] ) ;
        ChangeVolumeSoundMem(128, sound_se[2] ) ;
        ChangeVolumeSoundMem( 80, sound_se[8] ) ;


---- music.cpp の music_play関数を以下のように変更 ----

void music_play(){
        int i;
        for(i=0;i<SE_MAX;i++){
                if(se_flag[i]==1){
                        if(CheckSoundMem(sound_se[i])!=0){
                                if(i==8)continue;
                                StopSoundMem(sound_se[i]);
                        }
                        PlaySoundMem(sound_se[i],DX_PLAYTYPE_BACK);
                }
        }
}


最後に追加したmusic_play関数の

if(i==8)continue;

は何故かいてあるかというと、敵に当たった時の音である8番の効果音は、

鳴ってるときに停止してから再生しなおすと、プツプツ途切れて聞こえるのです。

つまり、再生し終わってから再生しないといけないんですね。

今の所それが必要なのは8だけなので今はこうしておきましょう。

エクセルデータは適当に変更して下さい。





実行結果


- Remical Soft -