第二十四章 来制作Boss吧
接下来,我们终于快要进入弹幕制作了。
不过要制作Boss的弹幕的话,还得先制作Boss才行。
要制作Boss的框架非常麻烦,大家一起努力吧。
不过在此之前,我们得先讨论一些物理的东西。
在龙神录中,Boss倏倏地在到处移动。
虽然希望Boss能够遵从“在这个时间内从这里开始到这里移动”这样规定的指示,但是只是在匀速运动中移动到达指定地点之后突然停止的运动看起来会很没意思。
我们想看到的是Boss先加速、再减速然后华丽地停下来,这样子漂亮的动作。
因此我们来研究一下能够处理“在这个时间内仅在这个距离内漂亮地移动”这样的指示的计算式吧。
如果您的数学不太好的话,下面的计算式可能会让您感觉厌烦,不过这是非常简单的计算,所以还是请看看吧。
在物理的力学中,匀加速运动有三大方程。
也即是在下面的方程的1~3,其中v是速度,a是加速度、y是距离、t是时间。
现在我们考虑这样一个过程:咚地发射出去,然后在指定的时间ty内在指定的距离距离y内减速,然后停下来。
这样一来,我们就把式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 search_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;//オフ 设置移动为无效
}
//计算Boss的弹幕
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;
}
//设置Boss
void enter_boss(int num){
if(num==0){//中路Boss开始时的情况
memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);//销毁杂兵
memset(shot,0,sizeof(shot_t)*SHOT_MAX);//销毁弹幕
boss.x=FIELD_MAX_X/2;//设置Boss的初始坐标
boss.y=-30;
boss.knd=-1;//弹幕的种类
}
boss.flag=1;
boss.hagoromo=0;//是否扇状扩散的flag
boss.endtime=99*60;//剩余时间
boss.state=1;//设置状态为待机中
boss.cnt=0;
boss.graph_flag=0;//恢复绘制flag
boss.knd++;
boss.wtime=0;//初始化待机时间
memset(&boss_shot,0,sizeof(boss_shot_t));//初始化Boss的弹幕信息
input_phy(60);//附加上60次计数在物理计算中回到固定位置
}
//Boss的待机处理
void waitandenter(){
int t=140;
boss.wtime++;
if(boss.wtime>t) //如果待机140次计数的话设置弹幕
enter_boss_shot();
}
//Boss的弹幕main
void boss_shot_main(){
if(stage_count==boss.appear_count[0] && boss.flag==0)//如果到了开始时间的话
enter_boss(0);//开始
if(boss.flag==0)//如果Boss还没有登录那么什么都不做
return;
if(boss.phy.flag==1)//如果物理移动计算有效
calc_phy();//进行物理计算
if(boss.state==2 && (boss.hp<=0 || boss.endtime<=0)){//如果在弹幕中体力为0的话
enter_boss(1);//进入下一次弹幕
}
if(boss.state==1){//弹幕之间的待机时间
waitandenter();
}
if(boss.state==2){//如果在弹幕中
boss_shot_bullet[boss.knd]();//进入弹幕函数
boss_shot_calc();//计算弹幕
}
boss.cnt++;
}
呀~Boss的控制真是麻烦啊。
首先,如果stage_count这个游戏计数器的值和boss.appear_count这个决定Boss是否出现的计数器的值相同的话,那么就让Boss出现。由于为Boss定义了boss这个变量,因此我们在这里将flag设置为有效。
我们让boss.flag=1。
boss.state在1的时候,这是弹幕之间的冷却时间。敌人处于待机状态。 boss.state在2的时候,正处于弹幕发射的时候。 向弹幕函数传入的方法就是之前在射击处理中用到的函数指针。
这次我们在最上面实现的
double bossatan2()
函数的作用是,调用整个函数就可以返回自机和敌人之间的角度,非常方便。在制作弹幕的时候一定要利用这个。
首先我们在ini函数中为这些项目传入合适的值:合适Boss出现、体力为多少等信息。
—- 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 中追加 —-
//和Boss射击相关的结构体
typedef struct{
//flag、种类、计数器、发射过来的敌人的编号、颜色
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;
//Boss的信息
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 中追加 —-
//Boss的固定位置
#define BOSS_POS_X (FIELD_MAX_X/2)
#define BOSS_POS_Y 100.0
//Boss所有的子弹的最大数量
#define BOSS_BULLET_MAX 3000
//弹幕的最大数
#define DANMAKU_MAX 50
—-在 GV.h 中追加 —-
GLOBAL int img_dot_riria[8];//莉莉安的像素画(译者注:莉莉安是游戏里面的角色,大概吧,嗯。)
GLOBAL boss_shot_t boss_shot;// Boss射击的信息
GLOBAL boss_t boss; //Boss的信息
—-在 main.cpp 的main函数中进行以下部分的变更—-
case 100://通常处理
calc_ch(); //角色计算
ch_move(); //控制角色的移动
cshot_main();//自机射击main
enemy_main();//敌人处理main
shot_main(); //射击main
boss_shot_main();
out_main(); //碰撞计算
effect_main();//特效main
graph_main();//绘制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) ;
}
}
}
}
//Boss
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();//背景绘制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);//Boom的特效
graph_effect(2);// Boom线的特效
graph_effect(3);// Boom角色的特效
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 search_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=search_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){
}
}
}
虽然并没有加上碰撞判定,不过还是先看看弹幕吧。
运行结果