ボスに当たり判定を付け、ボスの弾にも当たり判定を付けてみましょう。

今回ちょっと前までに忘れていた、キャラクタの装飾もちょろっとやっています。

なお、フィールド幅の定義はFIELD_MAX_Xと書いていたのですが、面倒になってきたので(汗)


FIELD_MAX_X -> FMX
FIELD_MAX_Y -> FMY
FIELD_X     -> FX
FIELD_Y     -> FY


に変更しましたので、以後ご了承下さい。

さて、当たり判定の話をしてみます。

以前の章で、当たり判定の関数は作りましたね。その時後に関数は汎用的に作れるようにすると言いました。

前回の関数と見比べてみて下さい。

以下のように変更しました。


//ショットの座標:1  当たりを判定する物体:2
int out_judge(double x1, double y1, double x2, double y2,
                          double range1, double range2, double spd1,double angle1){
    int j;
    double x=x1-x2;//敵と自機ショットとの距離
    double y=y1-y2;
    //敵の当たり判定と自機ショットの当たり判定の合計範囲
    double r=range1+range2;
    //中間を計算する必要があれば
    if(spd1>r){
        //1フレーム前にいた位置を格納する
        double pre_x=x1+cos(angle1+PI)*spd1;
        double pre_y=y1+sin(angle1+PI)*spd1;
        double px,py;
        for(j=0;j<spd1/r;j++){//進んだ分÷当たり判定分ループ
            px=pre_x-x2;
            py=pre_y-y2;
            if(px*px+py*py<r*r)
                return 1;
            pre_x+=cos(angle1)*r;
            pre_y+=sin(angle1)*r;
        }
    }
    if(x*x+y*y<r*r)//当たり判定内なら
        return 1;//当たり
    return 0;
}


前回までは、いちいち個別に変数をかいていましたが、今度は何の変数でも計算出来るようになっています。

受け取っている引数の形がかわっているだけで、アルゴリズムや処理の内容は全く変わっていません。

これはショットと物体が当たっているかを判定する関数であり、

x1,y1など1とつくものは、ショットの情報

x2,y2など2とつくものは、その物体の情報です。

これを踏まえてoutファイルを書き換えてみます。


---- out.cpp を変更 ----
#include "../include/GV.h"

#define ENEMY_RANGE_MAX 4
#define CSHOT_RANGE_MAX 2
#define CRANGE 2.0
#define BRANGE 40.0

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

//当たり判定
//ショットの座標:1  当たりを判定する物体:2
int out_judge(double x1, double y1, double x2, double y2,
              double range1, double range2, double spd1,double angle1){
    int j;
    double x=x1-x2;//敵と自機ショットとの距離
    double y=y1-y2;
    //敵の当たり判定と自機ショットの当たり判定の合計範囲
    double r=range1+range2;
    //中間を計算する必要があれば
    if(spd1>r){
        //1フレーム前にいた位置を格納する
        double pre_x=x1+cos(angle1+PI)*spd1;
        double pre_y=y1+sin(angle1+PI)*spd1;
        double px,py;
        for(j=0;j<spd1/r;j++){//進んだ分÷当たり判定分ループ
            px=pre_x-x2;
            py=pre_y-y2;
            if(px*px+py*py<r*r)
                return 1;
            pre_x+=cos(angle1)*r;
            pre_y+=sin(angle1)*r;
        }
    }
    if(x*x+y*y<r*r)//当たり判定内なら
        return 1;//当たり
    return 0;
}

//敵と自機ショットが当たったかどうかを判定する
int out_judge_cshot(int i,int s){
    if(cshot[i].cnt>0){//ショットの軌道が1度でも計算されていたら
        if(out_judge(cshot[i].x,cshot[i].y,enemy[s].x,enemy[s].y,
            cshot_range[cshot[i].knd],enemy_range[enemy[s].knd],
            cshot[i].spd,cshot[i].angle)){
                return 1;
        }
    }
    return 0;
}

//ボスと自機ショットが当たったかどうかを判定する
int out_judge_cshot_boss(int i){
    if(cshot[i].cnt>0){//ショットの軌道が1度でも計算されていたら
        if(out_judge(cshot[i].x,cshot[i].y,boss.x,boss.y,
            cshot_range[cshot[i].knd],BRANGE,cshot[i].spd,cshot[i].angle)){
                return 1;
        }
    }
    return 0;
}

//自機と敵ショットが当たったかどうかを判定する
int out_judge_enemyshot(int s,int n){
    if(shot[s].bullet[n].cnt>0){//ショットの軌道が1度でも計算されていたら
        if(out_judge(
            shot[s].bullet[n].x,shot[s].bullet[n].y,ch.x,ch.y,
            bullet_info[shot[s].bullet[n].knd].range,CRANGE,
            shot[s].bullet[n].spd,shot[s].bullet[n].angle
            )){
                return 1;
        }
    }
    return 0;
}

//自機とボスショットが当たったかどうかを判定する
int out_judge_bossshot(int n){
    if(boss_shot.bullet[n].cnt>0){//ショットの軌道が1度でも計算されていたら
        if(out_judge(
            boss_shot.bullet[n].x,boss_shot.bullet[n].y,ch.x,ch.y,
            bullet_info[boss_shot.bullet[n].knd].range,CRANGE,
            boss_shot.bullet[n].spd,boss_shot.bullet[n].angle
            )){
                return 1;
        }
    }
    return 0;
}

extern void enter_del_effect(int);

//敵が死ぬかどうかの決定
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;//敵のピチュリ音
        enter_del_effect(s);
        for(i=0;i<SHOT_MAX;i++){//敵総数分
            if(shot[i].flag!=0){//登録されている弾幕データがあれば
                if(s==shot[i].num){//その敵が登録した弾幕があれば
                    shot[i].flag=2;//それ以上弾幕を続けないフラグを立てる
                    break;
                }
            }
        }
    }
}

//sの敵にpower食らわす
void hit_enemy(int s,int power){
    enemy[s].hp-=power;//弾の持つパワー分HPを減らす
    enemy_death_judge(s);//敵が死ぬかどうかを決める
}

//ボスにpower食らわす
void hit_boss(int power){
    boss.hp-=power;//弾の持つパワー分HPを減らす
}


//自機ショットと敵との処理
void cshot_and_enemy(){
    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;//その自機ショットを消す
                        hit_enemy(s,cshot[i].power);
                        break;
                    }
                }
            }
            //ボスが出現していて、描画しないフラグがオフで、ショット中なら
            if(boss.flag==1 && boss.graph_flag==0 && boss.state==2){
                if(out_judge_cshot_boss(i)){
                    cshot[i].flag=0;
                    hit_boss(cshot[i].power);
                }
            }
        }
    }
}

//敵ショットと自機との処理
void enemyshot_and_ch(){
    int s,n;
    //雑魚敵のショット
    for(s=0;s<SHOT_MAX;s++){//敵ショット総数
        if(shot[s].flag>0){//そのショットが登録されていたら
            for(n=0;n<SHOT_BULLET_MAX;n++){//弾総数
                if(shot[s].bullet[n].flag==1){//弾が登録されていたら
                    if(out_judge_enemyshot(s,n)==1){//自機にその弾が接触していたら
                        shot[s].bullet[n].flag=0;//弾をオフ
                        if(ch.flag==0 && ch.mutekicnt==0){
                            ch.cnt=0;
                            ch.flag=1;
                            se_flag[3]=1;
                        }
                    }
                }
            }
        }
    }
    //ボスのショット
    if(boss_shot.flag>0){//そのショットが登録されていたら
        for(n=0;n<BOSS_BULLET_MAX;n++){//弾総数
            if(boss_shot.bullet[n].flag==1){//弾が登録されていたら
                if(out_judge_bossshot(n)==1){//自機にその弾が接触していたら
                    boss_shot.bullet[n].flag=0;//弾をオフ
                    if(ch.flag==0 && ch.mutekicnt==0){
                        ch.cnt=0;
                        ch.flag=1;
                        se_flag[3]=1;
                    }
                }
            }
        }
    }
}

//ボムのダメージを敵に喰らわす
void cbom_and_enemy(){
    int s;
    if(bom.flag!=1)return;
    for(s=0;s<ENEMY_MAX;s++){//敵総数
        if(enemy[s].flag>0)//その敵がいれば
            hit_enemy(s,ch.power/20);//ダメージを与える
    }
    //ボスがいて、描画しないフラグがオフで、ショット中なら
    if(boss.flag==1 && boss.graph_flag==0 && boss.state==2)
        hit_boss(ch.power/20);//喰らわす
}

//当たり判定メイン
void out_main(){
    cbom_and_enemy();//敵にボムを喰らわせる
    cshot_and_enemy();//自機ショットと敵との処理
    enemyshot_and_ch();//敵ショットと自機との処理
}


ちょっと長くなりましたが、基本的にボスの処理が雑魚と同じように入っただけで基本的な構造は全く変わっていません。

また、龍神録ではスペルカードの弾幕の時は背景がかわりますよね。

どの弾幕の時にどの背景になるか、設定するためにボスに.back_kndという変数を持たせます。

とりあえず、今はiniで、全部スペルカードにしておきましょう。


---- ini.cpp ini()の中に変更・追加 ----

        boss.appear_count[0]=50;//中ボスが出現する時刻
        for(int i=0;i<DANMAKU_MAX;i++){//弾幕それぞれのHP
                boss.set_hp[i]=10000;
                boss.hp_max=10000;
        }
        for(int i=0;i<DANMAKU_MAX;i++)//弾幕それぞれの背景の種類
                boss.back_knd[i]=1;


変数と構造体の用意もしておきます。


---- struct.h に追加 ----

//ボスの情報
typedef struct{
        int flag,cnt,knd,wtime,state,endtime,hagoromo,graph_flag;
        int hp,hp_max;
        int appear_count[2],set_hp[DANMAKU_MAX],back_knd[DANMAKU_MAX];
        double x,y,ang,spd;
        phy_t phy;
}boss_t;


---- define.h の以下を変更 ----

//フィールドの広さ
#define FMX 384
#define FMY 448
//フィールドの左上の座標
#define FX 32
#define FY 16


---- GV.h に追加 ----

GLOBAL int img_chetc[10];       //キャラクタに関する他の画像
GLOBAL int img_etc[50];//その他の画像


キャラクタの装飾をしたり、ボスのHPを表示したりする為の画像をetcとして用意しました。

それをロードして描画しておきます。


---- load.cpp のload関数内に追加 ----

        img_chetc[0]    = LoadGraph( "../dat/img/char/atari.png" );//当たり判定
        img_chetc[2]    = LoadGraph( "../dat/img/char/ball.png" );//ボール

        img_etc[1]      = LoadGraph( "../dat/img/enemy/hp.png" );
        img_etc[7]      = LoadGraph( "../dat/img/enemy/hp_boss.png" );//ボスのHP


---- graph.cppに追加・変更 ----

//ボス描画
void graph_boss(){
        int i;
        if(boss.flag==0)return;
        DrawRotaGraphF(boss.x+FX+dn.x,boss.y+FY+dn.y,1.0f,0.0f,img_dot_riria[0],TRUE);
        if(boss.hp_max==0){printfDx("graph_boss内0割り\n");return;}
        for(i=0;i<FMX*0.98*boss.hp/boss.hp_max;i++){
                if(boss.back_knd[boss.knd]==1)
                        DrawGraph(3+FX+i+dn.x,2+FY+dn.y,img_etc[7],FALSE);
                else
                        DrawGraph(3+FX+i+dn.x,2+FY+dn.y,img_etc[1],FALSE);
        }
}

//自機描画
void graph_ch(){
        double sx,sy,ny=(sin(2.0*PI*(count%50)/50)*3),ang=2.0*PI*(count%120)/120;

        if(CheckStatePad(configpad.slow)>0)//低速移動中なら
                sx=15,sy=15+ny;//引き寄せる
        else
                sx=30,sy=30+ny;//普通の位置に

        DrawRotaGraphF( ch.x-sx+FX, ch.y+sy+FY, 1.0f,  ang, img_chetc[2], TRUE );
        DrawRotaGraphF( ch.x+sx+FX, ch.y+sy+FY, 1.0f, -ang, img_chetc[2], TRUE );

        if(ch.mutekicnt%2==0){//無敵中なら点滅
                //自機表示
                DrawRotaGraphF(ch.x+FX+dn.x,ch.y+FY+dn.y,1.0f,0.0f,img_ch[0][ch.img],TRUE);
                if(CheckStatePad(configpad.slow)>0)//低速移動中なら当たり判定表示
                        DrawRotaGraphF( ch.x+FX, ch.y+FY, 1.0f, 2.0*PI*(count%120)/120, img_chetc[0], TRUE );
        }
}


スペルカード用の背景を作ってみます。

boss.kndのさす番号のboss.back_kndが1の時がスペルカードですが、今回は全部1にしてあります。

そして、背景は何もかいてないので、真っ暗になります。


---- graph_back.cpp 変更 ----

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

void graph_back00(){//通常背景
        SetDrawArea( 32 , 16 , 416 , 464 ) ;//描画可能エリアを設定
        DrawGraph(FX,count%700+FY-700,img_back[0],FALSE);
        DrawGraph(FX,count%700+FY    ,img_back[0],FALSE);
        SetDrawArea( 0, 0, 640, 480);//エリアを戻す
}

void graph_back01(){//スペルカード用背景
        SetDrawArea( 32 , 16 , 416 , 464 ) ;//描画可能エリアを設定

        //ここにスペルカード用の背景を描く

        SetDrawArea( 0, 0, 640, 480);//エリアを戻す
}

void graph_back_main(){
        //ボスがオンで、スペルカードなら
        if(boss.flag==1 && boss.back_knd[boss.knd]==1)
                graph_back01();
        else//それ以外
                graph_back00();
}


---- func.h を変更 ----

extern void boss_shot_bulletH000();

void (*boss_shot_bullet[DANMAKU_MAX])() =
{
        boss_shot_bulletH000,
        boss_shot_bulletH000,
        boss_shot_bulletH000,
        boss_shot_bulletH000,
        boss_shot_bulletH000,
        boss_shot_bulletH000,
        boss_shot_bulletH000,
};


func.hは弾幕情報をかくところです。

今はまだH000の弾幕しかありませんが、今後弾幕を増やしていくと、これが増えていきます。

そしてここに入れる関数の順番が弾幕の順番です。

今は、弾幕が終わるたびに0番の弾幕が開始されます。

7回弾幕を終えるとポインタエラーになるので、注意して下さい。


実行結果



- Remical Soft -