55 章

では54章で作った画像→弾幕変換ツールを完成させましょう。

今回追加すべき処理は「弾を置く」処理です。

決められた間隔で、線の上に弾を置いていきます。

全てのピクセルを端から調べていき、その場所が色つきなら置くことを検討します。

まず、現在登録されている弾全てとその現在の位置との距離を測り、全ての弾の距離が決められた距離以上なら、そこに弾を置いてよい事になります。

こうして、一定の距離以内に弾が重ならないようにして、線の上に弾を置いていきましょう。

また、弾を置く間隔は左右キーで調整出来るようにしておきます。

更に、スペースキーを押すことで、「弾だけ表示」「線画と弾表示」「線画だけ表示」など表示の方法を変えてやる機能もつけます。

とりあえず、コードを見ていきましょう。。

まず、弾の構造体を用意します。


//弾の座標(55)
typedef struct{
        float x,y;
}Pt_t;
//置いた弾の構造体
typedef struct{
        int num;//現在置いた弾数
        Pt_t Pt[ BULLET_MAX ];
}Bl_t;


座標の構造体Pt_tとそれがBULLET_MAX個集まった置いた弾に関する構造体Bl_tを用意します。


typedef struct{
        int state; //状態
        double len; //間隔
}Operate_t;

Bl_t Bl;
Operate_t Operate;


今度も操作に関する構造体Operate_tを用意します。表示の方法に関する.stateと、弾を置く距離に関する.lenがあります。


int img_bullet[4] ;
int img_back;


画像を使うので画像ハンドル格納用変数用意します


/* 弾置き計算 */
void CalcPut(){
    int i,x,y;
    double lx,ly,len;
    //現在登録されている弾を0個に
    Bl.num = 0;
    //ビットマップ画像サイズ分ループ
    for(y=0;y<BMP_TATE;y++){
        for(x=0;x<BMP_YOKO;x++){
            //もう登録できないなら抜ける
            if( Bl.num >= BULLET_MAX-1 ){
                break;
            }
            //その場が色つきなら
            if( Pixel[y][x] == 1){
                //今登録されている弾分ループ
                for(i=0; i<Bl.num; i++){
                    //その全ての弾と現在の場所との距離がOperate.len以上なら抜ける
                    lx = x - Bl.Pt[i].x;
                    ly = y - Bl.Pt[i].y;
                    if( lx*lx + ly*ly < Operate.len*Operate.len){
                        break;
                    }
                }
                //途中で抜けなかった=一つも近くに弾が無かったなら
                if( i == Bl.num ){
                    //登録
                    Bl.Pt[ Bl.num ].x = x;
                    Bl.Pt[ Bl.num ].y = y;
                    Bl.num++;
                }
            }
        }
    }
}


画像を端からチェックします。

色つきのピクセルにさしかかったら、そこに弾を置くことを検討します。

現在登録されている全ての弾との距離を測り、どの弾とも規定間隔以上ならそこに弾を登録します。


/* 操作計算 */
void CalcOperate(){
    //スペースが押されるたびに状態変化
    if( Key[KEY_INPUT_SPACE] == 1 ){
        Operate.state = ( Operate.state+1 )%3;
    }
    //左が押されると.lenを少なく
    if( Key[KEY_INPUT_LEFT] == 1 || Key[KEY_INPUT_LEFT] > 20 ){
        if( Operate.len > 1 ){
            Operate.len -= 0.2;
        }
        CalcPut();
    }
    //右が押されると.lenを多く
    if( Key[KEY_INPUT_RIGHT] == 1 || Key[KEY_INPUT_RIGHT] > 20 ){
        if( Operate.len < 50 ){
            Operate.len += 0.2;
        }
        CalcPut();
    }
}


操作を計算します。スペースキを押すごとに表示状態を変更します。

また、左右キーを押すごとに規定間隔を変更します。押しっぱなしで早く計算できます。


/* 描画 */
void Graph(){
    int i;
    //状態によって描画パターンを変える
    if( Operate.state == 1 ||  Operate.state == 2 ){
        DrawGraph(0,0,img_back,FALSE);
    }
    if( Operate.state == 0 ||  Operate.state == 1 ){
        for(i=0; i<Bl.num; i++){
            DrawRotaGraph(Bl.Pt[i].x,Bl.Pt[i].y,1.0,0.0,img_bullet[1],TRUE);
        }
    }
    SetDrawBlendMode( DX_BLENDMODE_ALPHA , 64 ) ;
    DrawBox(0,0,100,40,0,TRUE);
    SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ) ;
    DrawFormatString(0,0,GetColor(255,255,255),"間隔=%.1f",Operate.len);
    DrawFormatString(0,20,GetColor(255,255,255),"弾数=%d",Bl.num);
}


操作状態によって線画だけ、とか、弾だけ、とかにわけて表示します。

現在登録されている弾数や間隔も表示します。


//データを出力
void Output(){
    int i;
    FILE *fp;
    fp = fopen( "Output.dat" , "wb" );

    for(i=0; i<Bl.num; i++){//座標データを-1〜1に変換する
        Bl.Pt[i].x -= BMP_YOKO/2;
        Bl.Pt[i].y -= BMP_TATE/2;
        Bl.Pt[i].x /= BMP_YOKO/2;
        Bl.Pt[i].y /= BMP_TATE/2;
    }
    if( fp == NULL ){
        return;
    }
    fwrite( &Bl, sizeof(Bl_t), 1, fp );
    fclose(fp);
}


データの出力に関しては前の章と同じです。


では全体を通してみてみます。

--- main.cpp --- 

#include "../../../include/DxLib.h"
#include "Key_Mouse.h"

/* ビットマップ画像の横サイズ */
#define BMP_YOKO 400
/* ビットマップ画像の縦サイズ */
#define BMP_TATE 400

/* ビットマップのヘッダサイズ */
#define HEAD 54
/* トータルサイズ */
#define TOTAL (BMP_YOKO*BMP_TATE*3+HEAD)

/* AAデータを作成する合計枚数 */
#define BULLET_MAX 4000

/* ビットマップの色情報を格納するための構造体 */
typedef struct{
        unsigned char col[3];
}img_t;

int Key[256];

//弾の座標(55)
typedef struct{
    float x,y;
}Pt_t;
//置いた弾の構造体
typedef struct{
    int num;//現在置いた弾数
    Pt_t Pt[ BULLET_MAX ];
}Bl_t;

typedef struct{
    int state; //状態
    double len; //間隔
}Operate_t;

Bl_t Bl;
Operate_t Operate;

/* TOTAL分データを用意 */
unsigned char data[TOTAL];
/* イメージ画像のピクセル数だけ色格納用構造体を用意 */
img_t img[BMP_TATE][BMP_YOKO];
/* 2値化した情報を入れるための配列を用意 */
BYTE Pixel[BMP_TATE][BMP_YOKO];

int img_bullet[4] ; //(55)
int img_back;

/* ビットマップを読み込みdataに格納する関数 */
int ReadBmp(){
    char name[256]="../AA画像/aisha.bmp";
    FILE *fp;
    fp = fopen( name , "rb" );
    if( fp == NULL ){
        printfDx( "%sが見つかりません。",name);
        return -1;
    }
    fread( data, TOTAL, 1, fp );
    fclose(fp);
    return 0;
}

/* ビットマップの生データを各ピクセルの色格納用構造体に入れ直す */
void ConvData(){
    int x,y,c,t;
    t=HEAD;
    for(y=BMP_TATE-1;y>=0;y--){
        for(x=0;x<BMP_YOKO;x++){
            for(c=0;c<3;c++){
                img[y][x].col[c]=data[t];
                t++;
            }
        }
    }
}

/* 2値化 */
void Binarization(){
    int x,y,c;
    int sum;
    for(y=0;y<BMP_TATE;y++){
        for(x=0;x<BMP_YOKO;x++){
            sum = 0;
            //色の輝度平均を計算
            for(c=0;c<3;c++){
                sum += img[y][x].col[c];
            }
            sum /= 3;
            //0〜255の輝度の平均が128以上なら(明るいなら)0、未満なら(暗いなら)1
            if( sum >= 128){
                Pixel[y][x] = 0;
            } else {
                Pixel[y][x] = 1;
            }
        }
    }
}

/* 弾置き計算 */
void CalcPut(){
    int i,x,y;
    double lx,ly,len;
    //現在登録されている弾を0個に
    Bl.num = 0;
    //ビットマップ画像サイズ分ループ
    for(y=0;y<BMP_TATE;y++){
        for(x=0;x<BMP_YOKO;x++){
            //もう登録できないなら抜ける
            if( Bl.num >= BULLET_MAX-1 ){
                break;
            }
            //その場が色つきなら
            if( Pixel[y][x] == 1){
                //今登録されている弾分ループ
                for(i=0; i<Bl.num; i++){
                    //その全ての弾と現在の場所との距離がOperate.len以上なら抜ける
                    lx = x - Bl.Pt[i].x;
                    ly = y - Bl.Pt[i].y;
                    if( lx*lx + ly*ly < Operate.len*Operate.len){
                        break;
                    }
                }
                //途中で抜けなかった=一つも近くに弾が無かったなら
                if( i == Bl.num ){
                    //登録
                    Bl.Pt[ Bl.num ].x = x;
                    Bl.Pt[ Bl.num ].y = y;
                    Bl.num++;
                }
            }
        }
    }
}

/* 操作計算 */
void CalcOperate(){
    //スペースが押されるたびに状態変化
    if( Key[KEY_INPUT_SPACE] == 1 ){
        Operate.state = ( Operate.state+1 )%3;
    }
    //左が押されると.lenを少なく
    if( Key[KEY_INPUT_LEFT] == 1 || Key[KEY_INPUT_LEFT] > 20 ){
        if( Operate.len > 1 ){
            Operate.len -= 0.2;
        }
        CalcPut();
    }
    //右が押されると.lenを多く
    if( Key[KEY_INPUT_RIGHT] == 1 || Key[KEY_INPUT_RIGHT] > 20 ){
        if( Operate.len < 50 ){
            Operate.len += 0.2;
        }
        CalcPut();
    }
}

/* 描画 */
void Graph(){
    int i;
    //状態によって描画パターンを変える
    if( Operate.state == 1 ||  Operate.state == 2 ){
        DrawGraph(0,0,img_back,FALSE);
    }
    if( Operate.state == 0 ||  Operate.state == 1 ){
        for(i=0; i<Bl.num; i++){
            DrawRotaGraph(Bl.Pt[i].x,Bl.Pt[i].y,1.0,0.0,img_bullet[1],TRUE);
        }
    }
    SetDrawBlendMode( DX_BLENDMODE_ALPHA , 64 ) ;
    DrawBox(0,0,100,40,0,TRUE);
    SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ) ;
    DrawFormatString(0,0,GetColor(255,255,255),"間隔=%.1f",Operate.len);
    DrawFormatString(0,20,GetColor(255,255,255),"弾数=%d",Bl.num);
}
 
//データを出力
void Output(){
    int i;
    FILE *fp;
    fp = fopen( "Output.dat" , "wb" );

    for(i=0; i<Bl.num; i++){//座標データを-1〜1に変換する
        Bl.Pt[i].x -= BMP_YOKO/2;
        Bl.Pt[i].y -= BMP_TATE/2;
        Bl.Pt[i].x /= BMP_YOKO/2;
        Bl.Pt[i].y /= BMP_TATE/2;
    }
    if( fp == NULL ){
        return;
    }
    fwrite( &Bl, sizeof(Bl_t), 1, fp );
    fclose(fp);
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){

    SetGraphMode(BMP_YOKO,BMP_TATE,16);//400x400に
    SetWindowSizeChangeEnableFlag(TRUE);//画面の大きさを変更できる
    ChangeWindowMode(TRUE);//ウィンドウモード
    if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化
 
    Operate.len = 5;//弾の間隔を5にセット
    img_back = LoadGraph( "../AA画像/aisha.bmp" );
    LoadDivGraph( "../dat/img/bullet/14.png"   , 4 , 4 , 1 , 6 , 6 , img_bullet ) ;//弾読み込み(55)

    ReadBmp();//ビットマップ読み込み
    ConvData();//画像の色情報構造体に格納
    Binarization();//2値化
    CalcPut();//弾を置く計算

    while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){
          //↑メッセージ処理          ↑画面をクリア           ↑入力状態を保存       ↑ESCが押されていない
        CalcOperate();//操作計算
        Graph();//描画
        ScreenFlip();
    }
 
    Output();//ファイル出力
    DxLib_End();
    return 0;
}

実行結果


こうして線の上にそって弾が置けました。

この弾を使って次の章では弾幕を作ってみましょう。


- Remical Soft -