第五十一章 来制作完成汉字弹幕的工具吧(1)

>>点此回到教程目录

各位,好久不见。

在四圣龙神录Plus中

出现了这种风格的,图片和文字的弹幕。因为我收到了多个拜托我把这种弹幕的制作方法介绍一下的委托,因此我想就在这里开始讲解吧。

那么,我想既然这一章是“制作完成汉字弹幕的工具吧”,那么和龙神录的工程就完全不一样了而是另外一个“辅助工具制作”的工程了,因此在这一章我们不使用前面我们一直在使用的工程。

我们使用每次写程序都用到的框架http://dixq.net/g/#41,从头开始制作。如果是手动创建工程的话,请自行创建一个新的工程。

至于这工具要做成什么样子,首先请仔细看一下。另外,由于这个工具会在下一章中完成,因此这一章的运行结果看起来就有一种alpha版本的感觉。



就象这样,在下面先铺上自己想要描绘的汉字的图像,然后在上面像临摹一样放置子弹。

子弹的放置,就像在画图工具中拖拉的间断线那样。

每点击一次鼠标,就在鼠标的指针那里引出一条线,在下一次单击的时候就以单击的位置为终点在一条直线上放置子弹。

也即是说,如果两次在同一个地方单机的话那么就只在那里放置一个子弹,如果想在一条直线放置子弹的话,那就像拖拉间断线那样做就行了。 另外,当你正在专心致志地进行汉字数据的制作,在将要完成的时候突然发现“啊,画错了”,但是这个时候却没办法消去或者撤销,那可就泪奔了。

因此,在后面我们试着实现撤销的功能吧。

接下来,可能会有人觉得这会很困难,不过其实这很简单的。

我简单地说明一下处理的流程。

~鼠标部分~

查询鼠标是否单击
如果是第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;
}

>>点此回到教程目录

results matching ""

    No results matching ""