原文地址:
http://dixq.net/rp/24.html
那么接下来、终于要进入弹幕的制作了。
首先,要制作BOSS的弹幕的话,就需要有个BOSS。
BOSS的身体的制作的,要注意很多地方,比较麻烦,大家努力学习下吧。
在这里,稍微说点物理相关的话题吧。
龙神录里,BOSS看起来都是“咻”,“咻”的运动的。
虽然想按照“从这里到那里,移动这么长的时间”这样的指令来执行某个动作,但以速度匀速移动过去,到制定地点突然就停止了,这样看起来感觉不大好。
如果没有加速,减速,再停下来这样的过程的话,运动看起来就不漂亮。
于是,以“在这么长的距离,这么长的时间内,漂亮的移动过去”作为我们动作指令的目标,我们来考虑下怎么用算式来实现这个吧。
对数学学的不好的人可能会讨厌看到计算公式,我们就用很简单的计算来表示下看看吧。
物理的力学的匀加速运动中,有三个大公式。
就是下面3条公式。v是速度,a是加速度,y是距离,t是时间。
现在,我们考虑某个时间发射出去,在指定距离y里,用了一定的时间ty,做减速运动正好停止的这样一个问题。
v=v0−at...①
y=v0t−12at2...②
v2−v20=−2ay...③
以
v0
为初速度飞出去的物体,在时间
ty
内前进了
ymax
的距离后停下来时,最后的速度是0。所以把
v=0
代入①, 得到
v0=aty
a=v0ty
… ⑤
把
v=0
再代入③,得到
v20=2aymax
把⑤代入上面这个式子
v20=2v0tyymax
…④
两边都约去
v0
v0=2tyymax
… ⑥
把⑥代入⑤,得到
a=2t2yymax
…⑦
把⑥,⑦都代入②
y=2ymaxtyt−ymaxt2yt2
…A
这样,我们就推导出了式子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=移动消耗的时间
doubley max_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];//设置血量
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;//是否扇形散开的标志
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));//初始化boss的弹幕情况
input_phy(60);//经过60个计数后,物理计算运动的位置
}
//boss的待机处理
void waitandenter(){
int t=140;
boss.wtime++;
if(boss.wtime>t)//待机了140个计数后,设置弹幕
enter_boss_shot();
}
//boss的弹幕主方法
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)){//弹幕过程中,体力没了的话
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什么时候出现的boss.appear_count
一致的话,就让boss出现。
我们就用boss这个变量来表示boss,这里先给他设置一个标志。
设置 boss.flag=1
。
boss的状态等于1时,是弹幕和弹幕之间的休息时间。敌机处于待机状态。
boss的状态等于2时,正处于弹幕发射状态中。
传递给弹幕函数的方法,是之前射击里用的函数指针。
这里从最上面开始看具体实现。
这里调用 double bossatan2()
函数,可以很方便的返回自机和敌机所形成的角度。弹幕制作时一定要用到这个函数哦。
boss什么时候出现,体力为多少,等这些信息,都通过 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 に追加 ----
//boss弹幕相关的构造体
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;
//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();//自机射击主函数
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) ;
}
}
}
}
//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();//背景绘制主函数
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文件夹里的fun.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){
}
}
}
虽然中弹检测都还没有,我们先让弹幕运行起来吧。
运行结果
本人CSDN博客目录:
http://blog.csdn.net/tidus5
本人github博客:
http://tidus5.github.io