弾幕:「深弾幕結界」モドキ
作製難易度:測定不能 (難しすぎ/10)

実行結果

↑音楽に合わせてみた高画質バージョンはこちら↑


まず初めに、この弾幕は作り方を紹介する気がありません(ぇ

「こんな弾幕も頑張ったら作れますよ」という紹介程度にあるものだとお考え下さいm(_ _)m

適当に作ったのでは、同じ動きにならないので、リプレイ動画をキャプチャして、

動画を止め、それをまたキャプチャし、ペイントなどで、弾の座標を確認し、

角度を求め、計算式を想像し、同じ動きになるようにする・・・。

とやっていきました。 はっきり言って大変過ぎですorz

その計算式の作り方を、大まかに説明します。

まず、リプレイ動画を何度も見て、その動きがどうなっているか確認します。

すると

魔法陣がフィールドの中心を基準に回転し、そこからバラバラと弾が中心方向に向かって撃たれていることがわかります。

この弾幕は1発目の弾は中心に飛んでいき、

そこから少しずつ弾が向かう角度が変わっていき、最後に発射された弾はある程度中心から

異なった角度に飛んでいきます。

この異なった角度がいくつなのか上のようにピクセルの座標から角度を求める方法で計測してみましょう。

例えば、最後に発射された弾はある座標から、ある方向に飛んでいくわけです。



まず、キャプチャした画像から座標を確認します。確認したところ

座標=[87,78]

でした。 中心の座標は



座標=[192,215]

でした。弾が実際に向かって飛んでいった先の座標は



座標=[185,389]

でした。

今、この3点から、最後に発射された弾と中心との直線、最後に発射された弾の軌跡の直線の2直線が解ります。

2つの直線が計算出来るなら、2つの直線が成す角度がわかりますよね。

中心の座標はわかっているのだから、そこから何度異なっているかが解れば、角度いくつで飛ばせばいいかわかるわけです。

そこで、4点を入力したら2直線が成す角度を表示するツールを作ってみました。


#include <stdio.h>
#include <math.h>

#define PI 3.141592653589793238462643383279

typedef struct{
        double x,y;
}p_t;

int main(){
        p_t p[4];
        char st[2][5]={{"始点"},{"終点"}};
        double angle0,angle1;
        for(int i=0;i<4;i++){
                printf("%dつ目の%s座標を入力(x,y)\n",i/2+1,st[i%2]);
                scanf(" %lf %lf",&p[i].x,&p[i].y);
        }
        angle0=atan2(p[1].y-p[0].y,p[1].x-p[0].x);
        angle1=atan2(p[3].y-p[2].y,p[3].x-p[2].x);

        printf("\n角度 = %.8f[rad]\n角度 = %.2f[°]\n"
                ,angle1-angle0,(angle1-angle0)/PI*180);

        return 0; 
}


実行結果

1つ目の始点座標を入力(x,y)
87 78
1つ目の終点座標を入力(x,y)
192 215
2つ目の始点座標を入力(x,y)
87 78
2つ目の終点座標を入力(x,y)
185 389

角度 = 0.34866626[rad]
角度 = 19.98[°]


このプログラムを使って角度を計算してみると、結果19.98°ということがわかりました。

1°程度は誤差があるものと考えると20°であると考える事が適切でしょう。

後は60fpsなら1フレームが16.66msであることから、距離と時間から速さを求め、

その軌道にあうように計算式を作っていきます。

今回とても大変だったので、2週目までしか作っていません、ごめんなさいm(_ _;)m

後、弾が一時停止する時間を設定するcnt_tillや動き出す時間を設定するcnt_sttという変数を

弾構造体に追加していますが、これは今回しか使わない変数ですので、

今回のみ追加しています。

また、弾の発射位置となっている魔法陣が回転しながら移動していますね。

これは、childという構造体を使って実現しています。

この機能はもうちょっと後で紹介しようと思っていた機能であるため、ここでは紹介しません。

こんな事も出来るんだ程度にご覧下さい。


//深弾幕結界
void boss_shot_bulletH010(){
#define TM010 9000 //最大周期(特に設定する意味は無い)
#define DIST010  (FMX/2*1.18) //chidlが1周目円運動する大きさ
#define DIST0101 (FMX/2*0.95) //childが2周目に円運動する大きさ
#define HANSHU 120 //childが1周目に半周する時間
#define HANSHU1 180 //childが2周目に半周する時間
#define GOOUT010 90    //childが中央から外へ行く時間
#define KAISHI010 (HANSHU) //chidlが発射を開始する時間
#define KAISHI010_1 (HANSHU1*0.6)
#define CHILD_TIME (HANSHU*5+HANSHU/3)            //childが存在する時間
#define CHILD_TIME1 (HANSHU1*3+HANSHU1*0.4)            //childが存在する時間
#define CHILD_SHOT_TIME (CHILD_TIME-KAISHI010)//childが実際に発射している時間
#define CHILD_SHOT_TIME1 (CHILD_TIME1-KAISHI010_1)//childが実際に発射している時間
#define ANG0 PI/9 //1周目の角度
#define ANG1 PI/6 //2周目の角度
#define TIME1 900 //1周目が終わる時間
#define RAG 20 //外に発射されて内に発射されるまでのラグ
#define TERM0 20 //全て発射されてから動き出すまでの時間
#define ST_ED 130 //最初に動き出してから最後の弾が動き出すまでの時間
#define ST0 (CHILD_SHOT_TIME+TERM0) //最初の弾が動く次官
#define ED0 (CHILD_SHOT_TIME+TERM0+ST_ED) //最後の弾が動く時間
#define TERM1 47
#define ST_ED1 105
#define ST1 (CHILD_SHOT_TIME1+TERM1)
#define ED1 (CHILD_SHOT_TIME1+TERM1+ST_ED1)

    int i,j,k,t=boss_shot.cnt%TM010,t2=boss_shot.cnt;
    int tt1=boss_shot.cnt-TIME1;
    static int num,flag,knum;
    static double child_dist,child_angle,child_dist2,child_angle2;
    if(t2==0){//最初なら
        input_phy_pos(FMX/2,FMY/2, 50);
        num=-1;
        flag=0;
    }
    //周期の最初
    if(t==0 || t2==TIME1){
        num++;
        //childデータの初期化
        child_dist=0;
        child_angle=0;
        child_dist2=0;
        child_angle2=0;
        knum=0;
    }
    //childの登録
    if(t2==GOOUT010 || t2==TIME1){
        int j=2;
        for(i=j-2;i<j;i++){
            child[i].flag =1;
            child[i].x      =boss.x;
            child[i].y      =boss.y;
            child[i].range=0.5;
            child[i].spd  =1;
            child[i].angle=0;
            child[i].knd  =0;
            child[i].col  =0;
            child[i].cnt  =0;
            child[i].state=i;
        }
    }
    //1周目
    if(num==0){
        if(GOOUT010<=t){
            //出現してから外向きに広がっていくまで
            if(GOOUT010<=t && t<GOOUT010+KAISHI010)
                child_dist+=DIST010/KAISHI010;
            //常に回転
            child_angle+=PI/HANSHU;
        }
    }
    //2周目
    if(num==1){
        //出現してから外向きに広がっていくまで
        if(child[0].cnt<KAISHI010_1)
            child_dist+=DIST0101/KAISHI010_1;
        //常に回転
        child_angle-=PI/HANSHU1;
    }
    //childデータ計算
    for(i=0;i<CHILD_MAX;i++){
        if(child[i].flag>0){//登録されていたら
            //軌道計算
            child[i].x=boss.x+cos(child_angle+PI*child[i].state)*child_dist;
            child[i].y=boss.y+sin(child_angle+PI*child[i].state)*child_dist;
//1回目
            if(num==0){
                if(KAISHI010<child[i].cnt){//発射開始カウント以上なら
                    if(((t+6)%36)/4<=5 && t%4==0){//この時弾登録
                        for(j=0;j<3;j++){//外向き3way発射
                            k=knum++;
                            boss_shot.bullet[k].col   = child[i].state;//弾の色
                            boss_shot.bullet[k].x     = child[i].x;//座標
                            boss_shot.bullet[k].y     = child[i].y;
                            boss_shot.bullet[k].knd   = 6;//弾の種類
                            boss_shot.bullet[k].angle 
                                = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度
                            boss_shot.bullet[k].flag  = 1;
                            boss_shot.bullet[k].cnt   = 0;
                            boss_shot.bullet[k].state = 10+j+10*child[i].state;//状態
                            boss_shot.bullet[k].spd   = 0.4;//スピード
                            se_flag[0]=1;
                        }
                    }
                    else if(t%4==0){//それ以外の時で、4回に一回
                        for(j=0;j<3;j++){
                            k=knum++;
                            boss_shot.bullet[k].col   = child[i].state;//弾の色
                            boss_shot.bullet[k].x     = child[i].x;//座標
                            boss_shot.bullet[k].y     = child[i].y;
                            boss_shot.bullet[k].knd   = 6;//弾の種類
                            boss_shot.bullet[k].angle 
                                = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度
                            boss_shot.bullet[k].flag  = 1;
                            boss_shot.bullet[k].cnt   = 0;
                            boss_shot.bullet[k].state = 30;
                            boss_shot.bullet[k].spd   = 0.4;//スピード
                        }
                    }
                }
                //消える時間なら消す
                if(child[i].cnt>CHILD_TIME)
                    child[i].flag=0;
            }
//1回目end
//2回目
            if(num==1){
                //発射カウント以上なら
                if(KAISHI010_1<child[i].cnt){
                    if((tt1-55)%(3*22)<(3*15) && t%3==0){//この時登録
                        for(j=0;j<3;j++){//外向き発射
                            k=knum++;
                            boss_shot.bullet[k].col   = child[i].state;//弾の色
                            boss_shot.bullet[k].x     = child[i].x;//座標
                            boss_shot.bullet[k].y     = child[i].y;
                            boss_shot.bullet[k].knd   = 6;//弾の種類
                            boss_shot.bullet[k].angle 
                                = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度
                            boss_shot.bullet[k].flag  = 1;
                            boss_shot.bullet[k].cnt   = 0;
                            boss_shot.bullet[k].state = 10+j+10*child[i].state;
                            boss_shot.bullet[k].spd   = 0.4;//スピード
                            se_flag[0]=1;
                        }
                    }
                    else if(t%3==0){//それ以外の時で3回に1回
                        for(j=0;j<3;j++){//外向き3way
                            k=knum++;
                            boss_shot.bullet[k].col   = child[i].state;//弾の色
                            boss_shot.bullet[k].x     = child[i].x;//座標
                            boss_shot.bullet[k].y     = child[i].y;
                            boss_shot.bullet[k].knd   = 6;//弾の種類
                            boss_shot.bullet[k].angle 
                                = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度
                            boss_shot.bullet[k].flag  = 1;
                            boss_shot.bullet[k].cnt   = 0;
                            boss_shot.bullet[k].state = 30;
                            boss_shot.bullet[k].spd   = 0.4;//スピード
                        }
                    }
                }
                //消える時間なら消す
                if(child[i].cnt>CHILD_TIME1)
                    child[i].flag=0;
            }
        }
    }
    for(i=0;i<BOSS_BULLET_MAX;i++){
        if(boss_shot.bullet[i].flag>0){
            int cnt=boss_shot.bullet[i].cnt;
            int state=boss_shot.bullet[i].state;
            //0は1周目の内側発射弾、100は2週目の内側発射弾
            if(state==0 || state==100){
                //停止する時間なら止めてステータスを変える
                if(boss_shot.bullet[i].cnt_till==cnt){
                    boss_shot.bullet[i].spd=0;
                    boss_shot.bullet[i].state=1;
                    if(state==100)
                        boss_shot.bullet[i].state+=100;

                }
            }
            //1,101は上記状態から止まった状態
            if(state==1 || state==101){
                //動き出す時間なら動き出させてステータスを変える
                if(boss_shot.bullet[i].cnt_stt==cnt){
                    boss_shot.bullet[i].state++;
                    if(flag==0)
                        flag=1;
                    boss_shot.bullet[i].cnt=0;
                }
            }
            //2,102は上記状態から加速し始めている(動いている)状態
            if(state==2 || state==102){
                if(flag==1)
                    boss_shot.bullet[i].spd+=0.05;
                if(boss_shot.bullet[i].spd>2.0 && flag==1)
                    flag=2;
                if(flag==2){
                    if(state==2)
                        if(boss_shot.bullet[i].spd<2.0)
                            boss_shot.bullet[i].spd+=0.05;
                    if(state==102)
                        if(boss_shot.bullet[i].spd<2.7)
                            boss_shot.bullet[i].spd+=0.05;
                }
            }
            //11,21はそれぞれ1周目、2週目の外側発射弾
            if(state==11 || state==21){
                if(cnt==RAG){
                    double zero_one;
                    double ang,spd;
                    if(num==0){
                        zero_one=(double)(t-GOOUT010-RAG-HANSHU)/CHILD_SHOT_TIME;
                        ang=ANG0*zero_one;
                        spd=2.3;
                    }
                    if(num==1){
                        zero_one=(double)(tt1-RAG-KAISHI010_1)/CHILD_SHOT_TIME1;
                        ang=PI/6-(PI/6+PI/4)*zero_one;
                        if(tt1==143)
                            ang=PI/3;
                        spd=2.3*0.55;
                    }
                    k=knum++;
                    boss_shot.bullet[k].col   = boss_shot.bullet[i].state==11 ? 0 : 1;//弾の色
                    boss_shot.bullet[k].x     = boss_shot.bullet[i].x;//座標
                    boss_shot.bullet[k].y     = boss_shot.bullet[i].y;
                    boss_shot.bullet[k].knd   = 6;//弾の種類
                    boss_shot.bullet[k].angle = bossatan3(k,boss.x,boss.y)+ang;//角度
                    boss_shot.bullet[k].flag  = 1;
                    boss_shot.bullet[k].cnt   = 0;
                    if(num==0){
                        boss_shot.bullet[k].state = 0;
                        boss_shot.bullet[k].cnt_till= (int)(150*zero_one+10);
                        boss_shot.bullet[k].cnt_stt 
                            = (int)((ST0-(ST_ED+TERM0))*(1-zero_one)+(ST_ED+TERM0)+10);
                    }
                    if(num==1){
                        boss_shot.bullet[k].state = 100;
                        boss_shot.bullet[k].cnt_till= (int)(150*zero_one);
                        boss_shot.bullet[k].cnt_stt 
                            = (int)((ST1-(ST_ED1+TERM1))*(1-zero_one)+(ST_ED1+TERM1));
                    }
                    boss_shot.bullet[k].spd   = spd;
                }
            }
        }
    }
}




謝辞:
36章はarrayさんの励ましとアドバイスによって製作する事が出来ました。ありがとうございました。
arrayさんが製作された36章を製作するきっかけとなった動画をご紹介します。




- Remical Soft -