さてさて、いよいよ弾幕作りに入っていきます。
まずボスの弾幕を作るにはボスが必要です。
ボスの骨格を作るのが色々と面倒ですが、頑張りましょう。
ちょっとここで、何故か物理のお話をします。
龍神録ではボスが「スーっ」「スーっ」と動いていたと思います。
「ここからここまでこの時間で移動してくれ」という指示に従って動いて欲しいわけですが、
等速運動で動いて、指定地点まで来たら突然止まるモーションだと安っぽく見えてしまいます。
加速したり、減速したりして、綺麗に止まってくれないと綺麗なモーションに見えません。
そこで、「これだけの距離をこれだけの時間で綺麗に移動してくれ」という指示を受け付ける計算式を考えて見ましょう。
ちょっと数学が苦手な人には嫌な計算式かもしれませんが、
とても簡単な計算なので、みてみてください。
物理の力学で等加速度運動をする為の3つの大きな方程式があります。
下の方程式の1〜3です。vは速度、aは加速、yは距離、tは時間を示します。
今、ドンと発射して、指定距離yで指定時間tyで減速してピタっと止まることを考えます。
ということで、式Aが導けました。
tyが指定時間、ymaxが移動距離です。カウンタをtにあわせて計算すればyが求まります。
これを水平成分、鉛直成分で計算すれば綺麗なモーションが可能になります。
以下その計算式を実装しているのが、
input_phy(登録)
calc_phy(計算)
です。注釈をご覧下さい。
---- boss_shot.cpp を変更 ---- #include "../include/GV.h" #include "../include/func.h" #define V0 10.0 int serch_boss_shot(){//空き番号を返す for(int i=0;i<BOSS_BULLET_MAX;i++){ if(boss_shot.bullet[i].flag==0) return i; } return -1; } double bossatan2(){//自機と敵との成す角度 return atan2(ch.y-boss.y,ch.x-boss.x); } //物理的計算をさせる登録をする(指定時間tで定位置に戻す) void input_phy(int t){//t= 移動にかける時間 doubleymax_x,ymax_y;if(t==0)t=1; boss.phy.flag=1;//登録オン boss.phy.cnt=0;//カウンタ初期化 boss.phy.set_t=t;//移動にかける時間をセット ymax_x=boss.x-BOSS_POS_X;//移動したい水平距離 boss.phy.v0x=2*ymax_x/t;//水平成分の初速度 boss.phy.ax =2*ymax_x/(t*t);//水平成分の加速度 boss.phy.prex=boss.x;//初期x座標 ymax_y=boss.y-BOSS_POS_Y;//移動したい鉛直距離 boss.phy.v0y=2*ymax_y/t;//鉛直成分の初速度 boss.phy.ay =2*ymax_y/(t*t);//鉛直成分の加速度 boss.phy.prey=boss.y;//初期y座標 } //物理的キャラクタ移動計算 void calc_phy(){ double t=boss.phy.cnt; boss.x=boss.phy.prex-((boss.phy.v0x*t)-0.5*boss.phy.ax*t*t);//現在いるべきx座標計算 boss.y=boss.phy.prey-((boss.phy.v0y*t)-0.5*boss.phy.ay*t*t);//現在いるべきy座標計算 boss.phy.cnt++; if(boss.phy.cnt>=boss.phy.set_t)//移動にかける時間分になったら boss.phy.flag=0;//オフ } //ボスの弾幕を計算する void boss_shot_calc(){ int i; boss.endtime--; if(boss.endtime<0) boss.hp=0; for(i=0;i<BOSS_BULLET_MAX;i++){ if(boss_shot.bullet[i].flag>0){ boss_shot.bullet[i].x+=cos(boss_shot.bullet[i].angle)*boss_shot.bullet[i].spd; boss_shot.bullet[i].y+=sin(boss_shot.bullet[i].angle)*boss_shot.bullet[i].spd; boss_shot.bullet[i].cnt++; if(boss_shot.bullet[i].cnt>boss_shot.bullet[i].till){ if(boss_shot.bullet[i].x<-50 || boss_shot.bullet[i].x>FIELD_MAX_X+50 || boss_shot.bullet[i].y<-50 || boss_shot.bullet[i].y>FIELD_MAX_Y+50) boss_shot.bullet[i].flag=0; } } } boss_shot.cnt++; } //弾幕をセット void enter_boss_shot(){ memset(&boss_shot , 0, sizeof(boss_shot_t));//弾情報初期化 boss_shot.flag=1; boss.wtime=0;//待機時間0 boss.state=2;//弾幕中状態に boss.hp=boss.set_hp[boss.knd];//HP設定 boss.hp_max=boss.hp; } //ボスをセット void enter_boss(int num){ if(num==0){//中ボス開始時の時は memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);//雑魚敵を消す memset(shot,0,sizeof(shot_t)*SHOT_MAX);//弾幕を消す boss.x=FIELD_MAX_X/2;//ボスの初期座標 boss.y=-30; boss.knd=-1;//弾幕の種類 } boss.flag=1; boss.hagoromo=0;//扇を広げるかどうかのフラグ boss.endtime=99*60;//残り時間 boss.state=1;//待機中状態に boss.cnt=0; boss.graph_flag=0;//描画フラグを戻す boss.knd++; boss.wtime=0;//待機時間を初期化 memset(&boss_shot,0,sizeof(boss_shot_t));//ボスの弾幕情報を初期化 input_phy(60);//60カウントかけて物理的計算で定位置に戻す } //ボスの待機処理 void waitandenter(){ int t=140; boss.wtime++; if(boss.wtime>t)//140カウント待機したら弾幕セット enter_boss_shot(); } //ボスの弾幕メイン void boss_shot_main(){ if(stage_count==boss.appear_count[0] && boss.flag==0)//開始時間なら enter_boss(0);//開始 if(boss.flag==0)//ボスが登録されて無ければ戻る return; if(boss.phy.flag==1)//物理演算移動オンなら calc_phy();//物理計算を if(boss.state==2 && (boss.hp<=0 || boss.endtime<=0)){//弾幕中で体力が無くなったら enter_boss(1);//次の弾幕を登録 } if(boss.state==1){//弾幕間の待機時間 waitandenter(); } if(boss.state==2){//弾幕中なら boss_shot_bullet[boss.knd]();//弾幕関数へ boss_shot_calc();//弾幕計算 } boss.cnt++; }
いや〜ボスの制御は面倒ですね。
まず、stage_countというゲームカウンタがboss.appear_countといういつボスが出現するかというカウンタ情報と一致したら
ボスを出現させます。ボスはbossという変数を用意しますから、ここでフラグをたてておきます。
boss.flag=1とします。
boss.stateが1の時、弾幕〜弾幕の休憩時間。敵が待機してる状態です。
boss.stateが2の時は、弾幕を発射しているさなかです。
弾幕関数へ渡す渡し方は以前ショットでやった関数ポインタです。
今回一番上で実装した
double bossatan2()
関数はこれを呼ぶだけで自機と敵との成す角度が返るので便利です。弾幕作製時には是非利用しましょう。
いつボスが出現するか、体力はいくつかという情報はとりあえずini関数で適当な値をいれておきます。
---- ini.cpp ini()を変更 ---- //ゲームの初期化 void ini(){ stage_count=1; memset(&ch,0,sizeof(ch_t)); memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX); memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX); memset(shot,0,sizeof(shot_t)*SHOT_MAX); memset(cshot,0,sizeof(cshot_t)*CSHOT_MAX); memset(effect,0,sizeof(effect_t)*EFFECT_MAX); memset(del_effect,0,sizeof(del_effect_t)*DEL_EFFECT_MAX); memset(&bom,0,sizeof(bom_t)); memset(&bright_set,0,sizeof(bright_set_t)); memset(&dn,0,sizeof(dn_t)); memset(&boss,0,sizeof(boss_t)); ch.x=FIELD_MAX_X/2; ch.y=FIELD_MAX_Y*3/4; ch.power=500; boss.appear_count[0]=100; for(int i=0;i<DANMAKU_MAX;i++) boss.set_hp[i]=5000; bright_set.brt=255; }
変数と構造体の用意もしておきます。
---- struct.h に追加 ---- //ボスショットに関する構造体 typedef struct{ //フラグ、種類、カウンタ、どの敵から発射されたかの番号、色 int flag,knd,cnt,num; //ベース角度、ベーススピード double base_angle[1],base_spd[1]; bullet_t bullet[BOSS_BULLET_MAX]; }boss_shot_t; //物理的計算に使う構造体 typedef struct{ int flag,cnt,set_t; double ax,v0x,ay,v0y,vx,vy,prex,prey; }phy_t; //ボスの情報 typedef struct{ int flag,cnt,knd,wtime,state,endtime,hagoromo,graph_flag; int hp,hp_max; int appear_count[2],set_hp[DANMAKU_MAX]; double x,y,ang,spd; phy_t phy; }boss_t; ---- define.h に追加 ---- //ボスの定位置 #define BOSS_POS_X (FIELD_MAX_X/2) #define BOSS_POS_Y 100.0 //ボスが持つ弾の最大数 #define BOSS_BULLET_MAX 3000 //弾幕最大数 #define DANMAKU_MAX 50 ---- GV.h に追加 ---- GLOBAL int img_dot_riria[8];//リリアのドット絵画像 GLOBAL boss_shot_t boss_shot;//ボスショット情報 GLOBAL boss_t boss; //ボス情報
---- main.cpp のメイン関数の以下の部分を変更 ---- case 100://通常処理 calc_ch(); //キャラクタ計算 ch_move(); //キャラクタの移動制御 cshot_main();//自機ショットメイン enemy_main();//敵処理メイン shot_main(); //ショットメイン boss_shot_main(); out_main(); //当たり計算 effect_main();//エフェクトメイン graph_main();//描画メイン if(boss.flag==0) stage_count++;
---- load.cpp load()関数に追加 ---- LoadDivGraph( "../dat/img/char/riria.png" , 8 , 8 , 1 , 100 , 100 , img_dot_riria ) ;
---- enemy.cpp enemy_enter()関数内の以下を追加 ---- void enemy_enter(){//敵の行動を登録・制御する関数 int i,j,t; if(boss.flag!=0)return; for(t=0;t<ENEMY_ORDER_MAX;t++){
---- graph.cpp の以下の関数を変更・追加 ---- void graph_boss(){ if(boss.flag==0)return; DrawRotaGraphF(boss.x+FIELD_X+dn.x,boss.y+FIELD_Y+dn.y,1.0f,0.0f,img_dot_riria[0],TRUE); } //弾丸の描画 void graph_bullet(){ int i,j; SetDrawMode( DX_DRAWMODE_BILINEAR ) ;//線形補完描画 for(i=0;i<SHOT_MAX;i++){//敵の弾幕数分ループ if(shot[i].flag>0){//弾幕データがオンなら for(j=0;j<SHOT_BULLET_MAX;j++){//その弾幕が持つ弾の最大数分ループ if(shot[i].bullet[j].flag!=0){//弾データがオンなら if(shot[i].bullet[j].eff==1) SetDrawBlendMode( DX_BLENDMODE_ADD, 255) ; DrawRotaGraphF( shot[i].bullet[j].x+FIELD_X+dn.x, shot[i].bullet[j].y+FIELD_Y+dn.y, 1.0, shot[i].bullet[j].angle+PI/2, img_bullet[shot[i].bullet[j].knd][shot[i].bullet[j].col],TRUE); if(shot[i].bullet[j].eff==1) SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0) ; } } } } //ボス if(boss_shot.flag>0){//弾幕データがオンなら for(j=0;j<BOSS_BULLET_MAX;j++){//その弾幕が持つ弾の最大数分ループ if(boss_shot.bullet[j].flag!=0){//弾データがオンなら if(boss_shot.bullet[j].eff==1) SetDrawBlendMode( DX_BLENDMODE_ADD, 255) ; DrawRotaGraphF( boss_shot.bullet[j].x+FIELD_X+dn.x, boss_shot.bullet[j].y+FIELD_Y+dn.y, 1.0, boss_shot.bullet[j].angle+PI/2, img_bullet[boss_shot.bullet[j].knd][boss_shot.bullet[j].col],TRUE); if(boss_shot.bullet[j].eff==1) SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0) ; } } } SetDrawMode(DX_DRAWMODE_NEAREST);//描画形式を戻す } void graph_develop(){ DrawFormatString(0,0,GetColor(255,255,255),"%d",stage_count); } void graph_main(){ if(bright_set.brt!=255)SetDrawBright(bright_set.brt,bright_set.brt,bright_set.brt); graph_back_main();//背景描画メイン graph_effect(0);//敵が死ぬエフェクト if(bright_set.brt!=255)SetDrawBright(255,255,255); graph_effect(4);//喰らいボムのエフェクト if(bright_set.brt!=255)SetDrawBright(bright_set.brt,bright_set.brt,bright_set.brt); graph_boss(); graph_enemy();//敵の描画 graph_cshot();//自機ショットの描画 if(bright_set.brt!=255)SetDrawBright(255,255,255); graph_ch();//自機の描画 if(bright_set.brt!=255)SetDrawBright(bright_set.brt,bright_set.brt,bright_set.brt); graph_bullet();//弾の描画 if(bright_set.brt!=255)SetDrawBright(255,255,255); graph_effect(1);//ボムのエフェクト graph_effect(2);//ボム線のエフェクト graph_effect(3);//ボムキャラのエフェクト graph_board();//ボードの描画 graph_develop(); }
---- function.h に追加 ---- //boss_shot.cpp GLOBAL void boss_shot_main();
弾幕の関数は雑魚ショットの時と同じように関数ポインタで格納します。
今回、includeフォルダにfunc.hというファイルを追加して下さい。
---- func.cpp を変更 ---- extern void boss_shot_bulletH000(); void (*boss_shot_bullet[DANMAKU_MAX])() = { boss_shot_bulletH000, };
試しに一つ弾幕を用意してみます。
---- boss_shotH.cpp を変更 ---- #include "../include/GV.h" extern int serch_boss_shot(); extern double bossatan2(); void boss_shot_bulletH000(){ #define TM000 120 int i,k,t=boss_shot.cnt%TM000; double angle; if(t<60 && t%10==0){ angle=bossatan2(); for(i= 0;i<30;i++){if((k=serch_boss_shot())!=-1){ boss_shot.bullet[k].col = 0; boss_shot.bullet[k].x = boss.x; boss_shot.bullet[k].y = boss.y; boss_shot.bullet[k].knd = 8; boss_shot.bullet[k].angle = angle+PI2/30*i; boss_shot.bullet[k].flag = 1; boss_shot.bullet[k].cnt = 0; boss_shot.bullet[k].spd = 3; se_flag[0]=1; } } } for(i=0;i<BOSS_BULLET_MAX;i++){ if(boss_shot.bullet[i].flag>0){ } } }
- Remical Soft -