>>点此回到教程目录
各位,好久不见。
在四圣龙神录Plus中
出现了这种风格的,图片和文字的弹幕。因为我收到了多个拜托我把这种弹幕的制作方法介绍一下的委托,因此我想就在这里开始讲解吧。
那么,我想既然这一章是“制作完成汉字弹幕的工具吧”,那么和龙神录的工程就完全不一样了而是另外一个“辅助工具制作”的工程了,因此在这一章我们不使用前面我们一直在使用的工程。
我们使用每次写程序都用到的框架http://dixq.net/g/#41,从头开始制作。如果是手动创建工程的话,请自行创建一个新的工程。
至于这工具要做成什么样子,首先请仔细看一下。另外,由于这个工具会在下一章中完成,因此这一章的运行结果看起来就有一种alpha版本的感觉。
就象这样,在下面先铺上自己想要描绘的汉字的图像,然后在上面像临摹一样放置子弹。
子弹的放置,就像在画图工具中拖拉的间断线那样。
每点击一次鼠标,就在鼠标的指针那里引出一条线,在下一次单击的时候就以单击的位置为终点在一条直线上放置子弹。
也即是说,如果两次在同一个地方单机的话那么就只在那里放置一个子弹,如果想在一条直线放置子弹的话,那就像拖拉间断线那样做就行了。 另外,当你正在专心致志地进行汉字数据的制作,在将要完成的时候突然发现“啊,画错了”,但是这个时候却没办法消去或者撤销,那可就泪奔了。
因此,在后面我们试着实现撤销的功能吧。
接下来,可能会有人觉得这会很困难,不过其实这很简单的。
我简单地说明一下处理的流程。
~鼠标部分~ 查询鼠标是否单击如果是第1次单击鼠标,那么记下那个坐标。如果是第2次单击鼠标,那么从第1次单击的地方开始向第2次单击的地方前进,同时反复进行这样子的工作:空出一定的距离然后登陆一次子弹的坐标。 ~绘制部分~ 绘制背景 绘制已经登录了的子弹如果已经是第1次单击的状态的话,那么从已经记忆了的地方开始往当前鼠标指针所在的坐标引线,仅此而已。
~鼠标部分~
查询鼠标是否单击如果是第1次单击鼠标,那么记下那个坐标。如果是第2次单击鼠标,那么从第1次单击的地方开始向第2次单击的地方前进,同时反复进行这样子的工作:空出一定的距离然后登陆一次子弹的坐标。
~绘制部分~
绘制背景
绘制已经登录了的子弹如果已经是第1次单击的状态的话,那么从已经记忆了的地方开始往当前鼠标指针所在的坐标引线,仅此而已。
那么,实际的操作请直接看程序部分吧。
由于这次我们使用了鼠标。因此我们使用了这样一些函数。
监视鼠标按键状态的函数
由于这个函数的内部处理不用清楚了解就能够使用,因此我就不做特别说明了。
另外,因为这次并不是和龙神录的工程一起说明的东西,所以把文件给分开了。
— Key_Mouse.h —
typedef struct{ int x; int y; //坐标 unsigned int Button[8]; //按钮的按键状态 int WheelRotVol;//滚轮的旋转量 }Mouse_t; int GetHitKeyStateAll_2(int GetHitKeyStateAll_InputKey[]){ char GetHitKeyStateAll_Key[256]; GetHitKeyStateAll( GetHitKeyStateAll_Key ); for(int i=0;i<256;i++){ if(GetHitKeyStateAll_Key[i]==1) GetHitKeyStateAll_InputKey[i]++; else GetHitKeyStateAll_InputKey[i]=0; } return 0; } int GetHitMouseStateAll_2(Mouse_t *Nezumi){ if(GetMousePoint( &Nezumi->x, &Nezumi->y ) == -1){ //获得鼠标位置 return -1; } int MouseInput=GetMouseInput(); //获得鼠标的按键状态 for(int i=0; i<8; i++){ //确认鼠标最多8个按键 if( (MouseInput & 1<<i ) != 0 ) Nezumi->Button[i]++; //如果有按键的话计数器增加 else Nezumi->Button[i] = 0; //如果没有按键的话就是0 } Nezumi->WheelRotVol = GetMouseWheelRotVol() ; //获得滚轮的旋转量 return 0; }
好的,我们就用这个文件名保存它。随便保存到哪里都行。
如果想要完全不做改变而使用这个工程的话,请参考下载了的工程。放在“mydata/source”里面。
接下来我们来说明定义的结构体。
//float型的坐标结构体 typedef struct{ float x,y; }fPt_t; //每个子弹的信息 typedef struct{ int Knd;//子弹的种类 int Col;//子弹的颜色 float Angle;//子弹的角度 float x,y; }Bl_t; //子弹全体的信息 typedef struct{ int Num;//登录了的个数 Bl_t Bl[ PMAX ]; //登录的子弹的信息 }BlPoint_t; //操作设定部分信息 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;
这次就像这个样子定义4个结构体。第1个结构体就不再多说。全部都和注释说明的一样,第2个结构体用于保存每个子弹的信息,第3个结构体用于设置全体子弹,最后一个是和操作设定有关的结构体。
看起来并不是很难嘛。由于子弹有种类、颜色、角度以及坐标这些属性因此放到Bl_t中。Bl即是Bullet。
至于BlPoint_t,假如我们用PMAX表示构成汉字的子弹的数量的话,那么就有必要记录这PMAX的一整个信息,因此我们定义了这个结构体。
Num中传入登录了的子弹的个数。
关于设定部分正如你所见。都是与放置什么样子的子弹的设定之类相关。
那么,我们来看看登录子弹时候的处理吧。
第2次单击的时候,就从第1次单击的坐标(以后称“第1次的坐标”)的位置开始以某个间隔在一条直线上放置子弹。
具体而言,就是用atan2来计算用第1次的坐标和第2次的坐标的角度,这个和射击的移动是一个道理,将第1次的坐标作为初始地点,在计算好了的角度下往第2次的坐标的方向进行类似飞过去那样前进就行了。
在设定项目中设定的间隔“Space”即是速度。我们以此在每一个计算好了的地方放置空白那么子弹也就放好了。
一开始就把第一次和第二次坐标之间的距离计算好,然后按照之前说明的顺序反复进行登录,要是移动的距离超过了的话那就停止登录。
我们使用以下三个函数来进行上述计算。
//登录子弹 void InputBlData(float x, float y, int Knd, int Col, float Angle){ BlPoint.Bl[BlPoint.Num].x = x;//记录当前的位置 BlPoint.Bl[BlPoint.Num].y = y; BlPoint.Bl[BlPoint.Num].Knd = Knd;//子弹的种类 BlPoint.Bl[BlPoint.Num].Col = Col;//颜色 BlPoint.Bl[BlPoint.Num].Angle = Angle;//角度 BlPoint.Num++;//表示当前有多少个子弹的计数器自增 } //放置子弹时候的计算 void CalcBullet(){ float x = Operate.fPt1.x, y = Operate.fPt1.y;//最开始的位置 //最开始单击的位置和最后单击的位置之间的角度 float Angle = atan2( Operate.fPt2.y - Operate.fPt1.y, Operate.fPt2.x - Operate.fPt1.x ); float xlen = Operate.fPt2.x - Operate.fPt1.x;//x的距离 float ylen = Operate.fPt2.y - Operate.fPt1.y;//y的距离 float Length = sqrt( xlen * xlen + ylen * ylen );//点与点的距离 float Proceeded = 0;//当前前进了的距离 //当前前进的距离在允许前进的距离以内的区间内,且在登录可能的个数下循环 while( BlPoint.Num < PMAX ){ InputBlData(x,y,Operate.Knd,Operate.Col,Operate.Angle); x += cos( Angle ) * Operate.Space;//往应该前进的方向前进 y += sin( Angle ) * Operate.Space; Proceeded += Operate.Space;//合计已经前进的距离 if(Length < Proceeded) break; } } //鼠标的计算 void CalcMouse(){ if( Mouse.Button[0]==1 ){//如果单击了左键 switch( Operate.State ){ case 0://第1次单击的时候 //记录那个时候的位置 Operate.fPt1.x = (float)Mouse.x; Operate.fPt1.y = (float)Mouse.y; Operate.State = 1; break; case 1://第2次单击的时候 //记录那个时候的位置 Operate.fPt2.x = (float)Mouse.x; Operate.fPt2.y = (float)Mouse.y; Operate.State = 0; //从第1次单击的位置开始到第2次单击的位置为止登录子弹 CalcBullet(); break; } } }
绘制的部分非常简单。
绘制背景、进行登录了的子弹个数次绘制,如果是单击了的状态的话,就从记录了的坐标开始到当前鼠标指针的位置绘制线段就行了。 内容如下。
//显示数据 void Show(){ int i; //绘制背景 DrawGraph(0,0,ImgBack,FALSE); //绘制子弹 for(i=0; i<BlPoint.Num; i++){ DrawCircle( (int)BlPoint.Bl[ i ].x, (int)BlPoint.Bl[ i ].y, 3, Red, TRUE ); } //绘制引出来的线 if( Operate.State==1 ){ DrawLine( (int)Operate.fPt1.x, (int)Operate.fPt1.y, Mouse.x, Mouse.y, Red ); } //在鼠标指针位置显示子弹 DrawCircle( Mouse.x, Mouse.y, 3, Red, TRUE ); //绘制完子弹之后,再绘制有多少当前设定了的空格 DrawLine(Mouse.x,Mouse.y,Mouse.x+Operate.Space,Mouse.y,Blue); }
之后是main函数。
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode(TRUE);//窗口模式 SetGraphMode(640,640,32);//画面尺寸变更 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(); } DxLib_End(); return 0; }
数据载入部分之类的在这里就省略不写了,这样子就是所有了。那么最后清除地来看一下整体吧。
— main.cpp —
#include "../../../include/DxLib.h" #include "math.h" #include "Key_Mouse.h" #define PI2 (3.141562f*2) //圆周率*2 #define PMAX 1000 //登录的子弹的最大数量 //float型坐标结构体 typedef struct{ float x,y; }fPt_t; //单个子弹的信息 typedef struct{ int Knd;//子弹的种类 int Col;//子弹的颜色 float Angle;//子弹的角度 float x,y; }Bl_t; //子弹全体信息 typedef struct{ int Num;//已登录的个数 Bl_t Bl[ PMAX ]; //登录的子弹的信息 }BlPoint_t; BlPoint_t BlPoint; //操作设定类情报 typedef struct{ int State; int Knd; int Col; int Space; float Angle; int flag; fPt_t fPt1; fPt_t fPt2; }Operate_t; int Key[256];//按键 int Red,White,Blue;//颜色 int ImgBullet[14][10],ImgBack;//子弹的图像和背景图像 Mouse_t Mouse;//鼠标 Operate_t Operate;//操作设定 //初始化 void ini(){ Operate.Knd=7;//将子弹的初始种类设置为7 Operate.Space=20;//Spac最开始为20 Operate.flag=1; } //载入 void load(){ White=GetColor(255,255,255); Red = GetColor(255,0,0); Blue = GetColor(0,255,255); ImgBack = LoadGraph("mydat/img/ryu.png"); } //登录子弹 void InputBlData(float x, float y, int Knd, int Col, float Angle){ BlPoint.Bl[BlPoint.Num].x = x;//记录当前位置 BlPoint.Bl[BlPoint.Num].y = y; BlPoint.Bl[BlPoint.Num].Knd = Knd;//子弹的种类 BlPoint.Bl[BlPoint.Num].Col = Col;//颜色 BlPoint.Bl[BlPoint.Num].Angle = Angle;//角度 BlPoint.Num++;//表示当前有多少个子弹的计数器自增 } //放置子弹时候的计算 void CalcBullet(){ float x = Operate.fPt1.x, y = Operate.fPt1.y;//最开始的地点 //最开始单击的位置和最后单击的位置的角度 float Angle = atan2( Operate.fPt2.y - Operate.fPt1.y, Operate.fPt2.x - Operate.fPt1.x ); float xlen = Operate.fPt2.x - Operate.fPt1.x;//x的距离 float ylen = Operate.fPt2.y - Operate.fPt1.y;//y的距离 float Length = sqrt( xlen * xlen + ylen * ylen );//点与点的距离 float Proceeded = 0;//当前已经前进的距离 //当前前进的距离在允许前进的距离以内的区间内,且在登录可能的个数下循环 while( BlPoint.Num < PMAX ){ InputBlData(x,y,Operate.Knd,Operate.Col,Operate.Angle); x += cos( Angle ) * Operate.Space;//往应该前进的方向前进 y += sin( Angle ) * Operate.Space; Proceeded += Operate.Space;//合计已经前进的距离 if(Length < Proceeded) break; } } //鼠标的计算 void CalcMouse(){ if( Mouse.Button[0]==1 ){//如果单击了左键 switch( Operate.State ){ case 0://第1次单击的时候 //记录那个时候的位置 Operate.fPt1.x = (float)Mouse.x; Operate.fPt1.y = (float)Mouse.y; Operate.State = 1; break; case 1://第2次单击的时候 //记录那个时候的位置 Operate.fPt2.x = (float)Mouse.x; Operate.fPt2.y = (float)Mouse.y; Operate.State = 0; //从第1次单击的位置开始到第2次单击的位置为止登录子弹 CalcBullet(); break; } } } void CalcOperate(){ } //显示数据 void Show(){ int i; //绘制背景 DrawGraph(0,0,ImgBack,FALSE); //绘制子弹 for(i=0; i<BlPoint.Num; i++){ DrawCircle( (int)BlPoint.Bl[ i ].x, (int)BlPoint.Bl[ i ].y, 3, Red, TRUE ); } //绘制引出来的线 if( Operate.State==1 ){ DrawLine( (int)Operate.fPt1.x, (int)Operate.fPt1.y, Mouse.x, Mouse.y, Red ); } //在鼠标指针位置显示子弹 DrawCircle( Mouse.x, Mouse.y, 3, Red, TRUE ); //绘制完子弹之后,再绘制有多少当前设定了的空格 DrawLine(Mouse.x,Mouse.y,Mouse.x+Operate.Space,Mouse.y,Blue); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){ ChangeWindowMode(TRUE);//窗口模式 SetGraphMode(640,640,32);//画面尺寸变更 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(); } DxLib_End(); return 0; }