第十一章 使用Excel来设置敌人出现的数据吧
在道中会出来很多敌人。
同时敌人拥有的信息有很多。至少也得有下面这些吧:
–在struct.h进行以下追加–
typedef struct{
//计数器、移动模式、敌人的种类
int cnt,pattern,knd;
//初始化坐标和移动速度
double x,y,sp;
//弹幕开始时间、弹幕种类、颜色、体力、子弹的种类、停止时间、物品(6个种类)
int bltime,blknd,col,hp,blknd2,wait,item_n[6];
}enemy_order_t;
这是和敌人出现信息有关的结构体。
何时、何处、什么样子的敌人、射过来什么样子的子弹、要停止多久、是否回去……之类的信息。
我们必须要指定许多东西。但是,在计数器达到112时x坐标在这里、y坐标在那里、移动模式是这样、计数器达到187的时候又要这样又要那样……
要是将这些数据全部手动输入然后代入到数据中的话会很麻烦的。
因此我们试试使用Excel来制作敌人的出现的数据吧。
首先,从这里下载已经用Excel写好了的出现信息数据。
在dat文件夹里面增加一个“csv”的文件夹,然后把Excel文件放进去。内部应该是这样子的。
如果没有Excel的话,文本处理器也能够打开,如下:
首先最左边的是敌人出现的时刻。因为这是60fps的游戏,60=1秒。
也就是说,这表示第一个敌人在游戏开始之后1.6秒左右出现。
然后置10个计数,也就是说每隔1/6秒就出现一个敌人。
敌人的信息除了x坐标以外其它都是一样的。使用Excel能够简单地作成有同样变化的值,非常方便。
接下来,使用DX Library将文件读入的时候,使用Dx Library的函数(为了使用archive)。
如果不知道DX Library的文件读入函数的使用方法,请到其参考主页上学习。
使用起来基本上和C语言的标准函数fopen(←只有一点区别)、fgetc之类的函数的方法相同。
其次,我认为就算是csv文件,即使没有使用过的经验,将这个文件用文本编辑器打开应该很快就能够明白了。由于这只是简单地将单元用逗号隔开用于隔开的数据,因此只要用逗号隔开然后仔细阅读的话,一般而言就能够掌握和阅读一般文本数据一样的要领了。那么,现在我们来写这个读入函数。
下面我们把从Excel数据读入用于存储敌人出现信息的变量enemy_order中,虽然用了一个代入函数,但是这没有有必要理解它,只要知道这个函数是用来把数据带入这个变量的就行了。
–在load.cpp中进行以下追加 –
//将敌人出现的信息从Excel读入并保存的函数
void load_story(){
int n,num,i,fp;
char fname[32]={"../dat/csv/storyH0.csv"};
int input[64];
char inputc[64];
fp = FileRead_open(fname);//读入文件
if(fp == NULL){
printfDx("read error\n");
return;
}
for(i=0;i<2;i++)//丢掉最开始的两行
while(FileRead_getc(fp)!='\n');
n=0 , num=0;
while(1){
for(i=0;i<64;i++){
inputc[i]=input[i]=FileRead_getc(fp);//读入1个字符
if(inputc[i]=='/'){//如果是斜杠
while(FileRead_getc(fp)!='\n');//循环一直到换行
i=-1;//重置计数器
continue;
}
if(input[i]==',' || input[i]=='\n'){//如果是逗号或者换行
inputc[i]='\0';//将到此的所有文字作为字符串
break;
}
if(input[i]==EOF){//如果到了文件尾
goto EXFILE;//终止
}
}
switch(num){
case 0: enemy_order[n].cnt =atoi(inputc);break;
case 1: enemy_order[n].pattern =atoi(inputc);break;
case 2: enemy_order[n].knd =atoi(inputc);break;
case 3: enemy_order[n].x =atof(inputc);break;
case 4: enemy_order[n].y =atof(inputc);break;
case 5: enemy_order[n].sp =atof(inputc);break;
case 6: enemy_order[n].bltime =atoi(inputc);break;
case 7: enemy_order[n].blknd =atoi(inputc);break;
case 8: enemy_order[n].col =atoi(inputc);break;
case 9: enemy_order[n].hp =atoi(inputc);break;
case 10:enemy_order[n].blknd2 =atoi(inputc);break;
case 11:enemy_order[n].wait =atoi(inputc);break;
case 12:enemy_order[n].item_n[0]=atoi(inputc);break;
case 13:enemy_order[n].item_n[1]=atoi(inputc);break;
case 14:enemy_order[n].item_n[2]=atoi(inputc);break;
case 15:enemy_order[n].item_n[3]=atoi(inputc);break;
case 16:enemy_order[n].item_n[4]=atoi(inputc);break;
case 17:enemy_order[n].item_n[5]=atoi(inputc);break;
}
num++;
if(num==18){
num=0;
n++;
}
}
EXFILE:
FileRead_close(fp);
}
想完全理解的话太阳都要落山了,那么我们就先把它放到这里继续往下走吧。
在进入具体的处理之前,我先说明几个细微的增加点。
在ini.cpp中,在初始化函数中初始化现在我们增加了的数据。 在define.h中,设定了允许登录的最大数量。 在Gv.h中我们定义了使用到的变量。 –在ini.cpp的ini函数中进行以下追加-
memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);
–在define.h中进行以下追加–
//敌人出现信息的最大数量
#define ENEMY_ORDER_MAX 500
–在GV.h中进行以下追加–
GLOBAL enemy_order_t enemy_order[ENEMY_ORDER_MAX];//敌人的出现信息
–在function.h中进行以下追加–
GLOBAL void load_story();
–main.cpp中的main函数的switch语句中的以下部分变更–
case 99://STG开始前的初始化
ini();
/*** 修改注意 ***/
load_story();
/*** 修改注意 ***/
func_state=100;
break;
这样一来,我们就把这次敌人出现信息相关的数据放到enemy_order里面了。
然后我们把保存了的数据在下面进行登录,而这实际上就是在游戏内登录敌人。
使用enemy_enter进行敌人数据的登录。
因为enemy_order[n].cnt中已经存入了敌人应该合适出现的数据,那么便在与当前的游戏计数器一致的时候,进行敌人数据的登录。
进行敌人数据登录的时候,为了找到哪个编号是空的,我们实现一个叫做enemy_num_search的函数。
放入敌人数据的enemy数组下标是0~ENEMY_MAX-1,这个函数将告诉我们哪个编号可以使用。如果全部都不是空的话那么返回-1,这个时候就不再进行登录。
另外,前文已经说过enemy[n].wait表示停止时间,像下面的红字那样使用。
这样一来就可以控制敌人的停止时间了。
再者,之所以敌人移动的计算,既有利用ang和sp组合的计算,也有利用vx、vy组合的计算,这是因为根据情况的不同,用途会不同。
当然,这次在enemy_parttern中使用到的vy=2也可以和ang=3.14/2、sp=2进行置换,前者更容易理解就是了。
–enemy.cpp变更–
#include "../include/GV.h"
//使用敌人的移动模式0进行移动控制
void enemy_pattern0(int i){
int t=enemy[i].cnt;
if(t==0)
enemy[i].vy=2;//下降
if(t==60)
enemy[i].vy=0;//静止
/*** 修改注意 ***/
if(t==60+enemy[i].wait)//在登录的时间区间内停止
/*** 修改注意 ***/
enemy[i].vy=-2;//上升
}
//检测空闲的敌人编号
int enemy_num_search(){
for(int i=0;i<ENEMY_MAX;i++){//搜寻没有立上flag的enemy
if(enemy[i].flag==0){
return i;//返回可以使用的编号
}
}
return -1;//如果全部都使用了返回错误
}
//登录敌人的信息
void enemy_enter(){//登录、控制敌人行动的函数
int i,j,t;
for(t=0;t<ENEMY_ORDER_MAX;t++){
if(enemy_order[t].cnt==stage_count){//如果当前计数是是顺序的计数的瞬间
if((i=enemy_num_search())!=-1){
enemy[i].flag =1;//flag
enemy[i].cnt =0;//计数器
enemy[i].pattern=enemy_order[t].pattern;//移动模式
enemy[i].muki =1;//方向
enemy[i].knd =enemy_order[t].knd;//敌人的种类
enemy[i].x =enemy_order[t].x;//坐标
enemy[i].y =enemy_order[t].y;
enemy[i].sp =enemy_order[t].sp;//速度
enemy[i].bltime =enemy_order[t].bltime;//子弹的发射时间
enemy[i].blknd =enemy_order[t].blknd;//弹幕的种类
enemy[i].blknd2 =enemy_order[t].blknd2;//子弹的种类
enemy[i].col =enemy_order[t].col;//颜色
enemy[i].wait =enemy_order[t].wait;//停止时间
enemy[i].hp =enemy_order[t].hp;//体力
enemy[i].hp_max =enemy[i].hp;//体力最大值
enemy[i].vx =0;//水平分量的速度
enemy[i].vy =0;//竖直分量的速度
enemy[i].ang =0;//角度
for(j=0;j<6;j++)
enemy[i].item_n[j]=enemy_order[t].item_n[j];//掉落的道具
}
}
}
}
//控制敌人的行动
void enemy_act(){
int i;
for(i=0;i<ENEMY_MAX;i++){
if(enemy[i].flag==1){//如果敌人的flag为有效
enemy_pattern0(i);
enemy[i].x+=cos(enemy[i].ang)*enemy[i].sp;
enemy[i].y+=sin(enemy[i].ang)*enemy[i].sp;
enemy[i].x+=enemy[i].vx;
enemy[i].y+=enemy[i].vy;
enemy[i].cnt++;
enemy[i].img=enemy[i].muki*3+(enemy[i].cnt%18)/6;
//如果敌人跑到画面外面了就销毁
if(enemy[i].x<-20 || FIELD_MAX_X+20<enemy[i].x || enemy[i].y<-20 || FIELD_MAX_Y+20<enemy[i].y)
enemy[i].flag=0;
}
}
}
//敌人处理main
void enemy_main(){
enemy_enter();
enemy_act();
}
运行结果