素人プログラマーの日常

効率の良いコード、美しいコードなんて書けません。

SHOOT DOWN 完成!

いよいよ完成お披露目です!

本当は昨日の時点で完成していましたが、サイトにアップしたところで力尽きました(笑

↓そのサイト。こちらでダウンロード出来ます。
http://tenkomorituuhan2.com/products/shootdown/top.html

プレイするまでもないという方のために、プレイ動画もアップしてます(笑


最後の最期で、ウインドウの閉じるボタン(×ボタン)を押しても処理が残ることに気付きました(こういうところがコンピュータの融通が利かないところ!)。急遽ウインドウの×ボタンを押せないようにして stdlib.h を追加して exit() するようにしました。でもって atexit( DxLib_End ) としようとしたら、登録する関数は void型じゃないとエラーになります。仕方ないので void EndDivision() を新たに作成し、そこで終了させています。・・・でもこれって exit いらね~~~!

if( GetWindowUserCloseFlag( TRUE ) ) exit( 0 );

↑この行を↓こうすればいいだけwww

if( GetWindowUserCloseFlag( TRUE ) ) DxLib_End();

無駄なことをしてしまいましたが、動作に影響するわけではありません。数バイト容量が増えただけなので、もう変更しません!手を加えるとしたら、それは "SHOOT DOWN2" として新たに作り直すときです。その日が来るのか、来たとしても記憶に残っているかは怪しいですが。

で、
出来上がってから、息子にプレイさせてみました。数十回やりましたが、やっと400点超えるくらいです。プレイ中に「弾を見ないで敵ばっか見てるんだよな~」とか、ごちゃごちゃと言い訳していました(笑。息子は動きのあるゲームが苦手なので、ま、こんなもんです。私はテストプレイも含め数百回プレイしました(数秒で終わるときもあるしw)。今のところ17054点が最高です。ガツガツやるより、安全重視のほうがレベルが上がっていき、レベルが上がると1機あたりの点数も上がっていくので、最初はそれである程度スコアが稼げます。でもレベルアップすると難しくもなってくるので、高得点を出すには最初からガツガツやるしかなくなります。とはいえ、それほど真剣にならず気軽に楽しむゲームだと思います。真剣にプレイすると、この手のゲームはマバタキを忘れてしまいます。ドライアイ、気をつけてください。



ブログとしては長くなってしまいますが、完成品のソースコードを掲載します。
※素人が書いたものです。このままコピペして実行するとPCが爆発する恐れもありますので、解かる人が確認した上で使用してください。



#include <stdio.h>
#include <stdlib.h>
#include "DxLib.h"

const int wX = 640;            //ウインドウ横幅
const int wY = 480;            //ウインドウ縦幅

const int tamakazu = 5;        //自機の弾の最大数
const int tekikazu = 10;        //敵機の最大数
const int ttamakazu = 100;    //敵機の弾の最大数
const int expkazu = 50;        //爆発エフェクトの最大数
const int starkazu = 10;        //背景星の数

const int tamamx = 7;        //自機の弾の発射間隔


int tama[tamakazu];            //自機の弾フラグ
int tamaX[tamakazu];        //自機の弾のX座標
int tamaY[tamakazu];        //自機の弾のY座標
int tamacnt;                    //次弾発射までのカウンタ
int tamaNo;                        //弾の順番

int teki[tekikazu];                //敵機フラグ
int tekiX[tekikazu];            //敵機のX座標
int tekiY[tekikazu];            //敵機のY座標
int spd[tekikazu];                //敵機の速度
int tekicnt[tekikazu];            //次弾発射までのカウンタ
int tekicntmx[tekikazu];        //次弾発射までの間隔

int ttama[ttamakazu];        //敵機の弾フラグ
int ttamaX[ttamakazu];        //敵機の弾のX座標
int ttamaY[ttamakazu];        //敵機の弾のY座標
int ttamaXspd[ttamakazu];    //敵機の弾のX方向速度
int ttamaYspd[ttamakazu];    //敵機の弾のY方向速度

int exp[expkazu];                //爆発フラグ
int expX[expkazu];                //爆発のX座標
int expY[expkazu];                //爆発のY座標
int expXspd[expkazu];        //爆発のX方向速度
int expYspd[expkazu];            //爆発のY方向速度
int expcnt[expkazu];            //爆発が消えるまでのカウンタ

int starX[starkazu];            //星のX座標
int starY[starkazu];            //星のY座標
int starspd[starkazu];            //星の速度

int score;                            //スコア
int highscore[6];                //ハイスコア
int level;                            //レベル


int mouX , mouY;                //マウスの座標取得用
int X ,Y;                            //自機描画用
int miss;                            //ミスフラグ
int gameF;                        //ゲーム全体のフラグ

int Ghandle[5];                    //メイングラフィック用ハンドル
int Ghandlestar[5];    //星グラフィック用ハンドル

int Shandleexp;                    //爆発音ハンドル
int Shandlebgm;                //BGMハンドル

//プロトタイプ宣言

void Title();                //タイトル画面
void Play();                //メイン

void Allinit();                //スタート時初期化
void Tekiinit();                //編隊ごとの初期化

void Load();                //ロード
void Save();                //セーブ
void EndDivision();        //終了処理

// プログラムは WinMain から始まります
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine, int nCmdShow )
{
    SetOutApplicationLogValidFlag( FALSE );    //ログ出力しない
    SetMainWindowText( "Shoot Down" );        //ウインドウタイトル指定
    SetGraphMode( wX , wY , 32 );            //ウインドウの大きさとカラービット数指定
    ChangeWindowMode( TRUE );                //ウインドウで表示
    SetDrawScreen( DX_SCREEN_BACK );    //ちらつき防止設定
    SetWaitVSyncFlag( TRUE );                    //フレーム同期
    SetMouseDispFlag( TRUE );                    //マウスカーソル表示
    SetWindowUserCloseEnableFlag( FALSE );    //×ボタンで終了しない

    if( DxLib_Init() == -1 ) return -1;        // DXライブラリ初期化処理


    LoadDivGraph( "img/sd_main.png" , 5 , 5 , 1 , 32 , 32 , Ghandle );        //メイン画像読込み
    LoadDivGraph( "img/sd_star.png" , 5 , 5 , 1 , 8 , 8 , Ghandlestar );        //星画像読込み

    Shandleexp = LoadSoundMem( "sound/sd_exp.wav" );
    Shandlebgm = LoadSoundMem( "sound/sd_bgm1.wav" );

    atexit( EndDivision );

    Load();        //ハイスコアロード

    {            // Main Loop START /////////////////////////////////////////////

        while( gameF != 1 ){
            Title();
            Play();
        }

    }            // Main Loop END //////////////////////////////////////////////

    DxLib_End() ;                // DXライブラリ使用の終了処理

    return 0 ;                // ソフトの終了
}

void Title(){            //タイトル画面/////////////////////////////////

    for( int i=4 ; i>-1 ; i-- ){
        if( score > highscore[i] ){
            if( i == 4 ) continue;
            highscore[i+1] = highscore[i];
            highscore[i] = score;
        } else {
            if( i < 4 ) highscore[i+1] = score;
            break;
        }
    }

    if( ( score > 0)||( level > highscore[5] ) )Save();
    if( level > highscore[5] ) highscore[5] = level;

    ClearDrawScreen();
    gameF = 0;

    LoadGraphScreen( 50 , 50 , "img/title.png" , TRUE );    //タイトル画像描画

    //スコア表示
    int col = GetColor( 255 , 255 , 255 );
    DrawFormatString( 5 , 5 , col , "SCORE %d" , score );
    DrawFormatString( 540 , 5 , col , "LEVEL %d" , level );

    //ハイスコア表示
    DrawString( 420 , 100 , "HIGH SCORE" , GetColor( 255 , 255 , 0 ) );
    for( int i=0 ; i<5 ; i++ ){
        DrawFormatString( 420 , 140+i*20 , GetColor( 255 , 255 , 0 ) , "%d %d" , i+1 , highscore[i] );
    }
    DrawFormatString( 420 , 260 , GetColor( 255 , 255 , 0 ) , "LEVEL %d" , highscore[5] );

    DrawString( 50 , 420 , "http://dgen.hatenablog.com/" , GetColor( 255 , 255 , 255 ) );

    ScreenFlip();                    //描画をウインドウへ反映

    while( 1 ){    
        if( GetWindowUserCloseFlag( TRUE ) ) exit( 0 );
        if( ( GetMouseInput() & MOUSE_INPUT_LEFT ) !=0 ) break;
        if( ( GetMouseInput() & MOUSE_INPUT_RIGHT ) !=0 ){
            gameF = 1;
            break;
        }
        WaitVSync( 1 );
    }

    //右クリックで終了

    Allinit();                //スタート時初期化

}

void Play(){            //メイン/////////////////////////////////////////

    while( gameF == 0 ){

        ClearDrawScreen();

        //敵機がいなかったら出現させる
        int j=0;
        for( int i=0 ; i<tekikazu ; i++ ){
            if( teki[i]<1 ) j++;
        }
        if( j == tekikazu ) Tekiinit();

        //マウスの位置を取得
        if( miss == 0 ){
            GetMousePoint( &mouX , &mouY );

            if( 16>mouX ) X = 16;
            else if( mouX>wX-16 ) X = wX-16;
            else X = mouX;

            if( 16>mouY ) Y = 16;
            else if( mouY>wY-16 ) Y = wY-16;
            else Y = mouY;


            //自弾発射処理
            if( tamacnt>tamamx ){
                tama[tamaNo] = 1;
                tamaX[tamaNo] = X-16;
                tamaY[tamaNo] = Y-16;
                tamacnt = 0;
                tamaNo = ( tamaNo+1 )%tamakazu;
            }
            tamacnt++;

        } else {            //ミス処理
            for( int i=0 ; i<expkazu ; i++ ){
                if( exp[i] > 0 ) continue;
                exp[i] = 1;
                expX[i] = (X-16)*10;
                expY[i] = (Y-16)*10;
                expXspd[i] = GetRand( 120 )-60;
                expYspd[i] = GetRand( 120 )-60;
                expcnt[i] = GetRand( 150 )+105;
                break;
            }
            if( miss == 1 ){
                StopSoundMem( Shandlebgm );
                PlaySoundMem( Shandleexp , DX_PLAYTYPE_BACK );
            }

            miss++;
            if( miss > 120 ) gameF = 2;
        }
        //自機描画
        DrawGraph( X-16 , Y-16 , Ghandle[0] , TRUE );

        //自弾移動処理
        for( int i=0 ; i<tamakazu ; i++ ){
            if( tama[i]>0 ){
                tamaY[i] = tamaY[i]-16;
                if( tamaY[i]<-16 ){
                    tama[i] = 0;
                    continue;
                }
                DrawGraph( tamaX[i] , tamaY[i] , Ghandle[2] , TRUE );
            }
        }

        //敵機移動処理
        for( int i=0 ; i<tekikazu ; i++ ){
            if( teki[i]<1 ) continue;
            tekiY[i] = tekiY[i]+spd[i];
            if( tekiY[i]/10 > wY ){
                teki[i] = 0;
                continue;
            }

            for( int j=0 ; j<tamakazu ; j++ ){        //自弾と敵機の当たり判定
                if( tama[j] < 1 ) continue;
                if( ( tamaX[j]-tekiX[i]/10 > -30 )&&( tamaX[j]-tekiX[i]/10 < 30 )&&( tamaY[j]-tekiY[i]/10 > -30 )&&( tamaY[j]-tekiY[i]/10 < 30 ) ){
                    for( int k=0 ; k<expkazu ; k += 5 ){
                        if( exp[k] > 0 ) continue;
                        PlaySoundMem( Shandleexp , DX_PLAYTYPE_BACK );
                        score = score+8+level*2;
                        for( int l=k ; l<k+5 ; l++ ){
                            exp[l] = 1;
                            expX[l] = tekiX[i];
                            expY[l] = tekiY[i];
                            expXspd[l] = GetRand( 120 )-60;
                            expYspd[l] = GetRand( 120 )-60;
                            expcnt[l] = GetRand( 150 )+105;
                        }
                        break;
                    }
                    tama[j] = 0;
                    teki[i] = 0;
                }
            }

            tekicnt[i]++;        //敵弾発射処理
            if( tekicnt[i] > tekicntmx[i] ){
                tekicnt[i] = 0;
                for( int t=0 ; t<ttamakazu ; t++ ){
                    if( ttama[t] > 0 ) continue;
                    ttama[t] = 1;
                    ttamaX[t] = tekiX[i]+100;
                    ttamaY[t] = tekiY[i]+100;
                    int tempX , tempY;
                    if( X*10-ttamaX[t] > 0 ) tempX = 1; else tempX = -1;
                    if( Y*10-ttamaY[t] > 0 ) tempY = 1; else tempY = -1;
                    ttamaXspd[t] = ( GetRand( 30 )+5+level/5 )*tempX;
                    ttamaYspd[t] = ( GetRand( 30 )+5+level/5 )*tempY;
                    break;
                }
            }

            //自機と敵機の当たり判定
            if( ( X-tekiX[i]/10 > -14 )&&( X-tekiX[i]/10 < 46 )&&( Y-tekiY[i]/10 > -14 )&&( Y-tekiY[i]/10 < 46 )&&( miss == 0 ) ) miss = 1;

            DrawGraph( tekiX[i]/10 , tekiY[i]/10 , Ghandle[1] , TRUE );
        }

        //敵爆発処理
        for( int i=0 ; i<expkazu ; i++ ){
            if( exp[i] < 1 ) continue;
            expX[i] = expX[i]+expXspd[i];
            expY[i] = expY[i]+expYspd[i];
            expcnt[i] -= 5;
            if( expcnt[i] < 1 ){
                exp[i] = 0;
                expcnt[i] = 0;
                continue;
            }

            SetDrawBlendMode( DX_BLENDMODE_ALPHA , expcnt[i] );
            DrawGraph( expX[i]/10 , expY[i]/10 , Ghandle[4] , TRUE );
        }
        SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 );

        //敵弾処理
        for( int i=0 ; i<ttamakazu ; i++ ){
            if( ttama[i] < 1 ) continue;
            ttamaX[i] = ttamaX[i]+ttamaXspd[i];
            ttamaY[i] = ttamaY[i]+ttamaYspd[i];
            if( ( ttamaX[i] < -120 )||( ttamaX[i] > wX*10 )||( ttamaY[i] < -120 )||( ttamaY[i] > wY*10 ) ){
                ttama[i] = 0;
                continue;
            }
            //自機と敵弾の当たり判定
            if( ( ttamaX[i]/10-X > -16 )&&( ttamaX[i]/10-X < 4 )&&( ttamaY[i]/10-Y > -16 )&&( ttamaY[i]/10-Y < 4 )&&( miss == 0 ) ) miss = 1;

            DrawGraph( ttamaX[i]/10 , ttamaY[i]/10 , Ghandle[3] , TRUE );
        }

        //星描画
        for( int i=0 ; i<starkazu ; i++ ){
            starY[i] = starY[i] + starspd[i];
            if( starY[i] > wY*10 ){
                starX[i] = GetRand( wX*10-80 );
                starY[i] = -80;
            }
            DrawGraph( starX[i]/10 , starY[i]/10 , Ghandlestar[GetRand(4)] , TRUE );
        }

        //スコア表示
        int col = GetColor( 255 , 255 , 255 );
        DrawFormatString( 5 , 5 , col , "SCORE %d" , score );
        DrawFormatString( 540 , 5 , col , "LEVEL %d" , level );

        ScreenFlip();

        //右クリックで終了
        if( ( GetMouseInput() & MOUSE_INPUT_RIGHT ) !=0 ) gameF = 1;

        //×ボタンで終了
        if( GetWindowUserCloseFlag( TRUE ) ) exit( 0 );

    }

}

void Allinit(){        //スタート時初期化///////////////////////////

    PlaySoundMem( Shandlebgm , DX_PLAYTYPE_LOOP );

    for( int i=0 ; i<tamakazu ; i++ ) tama[i] = 0;
    for( int i=0 ; i<tekikazu ; i++ ) teki[i] = 0;
    for( int i=0 ; i<ttamakazu ; i++ ) ttama[i] = 0;
    for( int i=0 ; i<expkazu ; i++ ) exp[i] = 0;

    level = 0;
    X = wX/2;
    Y = wY-64;
    tamacnt = 0;
    tamaNo = 0;
    score = 0;
    miss = 0;
    
    for( int i=0 ; i<starkazu ; i++ ){
        starX[i] = GetRand( wX*10-80 );
        starY[i] = GetRand( wY*10 );
        starspd[i] = GetRand( 20 )+20;
    }

}

void Tekiinit(){        //編隊ごとの初期化///////////////////////////

    level++;
    for( int i=0 ; i<tekikazu ; i++ ){
        teki[i] = 1;
        tekiX[i] = GetRand( wX*10-640 )+320;
        tekiY[i] = -GetRand( 3200 );
        spd[i] = GetRand( 50 )+20+level/6;
        tekicnt[i] = 0;
        tekicntmx[i] = GetRand( 30 )+25-( level/4 );
        if( tekicntmx[i] < 10 ) tekicntmx[i] = 10;
    }

}

void Load(){        //ロード

    FILE *fp;
    errno_t error;
    if( ( error = fopen_s( &fp , "highscore.sv" , "r" )) !=0 ){
        DrawString( 50 , 50 , "セーブデータがありません" , GetColor( 255 , 255 , 0 ) );
        DrawString( 50 , 70 , "ファイルを作成します" , GetColor( 255 , 255 , 0 ) );
        ScreenFlip();
        WaitVSync( 60 );
    } else {
        fread( highscore , sizeof( int ) , 6 , fp );
        fclose( fp );
    }

}

void Save(){        //セーブ

    FILE *fp;
    errno_t error;
    if( ( error = fopen_s( &fp , "highscore.sv" , "w" )) !=0 ){
        DrawString( 50 , 50 , "セーブできません" , GetColor( 255 , 255 , 0 ) );
        ScreenFlip();
        WaitVSync( 60 );
    } else {
        fwrite( highscore , sizeof( int ) , 6 , fp );
        fclose( fp );
    }

}

void EndDivision(){        //終了処理
    DxLib_End();
}