第十七章 让自机射出子弹吧

>>点此回到教程目录

慢慢地,游戏开始有点游戏的样子了呢。

现在我们加上自机的射击和碰撞判定,这样一来大体上的构架就完成了。

自机的射击的做法大体上而言和敌人的射击没有多大的区别。

我们先定义保存子弹情报的容器,这个容器里面只需要保存种类、速度、角度等信息就可以了。

只要我们事先设定好了数字,那么计算部分就能够方便地进行轨道和碰撞判定的计算了。

那么,我们和平常一样准备这个容器。

—-在 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这么多像素的话,由于已经知道上一帧也在画面外面,因此这个时候就有必要销毁这个子弹了。

上面那段代码就是为了实现上面的算法进行的处理。


运行结果:


>>点此回到教程目录

results matching ""

    No results matching ""