ここまでの冒険を記録しますか?

長岡技大ソフトウェア開発サークルの開発ブログ

【ゲーム製作入門】C/C++でブロック崩しを作る⑦【DXライブラリ】

書き手:肥田野

プログラミング初心者向けに、簡単なゲームの制作を通してC/C++の基本を解説していきます。

当サークルに所属しない方でも参考にしていただければ幸いです。

この記事では前回の内容を理解していることを前提に進めていきます。

正直前回は詰め込みすぎたので、きちんと理解してもらえたのか不安もありますが、もう一度クラスについて考えてみましょう。

今回は配列を用いたクラスの管理と、当たり判定などの不具合を修正します。

それでは今回解説するコードです。

#include "DxLib.h"

class Block{
public:
    int blockX,blockY;
    int width,height;
    bool live;

    void View(){
        if(live){
            DrawBox(blockX,blockY,blockX+width,blockY+height,GetColor(255,255,255),TRUE);
        }
    }

    Block(int setX,int setY){
        blockX = setX;
        blockY = setY;
        width = 90;
        height = 20;
        live = true;
    }
};

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine, int nCmdShow ){
    ChangeWindowMode(TRUE);
    if( DxLib_Init() == -1 )return -1 ;
    SetDrawScreen(DX_SCREEN_BACK);
    
    int ballX = 320,ballY = 240,ballR = 10;//new
    int vecX = 0,vecY = 0;
    int px = 320,py = 400;

    Block* bl[20];//new
    for(int i=0;i<20;i++){
        bl[i] = new Block(100+110*(i%4),40+40*(i/4));//new
    }
    
    
    while(ProcessMessage() != -1){
        int startTime = GetNowCount();
        ScreenFlip();
        ClearDrawScreen();
        
        DrawCircle(ballX,ballY,ballR,GetColor(255,255,255),TRUE);
        DrawBox(px,py,px+60,py+20,GetColor(255,255,255),TRUE);

        for(int i=0;i<20;i++){
            bl[i]->View();//new
        }

        if(vecX != 0 && vecX != 0){
            if(ballX>640)vecX = -1;
            if(ballX<0)vecX = 1;
            if(ballY<0)vecY = 1;
            if(ballX>px && ballX<px+60 && ballY+ballR > py && ballY < py)vecY = -1;
            if(ballY>480){
                ballX = 320;
                ballY = 240;
                vecX = 0;
                vecY = 0;
            }
            //new
            for(int i=0;i<20;i++){
                if(bl[i]->live){
                    if(ballX > bl[i]->blockX && ballX < bl[i]->blockX+bl[i]->width && 
                        ballY+ballR > bl[i]->blockY && ballY+ballR < bl[i]->blockY+bl[i]->height){//上
                            bl[i]->live = false;
                            vecY *= -1;
                    }
                    if(ballX > bl[i]->blockX && ballX < bl[i]->blockX+bl[i]->width && 
                        ballY-ballR > bl[i]->blockY && ballY-ballR < bl[i]->blockY+bl[i]->height){//下
                            bl[i]->live = false;
                            vecY *= -1;
                    }
                    if(ballX+ballR > bl[i]->blockX && ballX+ballR < bl[i]->blockX+bl[i]->width && 
                        ballY > bl[i]->blockY && ballY < bl[i]->blockY+bl[i]->height){//左
                            bl[i]->live = false;
                            vecX *= -1;
                    }
                    if(ballX-ballR > bl[i]->blockX && ballX-ballR < bl[i]->blockX+bl[i]->width && 
                        ballY > bl[i]->blockY && ballY < bl[i]->blockY+bl[i]->height){//右
                            bl[i]->live = false;
                            vecX *= -1;
                    }
                }
            }
            //newここまで
        }else{
            DrawFormatString(260,260,GetColor(255,255,255),"PUSH SPACE");
            if(CheckHitKey(KEY_INPUT_SPACE)){
                vecX = 1;
                vecY = 1;
                for(int i=0;i<20;i++)bl[i]->live = true;
            }
        }
        for(int i=0;i<20;i++){//new
            if(bl[i]->live)break;
            if(i == 19){
                DrawFormatString(260,120,GetColor(255,255,255),"GAME CLEAR!");
                ballX = 320;
                ballY = 240;
                vecX = 0;
                vecY = 0;
            }
        }

        ballX += 5*vecX;
        ballY += 5*vecY;

        if(CheckHitKey(KEY_INPUT_RIGHT) == 1 && px+60 < 640){//new
            px += 10;
        }
        if(CheckHitKey(KEY_INPUT_LEFT) == 1 && px > 0){
            px -= 10;
        }
        
        if(CheckHitKey(KEY_INPUT_ESCAPE) == 1)break;
        int endTime = GetNowCount();
        WaitTimer((1000/60)-(endTime-startTime));
    }

    for(int i=0;i<20;i++){
        delete bl[i];
    }
    
    DxLib_End() ;
    return 0 ;
}

newコメントを中心に解説します。

int ballR = 10;

当たり判定をより厳密にさせるため、球の半径を変数として扱うことにしました。

Block* bl[20];

前回はbl0やbl1といった変数でブロックを識別していましたが、これも変数として扱うことでより管理がしやすくなります。

実体を持たせるときにも、前回は一つ一つ実行させていたものがfor文で一気にできるようになっています。

bl[i] = new Block(100+110(i%4),40+40(i/4));

Block()の引数には、そのブロックを配置したい場所の座標を入力させることにしていました。

今回は横に4つ、縦に5段並べたいのですが、それをたった一つ(正確にはXとYで一つずつ)の計算式で表したのがこれです。

まずX座標ですが、左から数えたら0番目、1番目、2番目、3番目のどれかになりますよね。

その場合、各ブロックの番号を4で割った余り(i%4)が、X座標になるんです。

ちなみに「100+110*(i%4)」の100は一番左端のスペース、110はブロックのX座標から右隣のブロックのX座標までの距離です。この数字を調節すると、ブロックの間隔が変わります。

反対にY座標は、ブロックの番号を4で割った商(i/4)で求められます。

bl[i]->View();

前回まで存在したliveControl()は、当たり判定の仕様変更をした影響で消滅しました……

if(ballX > bl[i]->blockX && ballX < bl[i]->blockX+bl[i]->width && ballY+ballR > bl[i]->blockY && ballY+ballR < bl[i]->blockY+bl[i]->height){//上

おそらく過去最長になるif文の条件式ですが、やっていることはシンプルです。

今まではブロックとボールの接触を一つの条件式で判定していましたが、今度はブロックの上下左右に対応できるように4つに増やしました。

この式は上から来るボールを弾くための条件なので、

①球の中心のX座標がブロックの左端から右端の間にあること

②球の中心のY座標+半径が、ブロックの上端より下にあること

③球の中心のY座標+半径が、ブロックの下端より上にあること

この三つの条件がそろうのが、上から球が当たった時ということになります。

あとは当たったブロックのliveをfalseにして、上下か左右かでvecXまたはvecYを-1倍します。

for(int i=0;i<20;i++){//new
if(bl[i]->live)break;
if(i == 19){

ちょっと変わった処理をしていますね。ここではすべてのブロックを壊したかどうかをチェックしています。

ブロックの生死を順番に調べていって、もし生きているブロックがあればbreak;、そこでfor文を抜けてしまいます。

iが最後(19)まで達したなら全てのブロックが壊されているということですから、ゲームクリアの処理をしています。

for文はただ処理を繰り返すだけでなく、こういう変則的な使い方もできるんですよ。

if(CheckHitKey(KEY_INPUT_RIGHT) == 1 && px+60 < 640){

そういえばプレイヤーが画面端を突き抜けてしまっていたので、画面端に到達したらそれ以上は進めないように条件を付けたしました。

さて、ブロックのオブジェクトを配列に入れることで、より扱いやすくすることができました。

ついでに細かい不具合も直して、より完成度が高くなりましたね。

次回でいよいよラストにします。

これまでの知識を総動員して、ブロック崩しとしてのゲームを完成させましょう!