★数当てゲーム★


最初は、C言語編で作った「数当てゲーム」を作ってみます。

コンソールアプリケーションだと簡単なプログラムで出来ましたが、画像を扱うとどうなるか考えながら作ります。

前回と被る部分も非常に多いですが、出来れば新しくプロジェクトフォルダをコピーして作りましょう。


画像とフォントの準備から背景の転送まで


今回使う画像は、前回に引き続きトランプ画像です。

トランプ画像をプロジェクトフォルダ内の「Resource\\Textureフォルダ」へコピーしておいてください。

次に「GameBase.h」を開き、定数を宣言します。

//-----------------------------------------------------------------------------
//ゲーム中で使用するテクスチャ、BGM、SE、フォントのパス付ファイル名を書きます。
//-----------------------------------------------------------------------------
namespace KeyString
{
    const std::string TEXTURE_TRUMP = "Resource\\Texture\\Trump.png";

    const std::string FONT_GOTHIC = "MS ゴシック";
}

「SceneGame.cpp」を開いてください。

画像とフォントの追加を「Start関数」に追加します。

//=============================================================================
// シーンの実行時に1度だけ呼び出される開始処理関数
//=============================================================================
void SceneGame::Start()
{
    m_pEngine->AddTexture(TEXTURE_TRUMP);
    m_pEngine->AddFont(FONT_GOTHIC, 20);
}

「Exit関数」に解放コードも追加します。

//=============================================================================
// シーンの終了時に呼び出される終了処理関数
//=============================================================================
void SceneGame::Exit()
{
    m_pEngine->ReleaseFont(FONT_GOTHIC);
    m_pEngine->ReleaseTexture(TEXTURE_TRUMP);
}

「Draw関数」に背景画像の転送を書きましょう。

背景は前回同様トランプ画像の左下にあるものを使います。

//=============================================================================
// シーンの実行時に繰り返し呼び出される描画処理関数
//=============================================================================
void SceneGame::Draw()
{
    //背景の転送
    SetRect(&m_sour, 0, 1568, 640, 2048);
    SetRect(&m_dest, 0, 0, 640, 480);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);
}

ここまでの実行結果を確認しておいてください。

<実行結果 クライアント領域のみ>


数の選択


コンソールアプリケーションと同じように、1から10までの数をランダムで用意してそれを当てるゲームにするため変数を用意します。

「SceneGame.h」を開き、privateに変数を追加します。

private:
    
    int m_answer;

    int m_input;
};

変数m_answerには乱数をセットし、変数m_inputにはこちらが選択した数を入れるようにします。

「SceneGame.cpp」を開き「Start関数」で乱数をセットしましょう。

//=============================================================================
// シーンの実行時に1度だけ呼び出される開始処理関数
//=============================================================================
void SceneGame::Start()
{
    m_pEngine->AddTexture(TEXTURE_TRUMP);
    m_pEngine->AddFont(FONT_GOTHIC, 20);

    m_answer = rand() % 10 + 1;
}

さて、コンソールアプリケーションではscanf関数が使えましたが、このシステムではscanf関数のような入力機能はありません。

今回はトランプの画像を使って、マウスで数を選択する方法を採りましょう。

使う画像は↓の緑で囲んである画像です。

座標は画像に書き込んでありますので、転送元は

 左の座標   0

 上の座標 750

となります。

カードの幅は「40ピクセル」ですから10枚分で「400ピクセル」で、高さは「60ピクセル」になります。

最終的な転送元座標は、

 左の座標   0

 上の座標 750

 右の座標 400

 下の座標 810

となります。

転送先は画面の真ん中にしたいと思いますので、皆さんで計算して画像を描画させてください。









































解答です。

//=============================================================================
// シーンの実行時に繰り返し呼び出される描画処理関数
//=============================================================================
void SceneGame::Draw()
{
    //背景の転送
    SetRect(&m_sour, 0, 1568, 640, 2048);
    SetRect(&m_dest, 0, 0, 640, 480);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);
	
    //カードの転送
    SetRect(&m_sour, 0, 750, 400, 810);
    SetRect(&m_dest, 120, 210, 520, 270);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);
}

<実行結果 クライアント領域のみ>

転送先は何度も計算式を書きました。

 左の座標 (640 - 400 ) / 2 = 120

 上の座標 (480 - 60) / 2 = 210

 右の座標 120 + 400 = 520

 下の座標 210 + 60 = 270

このカードをマウスで選択する事で、1から10の数を選んだと言う事にします。


マウスを使う


マウスを使うため、POINT構造体を準備します。

「SceneGame.h」を開き、privateに構造体を追加します。

private:
    
    int m_answer;

    int m_input;

    POINT m_point;
};

「SceneGame.cpp」を開き「Update関数」に次のコードを書きます。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();
    }
}

マウスの左ボタンを押した時に、マウスの座標を取得するコードです。

ここで取得した座標が、きちんとカードの画像の領域内かどうかチェックします。

カードの転送座標を見れば、領域内かどうかのプログラムは書けます。

領域判定を参考にif文を作ってみてください。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();

        //ここにif文を追加
    }
}









































解答です。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();

        if (m_point.x >= 120 && m_point.x < 520 && m_point.y >= 210 && m_point.y < 270) {

        }
    }
}

これでトランプ画像の上でマウスが押された事が分かります。


マウス座標から数値を計算する


ここが、今回の「肝」と呼べる部分です。

自分が選択した数値を変数m_inputに格納し、m_answerと比較しなければなりません。

カードのAの部分でボタンを押したら「1」、2の部分であれば「2」・・・10の部分であれば「10」の数値をm_inputに格納したいです。

しかし、こちらが分かっているのはマウスのボタンを押した座標のみ・・・どうすればこのような計算が出来るのでしょうか?

まずは画像と座標の関係をじっくり見てみましょう。

カードの高さは皆同じなので、横方向の違いについて注目します。

カードの幅は「40ピクセル」で、転送先座標の左は「120」です。

上の図を見ると、一番左のAの横方向の座標は「120」から始まり「159」までの「40ピクセル」分になります。

比較しやすいように表にしてみましょう。

数字範囲
120 - 159
160 - 199
200 - 239
240 - 279
280 - 319
320 - 359
360 - 399
400 - 439
440 - 479
10480 - 519

何か法則が見えそうな気がしますので、範囲の始まりを0からにするため全体的に「120」を引いてみます。

数字範囲
0 - 39
40 - 79
80 - 119
120 - 159
160 - 199
200 - 239
240 - 279
280 - 319
320 - 359
10360 - 399

それぞれのカードの範囲の開始座標が「40」の倍数になりました。

※以前、範囲の右側は「未満」にすると書きました。

例えばAの場合「0から40以下」とすると2のカードと範囲が一部被るので「0から40未満」とします。


本題に戻ります。

この「範囲」は「40ピクセル」ごとに区切られています。

そこで「範囲」を「40」で割るとどうなるでしょうか。

※小数点以下は切り捨てで計算してください。









































解答です。

数字計算結果
0
1
2
3
4
5
6
7
8
109

Aの場合「0から39」までの数値を「40」で割ると、商は「0」です。

2の場合「40から79」までの数値を「40」で割ると、商は「1」です。

このように計算した結果Aから10までのカードを選択して「0」から「9」の数値を得る事が出来ました。

これに1を加える事で、完全に同じ数値になります。

ここまでの式を書いてみると、

 (point.x - 120) / 40 + 1

となります。

これを変数m_inputに格納しますので「Update関数」を変更しましょう。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();

        if (m_point.x >= 120 && m_point.x < 520 && m_point.y >= 210 && m_point.y < 270) {

            m_input = (m_point.x - 120) / 40 + 1;
        }
    }
}

判定と結果表示


後はm_answerとm_inputが同じかどうか調べれば良いのですが、どのように結果を表示するかを考えなければなりません。

今回は同じであった場合のみ「BINGO!」と文字を表示するようにしてみます。

文字表示は「Draw関数」で行う必要がありますが、判定は「Update関数」で行います。

そのため、フラグを1つ追加して判定結果を入れる事にします。

「SceneGame.h」を開き、privateにbool型のフラグを追加しましょう。

private:
    
    int m_answer;

    int m_input;

    POINT m_point;

    bool m_bRight;
};

「正解フラグ」を追加しました。

初期値を格納するため「Start関数」にコードを追加します。

//=============================================================================
// シーンの実行時に1度だけ呼び出される開始処理関数
//=============================================================================
void SceneGame::Start()
{
    m_pEngine->AddTexture(TEXTURE_TRUMP);
    m_pEngine->AddFont(FONT_GOTHIC, 20);

    m_answer = rand() % 10 + 1;

    m_bRight = false;
}

続いて「Update関数」に判定を追加しましょう。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();

        if (m_point.x >= 120 && m_point.x < 520 && m_point.y >= 210 && m_point.y < 270) {

            m_input = (m_point.x - 120) / 40 + 1;

            if (m_input == m_answer) {
                m_bRight = true;
            }
        }
    }
}

「正解フラグ」がtrueになった場合、「BINGO!」の文字を表示します。

「Draw関数」にコードを追加します。

//=============================================================================
// シーンの実行時に繰り返し呼び出される描画処理関数
//=============================================================================
void SceneGame::Draw()
{
    //背景の転送
    SetRect(&m_sour, 0, 1568, 640, 2048);
    SetRect(&m_dest, 0, 0, 640, 480);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);
	
    //カードの転送
    SetRect(&m_sour, 0, 750, 400, 810);
    SetRect(&m_dest, 120, 210, 520, 270);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);

    if (m_bRight) {
        m_pEngine->DrawPrintf(0, 0, FONT_GOTHIC, D3DCOLOR_XRGB(255, 255, 255), "BINGO!");
    }
}

実行してみましょう。

<実行結果 クライアント領域のみ>

m_answerと同じ数値のカードをクリックした時に「BINGO!」の文字が表示されます。

しかし、当たるまで何度でも選択出来るのでゲームと言う感覚ではありません。

当たらなかった場合は「ゲームオーバー」などの工夫が必要です。


ゲームオーバー


では、最後に当たらなかった場合の対処を書きます。

当たったかどうかは「m_bRight」を調べれば分かります。

しかし、初期値はfalseであり、このフラグのチェックだけではゲームオーバーかどうかを調べるのは不十分です。

一応やってみましょう。

「Update関数」に次のコードを追加します。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();

        if (m_point.x >= 120 && m_point.x < 520 && m_point.y >= 210 && m_point.y < 270) {

            m_input = (m_point.x - 120) / 40 + 1;

            if (m_input == m_answer) {
                m_bRight = true;
            }
        }
    }

    if (!m_bRight) {
        m_nowSceneData.Set(Common::SCENE_EXIT, false, NULL);
    }
}

これは「正解フラグ」がfalseの場合、アプリケーションを終了するコードです。

m_nowSceneData.Set(Common::SCENE_EXIT, false, NULL);

この一文でアプリケーションを終了させる事が出来ます。

実行してみると分かりますが、ゲームが始まる前に終了してしまいます。

上にも書きましたが、「正解フラグ」の初期値はfalseですから、いきなりこのif文が成立しゲームが終了するのです。

まだカードを選択していない状態なので、終わってはいけないのですが・・・


そこで「選択完了フラグ」を追加する事にします。

「SceneGame.h」を開き、privateにフラグを追加してください。

private:
    
    int m_answer;

    int m_input;

    POINT m_point;

    bool m_bRight;

    bool m_bSelected;
};

「選択完了フラグ」です。

「SceneGame.cpp」を開き「Start関数」で初期値を設定します。

//=============================================================================
// シーンの実行時に1度だけ呼び出される開始処理関数
//=============================================================================
void SceneGame::Start()
{
    m_pEngine->AddTexture(TEXTURE_TRUMP);
    m_pEngine->AddFont(FONT_GOTHIC, 20);

    m_answer = rand() % 10 + 1;

    m_bRight = false;

    m_bSelected = false;
}

「Update関数」でカードが選択されたらフラグを立てるコードを追加し、ゲームの終了部分も変更します。

//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
    if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {

        m_point = m_pEngine->GetMousePosition();

        if (m_point.x >= 120 && m_point.x < 520 && m_point.y >= 210 && m_point.y < 270) {

            m_input = (m_point.x - 120) / 40 + 1;

            if (m_input == m_answer) {
                m_bRight = true;
            }

            m_bSelected = true;
        }
    }

    if (m_bSelected) {
        if (!m_bRight) {
            m_nowSceneData.Set(Common::SCENE_EXIT, false, NULL);
        }
    }
}

これで「選択完了フラグ」がfalseの間はゲームオーバーにならなくなりました。

後は「Draw関数」を変更するだけです。

//=============================================================================
// シーンの実行時に繰り返し呼び出される描画処理関数
//=============================================================================
void SceneGame::Draw()
{
    //背景の転送
    SetRect(&m_sour, 0, 1568, 640, 2048);
    SetRect(&m_dest, 0, 0, 640, 480);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);
	
    //カードの転送
    SetRect(&m_sour, 0, 750, 400, 810);
    SetRect(&m_dest, 120, 210, 520, 270);
    m_pEngine->Blt(&m_dest, TEXTURE_TRUMP, &m_sour);

    if (m_bSelected) {
        if (m_bRight) {
            m_pEngine->DrawPrintf(0, 0, FONT_GOTHIC, D3DCOLOR_XRGB(255, 255, 255), "BINGO!");
        }
    }
}

こちらも「選択完了フラグ」がtrueの場合、「正解フラグ」を調べて「正解」であれば「BINGO!」を表示します。

<実行結果 クライアント領域のみ>


このゲームはこれで終わりです。

コンソールアプリケーションに比べて、色々と考えるべき部分が増えてきているのが分かると思います。

表現方法も様々なため、自分自身がどのように作りたいかよく考えて作るようにしましょう。

※今回も直値を使って作りましたが、まずは作れるようになる事を重視しています。

※ある程度自由に作れるようになったら、体裁も考えながら作っていきましょう。


次へ

戻る

目次へ