←3DSのHBL用アプリ開発 トップへ

ブロック崩しを作る

簡単なゲームを作ります。

最終更新:2017/10/12

初版:2017/10/07




はじめに


前回の図形を描画するライブラリを使って、簡単なゲームを作ります。
図形描画ライブラリのバージョン1.1をダウンロードしておいてください。
Sample

ソースコード


//ブロック崩し(by InoueSoftware)

#include <3ds.h>
#include <stdio.h>
#include <string.h>

#include "draw.h"

#define FieldSX 400 //フィールドの幅
#define FieldSY 240 //フィールドの高さ
#define BlockSX 50 //ブロックの幅
#define BlockSY 10 //ブロックの高さ
#define BlockMargin 3 //ブロックの余白
#define BlockMarginX 10 //左端のブロックの余白(X)
#define BlockMarginY 40 //左端のブロックの余白(Y)
#define BarSX 100 //跳ね返すバーの幅
#define BarSY 10 //跳ね返すバーの高さ
#define BarMarginY 20 //跳ね返すバーの下からの余白(Y)
#define BallSize 10 //玉のサイズ
#define BallDefaultSpeed 2  //ボールの速さの初期値
unsigned char BallSpeed = BallDefaultSpeed; //ボールの速さ
int BarPosX = 80; //バー位置
int BallPosX = (FieldSX/2), BallPosY = (FieldSY/2); //ボール位置
char BallDir = 0; //ボールの移動する向き (0=右上、1=右下、2=左上、3=左下)
bool Blocks[6][7] = { { 1,1,1,1,1,1,1 },
                      { 1,1,1,1,1,1,1 },
                      { 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 } };//ブロックの存在フラグ
unsigned char BlockColor[6][3] = { { 255,   0,   0},
                                   { 255, 127,   0},
                                   {   0,   0,   0},
                                   { 255, 255,   0},
                                   {   0, 255, 255},
                                   {   0, 127, 255} };//ブロックの色
touchPosition tp;

void DrawBar(void);
void MoveBall(void);
void DrawBall(void);
unsigned int CountBall(void);
void OnTouch(u16 x,u16 y);

int main(int argc, char **argv)
{
    gfxInitDefault();
    consoleInit(GFX_BOTTOM, NULL);
    gfxSetDoubleBuffering(GFX_TOP, true);
    printf("Blockout");
    // Main loop
    while (aptMainLoop())
    {
        //キー関連読み取り
        u8* fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL);
        SelectFrame(fb);//フレームへのポインタを設定
        hidScanInput();
        u32 kDown = hidKeysDown();
        u32 kUp = hidKeysUp();
        u32 kHeld = hidKeysHeld();
        hidTouchRead(&tp);
        if (kHeld & KEY_TOUCH)
            OnTouch(tp.px, tp.py);

        SetBrushColor(0, 0, 0);
        DrawRect(0, 0, FieldSX, FieldSY);//画面クリア
        MoveBall();
        printf("\x1b[1;0HBallDir:%d", BallDir);
        printf("\x1b[2;0HBalls:%03d", CountBall());
        //ブロックを描画
        int i1, i2;
        for (i1 = 0; i1 < (sizeof(Blocks) / sizeof(Blocks[0])); i1++) {
            for (i2 = 0; i2 < (sizeof(Blocks[0]) / sizeof(Blocks[0][0])); i2++) {
                if (Blocks[i1][i2]) {
                    SetPenColor(127, 127, 127);//枠の色
                    SetBrushColor(BlockColor[i1][0], BlockColor[i1][1], BlockColor[i1][2]);//中の色
                    DrawRect(//中身
                        BlockMarginX + BlockMargin + i2*(BlockSX + BlockMargin),
                        BlockMarginY + BlockMargin + i1*(BlockSY + BlockMargin),
                        BlockMarginX + BlockMargin + i2*(BlockSX + BlockMargin) + BlockSX,
                        BlockMarginY + BlockMargin + i1*(BlockSY + BlockMargin) + BlockSY
                        );
                    DrawFrame(//枠
                        BlockMarginX + BlockMargin + i2*(BlockSX + BlockMargin),
                        BlockMarginY + BlockMargin + i1*(BlockSY + BlockMargin),
                        BlockMarginX + BlockMargin + i2*(BlockSX + BlockMargin) + BlockSX,
                        BlockMarginY + BlockMargin + i1*(BlockSY + BlockMargin) + BlockSY
                        );
                }
            }
        }

        DrawBall();
        DrawBar();
        
        if (kDown & KEY_START) break;

        gfxFlushBuffers();
        gfxSwapBuffers();
        gspWaitForVBlank();
    }

    gfxExit();
    return 0;
}

//バーを描画
void DrawBar() {
    if (BarPosX > (FieldSX - BarSX))
        BarPosX = (FieldSX - BarSX) - 1;//はみ出ていたら修正
    if (BarPosX < 0)
        BarPosX = 0;//はみ出ていたら修正
    SetPenColor(10, 10, 127);//バーの色
    SetBrushColor(25, 25, 210);//バーの色
    DrawRect(//中身
        BarPosX,
        FieldSY - BarMarginY,
        BarPosX + BarSX,
        FieldSY - BarMarginY + BarSY
        );
    DrawFrame(//枠
        BarPosX,
        FieldSY - BarMarginY,
        BarPosX + BarSX,
        FieldSY - BarMarginY + BarSY
        );
}

//玉を描画
void DrawBall() {
    SetPenColor(142, 142, 142);//玉の色
    SetBrushColor(220, 220, 220);//玉の色
    DrawCircle(BallPosX, BallPosY, BallPosX + BallSize, BallPosY + BallSize, 1);
}

//玉の移動
void MoveBall() {
    //BallDir :ボールの移動する向き (0=右上、1=右下、2=左上、3=左下)

    if ((BallDir == 0) || (BallDir == 1)) {
        BallPosX += BallSpeed;
    }else if ((BallDir == 2) || (BallDir == 3)) {
        BallPosX -= BallSpeed;
    }
    if ((BallDir == 1) || (BallDir == 3)) {
        BallPosY += BallSpeed;
    }else if ((BallDir == 0) || (BallDir == 2)) {
        BallPosY -= BallSpeed;
    }

    //当たり判定
    //-画面外に出たときの判定
    if (BallPosX <= 0) {
        BallDir = BallDir -2;
        BallPosX = 0;
    }else if (BallPosX >= FieldSX - BallSize - 1) {
        BallDir = BallDir + 2;
        BallPosX = FieldSX - BallSize - 1;
    }
    if (BallPosY <= 0) {
        BallDir = (int)BallDir / (int)2 * 2 + 1;
        BallPosY = 0;
    }else if (BallPosY >= FieldSY - BallSize - 1){
        BallDir = (int)BallDir / (int)2 * 2;
        BallPosY = FieldSY - BallSize - 1;
        }

    //-バーに当たったとき、必ず上に反射する
    if (((BallPosX + BallSize) >= BarPosX) && (BallPosX <= (BarPosX + BarSX / 2)) && ((BallPosY + BallSize) >= (FieldSY - BarMarginY)) && (BallPosY <= (FieldSY - BarMarginY + BarSY)))
        BallDir = 2;
    else if (((BallPosX + BallSize) >= (BarPosX + BarSX / 2)) && (BallPosX <= (BarPosX + BarSX)) && ((BallPosY + BallSize) >= (FieldSY - BarMarginY)) && (BallPosY <= (FieldSY - BarMarginY + BarSY)))
        BallDir = 0;

    //-ブロックに当たったか判定
    for (int i1 = 0; i1 < (sizeof(Blocks) / sizeof(Blocks[0])); i1++) {
        for (int i2 = 0; i2 < (sizeof(Blocks[0]) / sizeof(Blocks[0][0])); i2++) {
            if (Blocks[i1][i2]) {
                if (((BallPosX + BallSize / 2) >= (BlockMarginX + BlockMargin + i2*(BlockSX + BlockMargin))) &&
                    ((BallPosX + BallSize / 2) <= (BlockMarginX + BlockMargin + i2*(BlockSX + BlockMargin) + BlockSX)) &&
                    ((BallPosY + BallSize / 2) >= (BlockMarginY + BlockMargin + i1*(BlockSY + BlockMargin))) &&
                    ((BallPosY + BallSize / 2) <= (BlockMarginY + BlockMargin + i1*(BlockSY + BlockMargin) + BlockSY))) {
                    Blocks[i1][i2] = 0;//当たった場合は、そのブロックのフラグを0にする(非表示)
                    if ((CountBall() % 10) == 0) BallSpeed++;
                    BallDir = 3 - BallDir;
                }

            }
        }
    }
}
//タッチ処理
//OnTouch(u16 X座標, u16 Y座標)
void OnTouch(u16 x, u16 y) {
    if (x < FieldSX - BarSX) {
        BarPosX = (int)((double)x * (320.0 / (double)(FieldSX - BarSX - 1)) - 15);
    }
}

//ボールの数を取得
unsigned int CountBall() {
    unsigned int cnt = 0;
    int i1, i2;
    for (i1 = 0; i1 < (sizeof(Blocks) / sizeof(Blocks[0])); i1++) {
        for (i2 = 0; i2 < (sizeof(Blocks[0]) / sizeof(Blocks[0][0])); i2++) {
            if (Blocks[i1][i2]) {
                cnt++;
            }
        }
    }
    return cnt;
}


●使い方
・実行するといきなりゲームが始まります。
・バーは下画面をタッチすることで動かすことができます。
・地面にあたっても無視されます。

●問題点
・ボールが下にあたっても、ゲームオーバーにならない。
・ブロックをすべて崩しても終わりにならない。
・いきなりゲームが始まる。

解説 - ボール


ボールを管理する変数/関数の解説です。
BallSpeed
ボールの速さを保存しておく変数です。
MoveBall関数でブロックの残りが10で割り切れた場合に1加算していきます。

BallDir
ボールの向きを保存しておく変数です。
扱いやすいように、0で右上、1で右下、2で左上、3で左下にボールが移動します。
(BallDir xor 2)で左右にスイッチングできます。
(BallDir and 2)で上向きにできます。
((BallDir and 2) + 1)で下向きにできます。
Sample
MoveBall関数で変更されます。

BallPosXBallPosY
ボールの位置を保存しておく変数です。

MoveBall関数
あたり判定をします。
当たらなかった場合は、BallDir変数の方向に進みます。
●バーのあたり判定
Sample
飛んできた側のバー(右から飛んできた場合は右半分)にあたると、左右が逆になります。
飛んできた側の反対のバー(右から飛んできた場合は左半分)にあたると、そのまま反射して進んでいきます。
※角度で判定をするとより楽しくなるかもしれません。

●ブロックのあたり判定
当たると単純に戻ってきます。
※角度で判定をするとより楽しくなるかもしれません。

DrawBall関数
ボールを描画します。
直径はBallSize定数で変更できます。



解説 - ブロック


Blocks
ブロックの存在フラグです。
1で存在、0で崩されたとなります。

BlockColor
ブロックの色です。
段ごとで設定されます。



解説 - タッチ処理


OnTouch関数
タッチされた位置に応じてバーを動かします。
下画面と上画面では画面の幅が違いますので、比率を使います。



終わりに


今回のサンプルは簡単な実装でしたので、出来はあまり良くありません。
あたり判定も簡単に作っているので、細かいところを丁寧に実装すると本格的なゲームが作れるかもしれません。




[前へ] [次へ]

(C)2017 InoueSoftware / 無断転載禁止