>>点此回到教程目录
这一章是51章的继续。在这一章中我们把在上一章中完成的原型(Proto Type)版给完成。
第51章的程序只是暂时地保留子弹的位置并绘制之。
现在我们要实现:
Ctrl + Z能够撤销操作(和windows标准的Ctrl+Z的功能一样)Ctrl + Y 能够将被撤销的操作还原(和windows标准的Ctrl+Y的功能一样)按着S键然后按左右方向键可以更改Space ※1使用左右方向键来变更子弹的角度C(color)键选择子弹的颜色K(kind)键选择 子弹的种类空格键可以切换显示 ※1: 按着鼠标拖动就会拉出一条红色的线,单击鼠标的话就会自动地按一定的间隔在一条直线上放置若干个子弹。也就是调整这个间隔的的功能。
Ctrl + Z能够撤销操作(和windows标准的Ctrl+Z的功能一样)Ctrl + Y 能够将被撤销的操作还原(和windows标准的Ctrl+Y的功能一样)按着S键然后按左右方向键可以更改Space ※1使用左右方向键来变更子弹的角度C(color)键选择子弹的颜色K(kind)键选择 子弹的种类空格键可以切换显示
※1: 按着鼠标拖动就会拉出一条红色的线,单击鼠标的话就会自动地按一定的间隔在一条直线上放置若干个子弹。也就是调整这个间隔的的功能。
我们追加上这7个功能,以及把制作好了的数据写入文件里面的功能吧。
现在我们首先看一下完成版是怎么操作的。
感性上已经有了个大概的体会了吧。
那么,现在来看看具体的程序代码吧。
由于这一次我们使用子弹的图像,因此读入子弹的图像。这个和之前没有区别。
—在 load.cpp 中进行以下追加 —
LoadDivGraph( "../dat/img/bullet/b0.png" , 5 , 5 , 1 , 76 , 76 , ImgBullet[0] ) ; LoadDivGraph( "../dat/img/bullet/b1.png" , 6 , 6 , 1 , 22 , 22 , ImgBullet[1] ) ; LoadDivGraph( "../dat/img/bullet/b2.png" , 10 , 10 , 1 , 5 , 120 , ImgBullet[2] ) ; LoadDivGraph( "../dat/img/bullet/b3.png" , 5 , 5 , 1 , 19 , 34 , ImgBullet[3] ) ; LoadDivGraph( "../dat/img/bullet/b4.png" , 10 , 10 , 1 , 38 , 38 , ImgBullet[4] ) ; LoadDivGraph( "../dat/img/bullet/b5.png" , 3 , 3 , 1 , 14 , 16 , ImgBullet[5] ) ; LoadDivGraph( "../dat/img/bullet/b6.png" , 3 , 3 , 1 , 14 , 18 , ImgBullet[6] ) ; LoadDivGraph( "../dat/img/bullet/b7.png" , 10 , 10 , 1 , 16 , 16 , ImgBullet[7] ) ; LoadDivGraph( "../dat/img/bullet/b8.png" , 10 , 10 , 1 , 12 , 18 , ImgBullet[8] ) ; LoadDivGraph( "../dat/img/bullet/b9.png" , 3 , 3 , 1 , 13 , 19 , ImgBullet[9] ) ; LoadDivGraph( "../dat/img/bullet/b10.png", 8 , 8 , 1 , 8 , 8 , ImgBullet[10]) ; LoadDivGraph( "../dat/img/bullet/b11.png", 8 , 8 , 1 , 35 , 32 , ImgBullet[11]) ; LoadDivGraph( "../dat/img/bullet/b12.png", 10 , 10 , 1 , 12 , 12 , ImgBullet[12]) ; LoadDivGraph( "../dat/img/bullet/b13.png", 10 , 10 , 1 , 22 , 22 , ImgBullet[13]) ;
这次加入了实际变更的函数除了上面的函数以外,仅仅只有CalcOperate()、Show()这两个函数了。
其它的函数从上一章开始就没有变更了,因此只看这些函数。
根据键盘输入来变更操作。由于和变更的操作相关的变量是下面这个结构体,所以请事先确认。
//操作设定信息 typedef struct{ int State;//状态 int Knd;//种类 int Col;//颜色 int Space;//空格 float Angle;//角度 int flag;//是否显示flag fPt_t fPt1;//第1次单击的位置 fPt_t fPt2;//第2次单击的位置 }Operate_t;
那么,我们试着来实现将这个变量按照
的顺序变更的功能吧。
现在我们来看看要实现的CalcOpertate函数的内部吧。
void CalcOperate(){ //各个子弹的颜色数 int Col[14]={5,6,10,5,10,3,3,10,10,3,8,8,10,10}; //左控制键(左Ctrl键)被按下的时候
现在来实现功能1。
BLPoint.Num是当前显示的子弹的数量。假设放置了5个子弹的话这个变量的值就为5。
因此如果我们把这个变量改为4的话,那么表面上显示出来的也就是到第4个为止的已经登录子弹了。另外,超过0.5秒还在按下的话,我们还要让它能够一口气地连续操作。(译者注:即连续后退)
if( Key[KEY_INPUT_LCONTROL]>0 ){ //Z键按下的话 if( Key[KEY_INPUT_Z]==1 || Key[KEY_INPUT_Z]>30){ //操作退后一步 if( BlPoint.Num>0 ){ BlPoint.Num--; } }
现在实现功能2。
这个功能是把之前用Z键撤销的操作再反过来回复的功能。
我们通过减少Bl.Point.Num实现了把已经进行了的动作给撤销掉。
我们要反向回复的话,只需要增加BL.Point.Num的值就可以了 。
不过,在前面的顺序下假设我们回复到4之后,是不可能恢复到并未登陆的6的。
因此,我们应该反向回复到已经登录了的编号为止。
由于我们用x,y坐标都为0来表示当前子弹还没有登录,如果子弹的不符合这个条件的话我们就增加一次。
//Y键按下的情况 if( Key[KEY_INPUT_Y]==1 || Key[KEY_INPUT_Y]>30 ){ //按下多少次Y键回复多少次,且只能恢复到之前存在过的样子 if( BlPoint.Num<PMAX-1 && !(BlPoint.Bl[BlPoint.Num].x==0 && BlPoint.Bl[BlPoint.Num].y==0) ){ BlPoint.Num++; } } }
现在来实现功能3。
我们用Operate.Space来调整子弹的空白。嘛,这个是很简单的吧。
//S键被按下的情况 if( Key[KEY_INPUT_S]>0 ){ //如果左方向键被按下 if( Key[KEY_INPUT_LEFT]==1 || Key[KEY_INPUT_LEFT]>30){ //增加空白 if( Operate.Space>2 ){ Operate.Space--; } } //右方向键 if( Key[KEY_INPUT_RIGHT]==1 || Key[KEY_INPUT_RIGHT]>30){ //增加空白 if( Operate.Space<300 ){ Operate.Space++; } } }
现在来实现功能4。
如果别的什么键都不按而按下左右方向键,就可以调整子弹的角度。
嘛,这个也不困难呢。
else{ if( Key[KEY_INPUT_LEFT]>0 )//左方向键 Operate.Angle-=PI2/360;//左转 if( Key[KEY_INPUT_RIGHT]>0 )//右方向键 Operate.Angle+=PI2/360;//右转 }
现在来实现功能5。
每次按下C键都会变更子弹的颜色。
在这里,请注意每个子弹的颜色的最大数都是不同的。
请稍微看一下「dat/img/bullet/」。
0号子弹b0.png的子弹颜色有5种。 1号子弹b1.png的子弹颜色有6种。 因此,比如如果要表示0号子弹的第6个颜色那就是不可能的了。
因此,我们在这个函数的开始定义保存颜色最大数的数组Col。
通过这个变量我们来控制颜色吧。
if( Key[KEY_INPUT_C]==1 )//颜色 Operate.Col = (Operate.Col+1)%Col[Operate.Knd];
嘛,剩下的工作就和之前一样了。
按下Space键的时候,将会让flag交互变换,使显示或隐藏。
用K键进行变更。变更的时候自动将颜色变为0。
if( Key[KEY_INPUT_SPACE]==1 )//显示flag Operate.flag*=-1; if( Key[KEY_INPUT_K]==1 ){//种类 Operate.Knd = (++Operate.Knd)%14; Operate.Col = 0; } }
之后我们只要完成显示数据内容的Show函数就OK了。
前半部分完成的内容和上一章没有区别。
只需要在Operate变量的内容中追加用于显示的部分就行了。
//显示数据 void Show(){ int i; //背景を描画 //绘制背景 DrawGraph(0,0,ImgBack,FALSE); //弾を描画 //绘制子弹 for(i=0; i<BlPoint.Num; i++){ DrawRotaGraphF( BlPoint.Bl[ i ].x, BlPoint.Bl[ i ].y,1.0,BlPoint.Bl[i].Angle, ImgBullet[BlPoint.Bl[i].Knd][BlPoint.Bl[i].Col], TRUE ); } //绘制引出来的线 if( Operate.State==1 ){ DrawLine( (int)Operate.fPt1.x, (int)Operate.fPt1.y, Mouse.x, Mouse.y, Red ); } //鼠标指针位置绘制子弹 DrawRotaGraph( Mouse.x, Mouse.y, 1.0, Operate.Angle, ImgBullet[Operate.Knd][Operate.Col], TRUE ); //绘制完子弹之后,再绘制有多少当前设定了的空格 DrawLine(Mouse.x,Mouse.y,Mouse.x+Operate.Space,Mouse.y,Blue); //如果显示flag为有效的话,显示当前的操作设定内容 if(Operate.flag==1){ SetDrawBlendMode( DX_BLENDMODE_ALPHA , 128 ) ; DrawBox(0,0,230,120,0,TRUE); SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0 ) ; DrawFormatString(0, 0,White,"坐标[%3d,%3d]",Mouse.x,Mouse.y); DrawFormatString(0, 20,White,"种类 [%2d] : K键",Operate.Knd); DrawFormatString(0, 40,White,"颜色 [%2d] : C键",Operate.Col); DrawFormatString(0, 60,White,"角度[%5.1f°] : ←→键",Operate.Angle/PI2*360.0f); DrawFormatString(0, 80,White,"空白 [%3d] : S+←→键",Operate.Space); DrawFormatString(0,100,White,"按下Space键不显示"); } }
唉呀,我们还忘记写把我们好不容易做出来的数据给写入到文件中的函数了。
//写出数据 void Output(){ int i; FILE *fp; //座標データを-1~1に変換する //坐标数据变换到-1~1之间 for(i=0; i<BlPoint.Num; i++){ BlPoint.Bl[i].x -= WINDOW_SIZE_X/2; BlPoint.Bl[i].x /= WINDOW_SIZE_X/2; BlPoint.Bl[i].y -= WINDOW_SIZE_Y/2; BlPoint.Bl[i].y /= WINDOW_SIZE_Y/2; } fp = fopen( "Output.dat" , "wb" ); if( fp == NULL ) return; fwrite( &BlPoint, sizeof(BlPoint), 1, fp ); fclose(fp); }
文件的输入输出用fopen函数就可以进行。
着并不是什么特别的函数,而是C的标准函数。
如果不知道如何使用的话,请谷歌“fopen“学习一下。
我们把结构体一次性写出到文件中。
然后读入的时候调用fread函数就能一次性读入了。
关于读入将会在下一章中说明。
在这里稍微注意一下。
为什么在写入数据之前要用for语句来处理一下呢?这是一个要点。
现在,各个坐标数据(x,y)都用0~640(WINDOW_SIZE_X, WINDOW_SIZE_Y)之间的值来表示出来了呢。(严格来说是639)
在上面的程序中(0,0) ~(640,640)都是允许的。
不过这么一来,画面的中心就是(320,320)呢。我们希望这一点能作为原点(0,0)。为什么这样子会更好我们会在下一章中实际体会到的。
再者,如果我们用-1~1的范围来展示坐标,表示出离中心有多远的话,会使子弹速度的设定之类的工作会更加方便,因此我们将
(0,0)~(640,640)的数据变换到(-1,-1)~(1,1)去。
这个处理我们也写好了呢。
那么最后我们最后来清晰地看一下完整的代码吧。
—在 main.cpp 中进行一下部分追加—
#define WINDOW_SIZE_X 640 #define WINDOW_SIZE_Y 640 --- (中略) --- void CalcOperate(){ //各弾の色数 //各个子弹的颜色数 int Col[14]={5,6,10,5,10,3,3,10,10,3,8,8,10,10}; //左Ctrl键被着的时候 if( Key[KEY_INPUT_LCONTROL]>0 ){ //Z键如果被按下 if( Key[KEY_INPUT_Z]==1 || Key[KEY_INPUT_Z]>30){ //操作往前退一步 if( BlPoint.Num>0 ){ BlPoint.Num--; } } //Y键按下的情况 if( Key[KEY_INPUT_Y]==1 || Key[KEY_INPUT_Y]>30 ){ //按下多少次Y键回复多少次,且只能恢复到之前存在过的样子 if( BlPoint.Num<PMAX-1 && !(BlPoint.Bl[BlPoint.Num].x==0 && BlPoint.Bl[BlPoint.Num].y==0) ){ BlPoint.Num++; } } } } //S键被按着时候 if( Key[KEY_INPUT_S]>0 ){ //左方向键如果被按下 if( Key[KEY_INPUT_LEFT]==1 || Key[KEY_INPUT_LEFT]>30){ //减少空白 if( Operate.Space>2 ){ Operate.Space--; } } //右方向键 if( Key[KEY_INPUT_RIGHT]==1 || Key[KEY_INPUT_RIGHT]>30){ //增加空白 if( Operate.Space<300 ){ Operate.Space++; } } } else{ if( Key[KEY_INPUT_LEFT]>0 )//左方向键 Operate.Angle-=PI2/360;//左转 if( Key[KEY_INPUT_RIGHT]>0 )//右方向键 Operate.Angle+=PI2/360;//右转 } if( Key[KEY_INPUT_C]==1 )//颜色 Operate.Col = (++Operate.Col)%Col[Operate.Knd]; if( Key[KEY_INPUT_SPACE]==1 )//显示flag Operate.flag*=-1; if( Key[KEY_INPUT_K]==1 ){//种类 Operate.Knd = (++Operate.Knd)%14; Operate.Col = 0; } } void Show(){ int i; //背景を描画 //绘制背景 DrawGraph(0,0,ImgBack,FALSE); //弾を描画 //绘制子弹 for(i=0; i<BlPoint.Num; i++){ DrawRotaGraphF( BlPoint.Bl[ i ].x, BlPoint.Bl[ i ].y,1.0,BlPoint.Bl[i].Angle, ImgBullet[BlPoint.Bl[i].Knd][BlPoint.Bl[i].Col], TRUE ); } //绘制引出来的线 if( Operate.State==1 ){ DrawLine( (int)Operate.fPt1.x, (int)Operate.fPt1.y, Mouse.x, Mouse.y, Red ); } //鼠标指针位置绘制子弹 DrawRotaGraph( Mouse.x, Mouse.y, 1.0, Operate.Angle, ImgBullet[Operate.Knd][Operate.Col], TRUE ); //绘制完子弹之后,再绘制有多少当前设定了的空格 DrawLine(Mouse.x,Mouse.y,Mouse.x+Operate.Space,Mouse.y,Blue); //如果显示flag为有效的话,显示当前的操作设定内容 if(Operate.flag==1){ SetDrawBlendMode( DX_BLENDMODE_ALPHA , 128 ) ; DrawBox(0,0,230,120,0,TRUE); SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0 ) ; DrawFormatString(0, 0,White,"坐标[%3d,%3d]",Mouse.x,Mouse.y); DrawFormatString(0, 20,White,"种类 [%2d] : K键",Operate.Knd); DrawFormatString(0, 40,White,"颜色 [%2d] : C键",Operate.Col); DrawFormatString(0, 60,White,"角度[%5.1f°] : ←→键",Operate.Angle/PI2*360.0f); DrawFormatString(0, 80,White,"空白 [%3d] : S+←→键",Operate.Space); DrawFormatString(0,100,White,"按下Space键不显示"); } } void Output(){ int i; FILE *fp; //坐标数据变换到-1~1之间 for(i=0; i<BlPoint.Num; i++){ BlPoint.Bl[i].x -= WINDOW_SIZE_X/2; BlPoint.Bl[i].x /= WINDOW_SIZE_X/2; BlPoint.Bl[i].y -= WINDOW_SIZE_Y/2; BlPoint.Bl[i].y /= WINDOW_SIZE_Y/2; } fp = fopen( "Output.dat" , "wb" ); if( fp == NULL ) return; fwrite( &BlPoint, sizeof(BlPoint), 1, fp ); fclose(fp); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode(TRUE);//窗口模式 SetGraphMode(WINDOW_SIZE_X,WINDOW_SIZE_Y,32);//变更画面尺寸 SetWindowSizeChangeEnableFlag(TRUE);//允许调整画面的大小 if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初始化与里表面化 SetMouseDispFlag( TRUE ) ;//鼠标显示有效 ini();//初始化 load();//载入 while(ProcessMessage()==0/*消息处理*/ && ClearDrawScreen()==0 /*清空画面*/&& GetHitKeyStateAll_2(Key)==0 /*保存输入状态*/&& Key[KEY_INPUT_ESCAPE]==0/* ESC键没有被按下*/){ GetHitMouseStateAll_2(&Mouse); CalcMouse();//鼠标计算 CalcOperate();//操作计算 Show();//显示 ScreenFlip(); } Output();//写出数据 DxLib_End(); return 0; }
这样一来我们就可以用子弹来描图画或者文字,描完 之后按下ESC键就可以吧子弹的坐标数据写到Output.data文件中。
在下一章中我们来使用这个数据来制作弹幕吧。