第十七章 让自机射出子弹吧
慢慢地,游戏开始有点游戏的样子了呢。
现在我们加上自机的射击和碰撞判定,这样一来大体上的构架就完成了。
自机的射击的做法大体上而言和敌人的射击没有多大的区别。
我们先定义保存子弹情报的容器,这个容器里面只需要保存种类、速度、角度等信息就可以了。
只要我们事先设定好了数字,那么计算部分就能够方便地进行轨道和碰撞判定的计算了。
那么,我们和平常一样准备这个容器。
—-在 struct.h 追加以下红字部分 —-
/***修改请注意***/
//和角色射击相关的结构体
typedef struct{
int flag,power,cnt,knd;//flag、power、计数器、种类
double x,y,angle,spd;//坐标、角度、速度
}cshot_t;
/***修改请注意***/
//和角色相关的结构体
typedef struct{
int flag; //flag
int cnt; //计数器
int power; //power
int point; //point
int score; //score
int num; //残机数
int mutekicnt; //无敌状态及其计数器
int shot_mode; //射击模式
int money; //金钱
int img;
int slow; //是否缓慢移动
double x,y; //坐标
/***修改请注意***/
int shot_cnt; //射击的计数器
/***修改请注意***/
}ch_t;
—-在 define.h 中进行以下追加 —-
//自机射击的子弹的登录最大数
#define CSHOT_MAX 200
—-在 GV.h 中进行以下追加 —-
GLOBAL int img_cshot[2]; //自机射击用的图像
GLOBAL cshot_t cshot[CSHOT_MAX];//自机射击用的变量
—-在 load.cpp 的 load()函数中进行以下追加 —-
img_cshot[0]=LoadGraph("../dat/img/char/bl_00.png");
img_cshot[1]=LoadGraph("../dat/img/char/bl_01.png");
sound_se[2]=LoadSoundMem("../dat/se/cshot.wav");
—-在 ini.cpp 的 ini函数中进行以下追加 —-
memset(cshot,0,sizeof(cshot_t)*CSHOT_MAX);
ch.power=500;
—-在 function.h 中进行以下追加 —-
GLOBAL void cshot_main();
—-在 graph.cpp 中进行以下追加 —-
void graph_cshot(){
for(int i=0;i<CSHOT_MAX;i++){
if(cshot[i].flag>0){
DrawRotaGraphF(cshot[i].x+FIELD_X,cshot[i].y+FIELD_Y,1,0,
img_cshot[cshot[i].knd],TRUE);
}
}
}
—-修改 graph.cpp 的 graph_main函数 —-
void graph_main(){
graph_enemy();
graph_cshot();
graph_ch();
graph_bullet();
graph_board();
}
—- main.cpp 的main函数内的switch语句部分进行以下变更 —-
case 100://通常处理
calc_ch(); //角色计算
ch_move(); //控制角色移动
/***修改请注意***/
cshot_main();//自机射击main
/***修改请注意***/
enemy_main();//敌人处理main
shot_main();//射击main
graph_main();//绘制main
stage_count++;
break;
最后是重要的自机射击登录部分。
我们试着让自机的power在200以下的话一次发射2发子弹,200以上的话一次发射4发。
这4发的位置如下所示。
int cshot0pos_x[4]={-10, 10,-30, 30};
int cshot0pos_y[4]={-30,-30,-10,-10};
第1发的位置是(-10,-30),第2发的位置是(10,-30),第3发的位置是(-30,-10)……大概就这样。
这次也和前面的章节中一样,由于先定义了登录数据库,于是在登录射击的时候通过
int search_cshot()
搜索空着的编号,然后登录之。
—- cshot.cpp 变更 —-
#include "../include/GV.h"
int cshot0num[2] ={2,4};
int cshot0pos_x[4]={-10, 10,-30, 30};
int cshot0pos_y[4]={-30,-30,-10,-10};
//返回自机射击登录可能的编号。
int search_cshot(){
for(int i=0;i<CSHOT_MAX;i++){
if(cshot[i].flag==0)
return i;
}
return -1;
}
//一般射击的登录
void ch0_shot_pattern(){
int k;
for(int i=0;i<cshot0num[ch.power<200?0:1];i++){
if((k=search_cshot())!=-1){
cshot[k].flag=1;
cshot[k].cnt=0;
cshot[k].angle=-PI/2;
cshot[k].spd=20;
cshot[k].x=ch.x+cshot0pos_x[i];
cshot[k].y=ch.y+cshot0pos_y[i];
cshot[k].power=23;
cshot[k].knd=0;
}
}
se_flag[2]=1;//播放发射音
}
//低速下一般射击的登录
void ch1_shot_pattern(){
int k;
for(int i=0;i<cshot0num[ch.power<200?0:1];i++){
if((k=search_cshot())!=-1){
cshot[k].flag=1;
cshot[k].cnt=0;
cshot[k].angle=-PI/2;
cshot[k].spd=20;
cshot[k].x=ch.x+cshot0pos_x[i]/3;//如果处于低速状态的话,将位置向中心偏移
cshot[k].y=ch.y+cshot0pos_y[i]/2;
cshot[k].power=23;
cshot[k].knd=0;
}
}
se_flag[2]=1;
}
//射击登录部分
void enter_shot(){
//当按下射击按钮的时候
if(CheckStatePad(configpad.shot)>0){
ch.shot_cnt++;
if(ch.shot_cnt%3==0){//每3次计数射击一次
if(CheckStatePad(configpad.slow)>0)//如果处于低速移动中的话
ch1_shot_pattern();
else
ch0_shot_pattern();
}
}
else
ch.shot_cnt=0;
}
//射击的移动计算
void calc_cshot(){
for(int i=0;i<CSHOT_MAX;i++){
if(cshot[i].flag==1){
int dranx=cshot[i].spd+11/2,drany=cshot[i].spd+55/2;
cshot[i].x+=cos(cshot[i].angle)*cshot[i].spd;
cshot[i].y+=sin(cshot[i].angle)*cshot[i].spd;
cshot[i].cnt++;
if(cshot[i].x<-dranx || cshot[i].x>FIELD_MAX_X+dranx ||
cshot[i].y<-drany || cshot[i].y>FIELD_MAX_Y+drany)//如果跑到画面外了的话
cshot[i].flag=0;
}
}
}
//和角色射击相关的函数
void cshot_main(){
calc_cshot();//射击轨道的计算
enter_shot();//射击登录
}
在calc_cshot函数中判定射击的子弹从画面内进入画面外的情况时,为何要使用
int dranx=cshot[i].spd+11/2,drany=cshot[i].spd+55/2;
来判定,这里我来补充说明一下。由于下一章将要介绍碰撞判定,因此有必要事先理解碰撞判定,但是由于下一章中这段代码不会出现了,因此我事先说明一下。
如果我们仅仅单纯地使用勾股定理来计算是否接触,是无法进行碰撞判定的。
比如射击的速度是200而子弹的碰撞判定是10,那么就会有190空出来的无用空间。(译者注:本文章的作者Dixq在他的网站的游戏编程馆s11章介绍了用勾股定理进行碰撞判定的方法,这里的描述即是那篇文章中用到的说法。)
也即是说没有必要计算子弹的轨迹去判定是否接触。
由于碰撞判定的计算是和轨迹相关的,如果子弹一旦跑到画面外就让它消失的话就无法计算最后的轨迹了。
也即是说,在之前的帧中已经在画面外且当前帧也是在画面外的情况下,才有必要销毁之。
其次,11,55指的是射击的子弹的图片尺寸。
如果已经超出画面速度+图片尺寸/2这么多像素的话,由于已经知道上一帧也在画面外面,因此这个时候就有必要销毁这个子弹了。
上面那段代码就是为了实现上面的算法进行的处理。
运行结果: