C言語ゲームプログラミング  ブロック崩し編 第4回




2014年10月より個人の方を対象に、Study C無料提供を開始しました。
C言語を勉強中の方は、学習・教育に最適なC言語インタープリタのStudy Cを使ってみてください(個人の方は無料です)。
大学・高専・高校などの教育機関での採用実績も多数あるロングセラー商品Study Cが、個人向けに無料提供を始めました。
インタープリタの手軽さに加え、ゲームや3Dタートルグラフィックで楽しく勉強したりと、C言語の学習を協力にサポートします。
ブロック崩しゲーム 3Dツリー クリスマスツリー
また、このようなボタンの用意されているページでは、掲載しているプログラムをStudy Cに直接ロードし実行したりすることができます。
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する
Study C無料利用についての詳細は、このページを参照してください。



前回のブロック崩し編 第4回での問題点を修正したいところですが、main()関数が肥大化して改造しずらくなっています。 今回は、ちょっと一休みしてプログラムが分かり易くなるように関数を分割していきます。

前回の問題点、ブロックを突き抜けるボール
ボールがブロックを突き抜ける

1.ボール関係の関数

まず、ボールの描画処理をdraw_ball()という関数に分割します。 単にボールのビットマップを描画するだけではなく、前に描画したボールがあれば消去します。

void    draw_ball()
{
        //ボールの消去・描画処理
        if(bold_xx != b_xx || bold_yy != b_yy){
                //前に描画したボールを消去
                if(bold_xx != -1 && bold_yy != -1){
                        gl_fillrect(bold_xx, bold_yy,
                                bold_xx + 16, bold_yy + 16, RGB(0, 0, 0));
                }
                //ボールの旧位置(bold_xx、bold_yy)を更新
                bold_xx = b_xx;
                bold_yy = b_yy;
                //新しい位置にボールを描画
                gl_drawbitmap(hbmp_ball, b_xx, b_yy, 16, 16, 0, 0);
        }
}

次にボールの移動処理をmove_ball()という関数に分割します。 ラケットとの当たり判定も行いボールをロストした場合は、0を返しそれ以外の時は-1を返す関数とします。

int     move_ball()
{
        char    buff[100];

        //横方向の処理
        b_xx += bd_xx;
        if(b_xx >= GAME_SX-16 || b_xx <= 0){
                //壁に当たったら効果音を鳴らす
                gl_playsound("c:\\windows\\media\\ding.wav");
                bd_xx = -bd_xx;
        }
        if(b_xx < 0){
                b_xx = 0;
        }
        if(b_xx > GAME_SX-16){
                b_xx = GAME_SX-16;
        }

        //縦方向の処理
        b_yy += bd_yy;
        if(b_yy >= RACKET_PY-16 || b_yy <= 0){
                bd_yy = -bd_yy;
        }
        if(b_yy <= 0){
                gl_playsound("c:\\windows\\media\\ding.wav");
                b_yy = 0;
        }

        //ボールがウィンドウ下部に来たときの処理
        if(b_yy > RACKET_PY-16){
                //if文にはボールのサイズを加味した方が良いですが
                //ここでは省略します。
                if(b_xx < r_xx - 16 || b_xx > r_xx + 80 + 16){
                        return 0;
                }
                MessageBeep(0);
                b_yy = RACKET_PY-16;
                //スコアを描画する

                score++;
                sprintf(buff, "%05d", score);
                gl_textout("MSゴシック", 90, 0, 0, GAME_SX+20, 50, buff,
                        RGB(255,255,255), RGB(0,0,0), 0);
        }
        return -1;
}

2.ラケット関係の関数

ボールの処理に続きラケットについても同じように改造していきます。

ラケットの描画処理をdraw_racket()という関数に分割します。 これも単にラケットを描画するだけではなく、前に描画したラケットがあれば消去します。

void    draw_racket()
{
        //ラケットの消去・描画処理
        if(rold_xx != r_xx){
                //前に描画したラケットを消去
                if(rold_xx != -1){
                        gl_fillrect(rold_xx, RACKET_PY, rold_xx + 80, RACKET_PY + 10, RGB(0, 0, 0));
                }
                //ラケットの旧位置(rold_xx)を更新
                rold_xx = r_xx;
                //新しい位置にラケットを描画
                gl_fillrect(r_xx, RACKET_PY, r_xx + 80, RACKET_PY + 10, RGB(255, 255, 255));
        }
}

ラケットのの移動処理はmove_racket()という関数に分割します。 マウスの位置に応じてラケットを移動させます。

void    move_racket()
{
        long    xx, yy;

        //ラケットをマウスで動かす
        gl_getmousepos(&xx, &yy);
        r_xx = xx;
        if(r_xx < 0)
                r_xx = 0;
        if(r_xx > GAME_SX - 80)
                r_xx = GAME_SX - 80;
}

3.グローバル変数、プロトタイプ宣言

main()内のローカル変数のままで、パラメータで各関数に渡す方が良いと思いますが変更量が少なくて済むようにグローバル変数にしてしまいました。

//ボール用の変数
HBITMAP hbmp_ball;

int     b_xx = GAME_SX/2, bd_xx = 10,
        b_yy = GAME_SY/3, bd_yy = 10;
int     bold_xx = -1, bold_yy = -1;

//ラケット用の変数
int     r_xx = 320, rold_xx = -1;

int     score = 0;

この他、前回のプログラムと同様にblock配列が必要となります。

作成した関数のプロトタイプ宣言です。 先に述べたように手抜き改造なので、パラメータ部は全部「(void)」になっています。

void    draw_ball(void);
int     move_ball(void);
void    draw_racket(void);
void    move_racket(void);

4.プログラム全体

プログラム全体は以下のようになります。

#include <graph.h>

#define GAME_SX         900
#define GAME_SY         700
#define BLOCK_XX        15
#define BLOCK_YY        43
#define RACKET_PY       670


int     block[BLOCK_YY][BLOCK_XX] = {
                {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
        };


//ボール用の変数
HBITMAP hbmp_ball;

int     b_xx = GAME_SX/2, bd_xx = 10,
        b_yy = GAME_SY/3, bd_yy = 10;
int     bold_xx = -1, bold_yy = -1;

//ラケット用の変数
int     r_xx = 320, rold_xx = -1;

int     score = 0;


void    draw_ball(void);
int     move_ball(void);
void    draw_racket(void);
void    move_racket(void);


main()
{
        HBITMAP hbmp_block;

        unsigned long
                tick;
        int     i1, i2;

        hbmp_ball = gl_loadbitmap("ball.bmp");
        hbmp_block = gl_loadbitmap("block1.bmp");
        if(hbmp_ball == NULL || hbmp_block == NULL){
                printf("ビットマップファイルが見つかりません.\n");
                exit(0);
        }
        gl_openwin(10, 10, 980, 700, 0);

        //縦の黄色線を描画
        gl_fillrect(GAME_SX, 0, GAME_SX+5, GAME_SY, RGB(200, 200, 0));

        //スコアの初期値を描画
        gl_textout("MSゴシック", 90, 0, 0, GAME_SX+20, 30, "SCORE", RGB(255,255,255), RGB(0,0,0), 0);
        gl_textout("MSゴシック", 90, 0, 0, GAME_SX+20, 50, "00000", RGB(255,255,255), RGB(0,0,0), 0);

        for(i2 = 0; i2 < BLOCK_YY; i2++)
        {
                for(i1 = 0; i1 < BLOCK_XX; i1++)
                {
                        if (block[i2][i1] != 0)
                        {
                                gl_drawbitmap(hbmp_block, i1 * 60, i2 * 20, 60, 20, 0, 0);
                        }
                }
        }

        tick = GetTickCount();
        for( ; ; ){
                if(GetTickCount() >= tick && GetTickCount() < tick + 50)
                        continue;
                tick = GetTickCount();

                //ESCキーを押したらプログラムが終了する処理も追加
                if(gl_getkeystate(VK_ESCAPE) < 0)
                        break;

                move_racket();
                if (move_ball() == 0)
                {
                        break;
                }

                draw_ball();
                draw_racket();

                gl_refresh();
        }
}


void    draw_ball()
{
        //ボールの消去・描画処理
        if(bold_xx != b_xx || bold_yy != b_yy){
                //前に描画したボールを消去
                if(bold_xx != -1 && bold_yy != -1){
                        gl_fillrect(bold_xx, bold_yy,
                                bold_xx + 16, bold_yy + 16, RGB(0, 0, 0));
                }
                //ボールの旧位置(bold_xx、bold_yy)を更新
                bold_xx = b_xx;
                bold_yy = b_yy;
                //新しい位置にボールを描画
                gl_drawbitmap(hbmp_ball, b_xx, b_yy, 16, 16, 0, 0);
        }
}


int     move_ball()
{
        char    buff[100];

        //横方向の処理
        b_xx += bd_xx;
        if(b_xx >= GAME_SX-16 || b_xx <= 0){
                //壁に当たったら効果音を鳴らす
                gl_playsound("c:\\windows\\media\\ding.wav");
                bd_xx = -bd_xx;
        }
        if(b_xx < 0){
                b_xx = 0;
        }
        if(b_xx > GAME_SX-16){
                b_xx = GAME_SX-16;
        }

        //縦方向の処理
        b_yy += bd_yy;
        if(b_yy >= RACKET_PY-16 || b_yy <= 0){
                bd_yy = -bd_yy;
        }
        if(b_yy <= 0){
                gl_playsound("c:\\windows\\media\\ding.wav");
                b_yy = 0;
        }

        //ボールがウィンドウ下部に来たときの処理
        if(b_yy > RACKET_PY-16){
                //if文にはボールのサイズを加味した方が良いですが
                //ここでは省略します。
                if(b_xx < r_xx - 16 || b_xx > r_xx + 80 + 16){
                        return 0;
                }
                MessageBeep(0);
                b_yy = RACKET_PY-16;
                //スコアを描画する

                score++;
                sprintf(buff, "%05d", score);
                gl_textout("MSゴシック", 90, 0, 0, GAME_SX+20, 50, buff,
                        RGB(255,255,255), RGB(0,0,0), 0);
        }
        return -1;
}


void    draw_racket()
{
        //ラケットの消去・描画処理
        if(rold_xx != r_xx){
                //前に描画したラケットを消去
                if(rold_xx != -1){
                        gl_fillrect(rold_xx, RACKET_PY, rold_xx + 80, RACKET_PY + 10, RGB(0, 0, 0));
                }
                //ラケットの旧位置(rold_xx)を更新
                rold_xx = r_xx;
                //新しい位置にラケットを描画
                gl_fillrect(r_xx, RACKET_PY, r_xx + 80, RACKET_PY + 10, RGB(255, 255, 255));
        }
}


void    move_racket()
{
        long    xx, yy;

        //ラケットをマウスで動かす
        gl_getmousepos(&xx, &yy);
        r_xx = xx;
        if(r_xx < 0)
                r_xx = 0;
        if(r_xx > GAME_SX - 80)
                r_xx = GAME_SX - 80;
}