続いてゲームシーンを作りましょう。
画像はタイトルシーンで追加してあり、解放していませんのでそのまま使えます。
「宝探しゲーム」は2次元配列でフィールドを作り、その中に設置された「宝」を探すゲームでした。
まずは「SceneGame.h」を開き、privateに2次元配列を追加します。
private: enum { ROW = 10, COL = 10, }; int m_field[ROW][COL]; }; |
次に「SceneGame.cpp」を開き、「Start関数」でフィールドを初期化します。
//=============================================================================
// シーンの実行時に1度だけ呼び出される開始処理関数
//=============================================================================
void SceneGame::Start()
{
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
m_field[i][j] = 0;
}
}
}
|
「0」で初期化しましたが、これは「選択していない状態」と言う事です。
と言う説明をしなければならないので、定数化しましょう。
今回は状態を3つに分けます。
0 未選択 1 選択済み 2 宝 |
「SceneGame.h」を開き、privateに列挙体を追加します。
private:
enum {
UNCHECKED, //未選択
CHECKED, //選択済み
TREASURE, //宝
};
enum { ROW = 10, COL = 10, };
int m_field[ROW][COL];
};
|
「SceneGame.cpp」の「Start関数」も修正します。
//============================================================================= // シーンの実行時に1度だけ呼び出される開始処理関数 //============================================================================= void SceneGame::Start() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { m_field[i][j] = UNCHECKED; } } } |
フィールドの状態を描画したいと思いますので画像を確認します。
「ぴぽや」さんからお借りした、マップチップ用画像です。
左が「未選択」、真ん中が「選択済み」、右が「宝」となっています。
画像サイズは、全て「32×32ピクセル」です。
フィールドの要素の値を元に転送元座標を計算して転送します。
基本はトランプと同じです。
フィールド 画像 左座標 0 未選択 0 1 選択済み 32 2 宝 64
この表を元に計算式を組み立てると、
左座標 = フィールドの値 × チップの幅(32)
となります。
「SceneGame.h」に定数を追加しますが、今回はチップの幅なども定数にしてみます。
private:
const int CHIP_X; //チップの左端の座標
const int CHIP_Y; //チップの上の座標
const int CHIP_WIDTH; //チップの幅
const int CHIP_HEIGHT; //チップの高さ
enum {
UNCHECKED,
CHECKED,
TREASURE,
};
enum { ROW = 10, COL = 10, };
int m_field[ROW][COL];
};
|
「SceneGame.cpp」を開き、コンストラクタ関数で初期値を設定します。
//============================================================================= // コンストラクタ // 引 数:Engine* エンジンクラスのアドレス //============================================================================= SceneGame::SceneGame(Engine *pEngine) : Scene(pEngine) , CHIP_X(0) , CHIP_Y(480) , CHIP_WIDTH(32) , CHIP_HEIGHT(32) { } |
「Draw関数」で転送元座標の設定だけ追加します。
//=============================================================================
// シーンの実行時に繰り返し呼び出される描画処理関数
//=============================================================================
void SceneGame::Draw()
{
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
SetRect(&m_sour,
CHIP_X + m_field[i][j] * CHIP_WIDTH,
CHIP_Y,
CHIP_X + m_field[i][j] * CHIP_WIDTH + CHIP_WIDTH,
CHIP_Y + CHIP_HEIGHT);
}
}
}
|
定数に慣れていない場合、一見ややこしくなったように見えるかも知れません。
左座標の計算式を値に直してみると、
0 + m_field[i][j] * 32 |
です。
なぜ、わざわざ「0」を定数にして足しているかと言うと、元画像の配置が変わった時のためです。
様々な変更があったとしても、出来るだけ少ない修正で済むように考えておきましょう。
次は転送先です。
とりあえず↓のように、画面の左上にフィールドを表示したいと思います。
C言語編でも何度もこのようなフィールドを表示してきました。
チップの幅と高さも分かっていますので、そこまで難しい事ではありません。
転送先の画像を図にしてみます。
左座標はチップの幅(32ピクセル)、上座標はチップの高さ(32ピクセル)ずつ増えています。
これを2重ループの添え字を使って計算します。
「Draw関数」に転送先座標と「Blt関数」を追加しましょう。
//============================================================================= // シーンの実行時に繰り返し呼び出される描画処理関数 //============================================================================= void SceneGame::Draw() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { SetRect(&m_sour, CHIP_X + m_field[i][j] * CHIP_WIDTH, CHIP_Y, CHIP_X + m_field[i][j] * CHIP_WIDTH + CHIP_WIDTH, CHIP_Y + CHIP_HEIGHT); SetRect(&m_dest, j * CHIP_WIDTH, i * CHIP_HEIGHT, j * CHIP_WIDTH + CHIP_WIDTH, i * CHIP_HEIGHT + CHIP_HEIGHT); m_pEngine->Blt(&m_dest, TEXTURE_TREASURE, &m_sour); } } } |
左座標は「j * 幅」、上座標は「i * 高さ」で変化させながら転送します。
実行して確認してください。
<実行結果 クライアント領域のみ>
ここからはマウスを使って、フィールドを選択出来るようにします。
これも「数当て」の時にやった事の応用で出来ます。
まずは、マウスの左ボタンを押したかどうか調べ、押していればマウスの座標を取得します。
「Update関数」に書きましょう。
//=============================================================================
// シーンの実行時に繰り返し呼び出される更新処理関数
//=============================================================================
void SceneGame::Update()
{
if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) {
POINT point = m_pEngine->GetMousePosition();
}
}
|
次にフィールドが表示されいている範囲かどうか調べます。
範囲を数値で表すと、
座標 数値 左 0 上 0 右 320 下 320
ですが、右は「チップの幅 × 横方向のチップ数」つまり
CHIP_WIDTH * COL |
ですし、左は「チップの高さ × 縦方向のチップ数」
CHIP_HEIGHT * ROW |
と定数で表す事が出来ます。
では、「Update関数」に追加しましょう。
//============================================================================= // シーンの実行時に繰り返し呼び出される更新処理関数 //============================================================================= void SceneGame::Update() { if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) { POINT point = m_pEngine->GetMousePosition(); if (point.x >= 0 && point.x < CHIP_WIDTH * COL && point.y >= 0 && point.y < CHIP_HEIGHT * ROW) { } } } |
まだ動きが無いので、実行しても何も変わりません。
「数当て」の時と同じく、取得したマウス座標から計算式を使って座標を変換します。
今回は、マウス座標から2次元配列の添え字を求めなければなりません。
くどいですが横方向の対応を表にしてみましょう。
point.xの範囲 欲しい添え字 0 〜 31 0 32 〜 63 1 64 〜 95 2 96 〜 127 3 128 〜 159 4 160 〜 191 5 192 〜 223 6 224 〜 255 7 256 〜 287 8 288 〜 319 9
前と同じですが、次の式で添え字が求められます。
point.x / 32
整数として計算すれば、小数点以下は切り捨てられます。
縦方向も同じですから、「Update関数」にクリックした箇所を「選択済み」にするコードを追加しましょう。
//============================================================================= // シーンの実行時に繰り返し呼び出される更新処理関数 //============================================================================= void SceneGame::Update() { if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) { POINT point = m_pEngine->GetMousePosition(); if (point.x >= 0 && point.x < CHIP_WIDTH * COL && point.y >= 0 && point.y < CHIP_HEIGHT * ROW) { int x = point.x / CHIP_WIDTH; int y = point.y / CHIP_HEIGHT; m_field[y][x] = CHECKED; } } } |
実行してフィールドをクリックしてみましょう。
<実行結果 クライアント領域のみ>
クリックしたところに「穴」が開いていけば成功です。
次は「宝」を設定します。
C言語編の時は1つだけセットしましたが、冷静に見ると100分の1個ですよね・・・
正直途中で飽きてしまいます。
と言う訳で、今回は「宝」を10個に増やしましょう。
まずは「宝」の個数を定数化します。
「SceneGame.h」を開き、privateに定数を追加します。
private:
const int CHIP_X;
const int CHIP_Y;
const int CHIP_WIDTH;
const int CHIP_HEIGHT;
const int TREASURE_MAX; //宝の個数
enum {
UNCHECKED,
CHECKED,
TREASURE,
};
enum { ROW = 10, COL = 10, };
int m_field[ROW][COL];
};
|
「SceneGame.cpp」を開きコンストラクタ関数で初期値を入れます。
//============================================================================= // コンストラクタ // 引 数:Engine* エンジンクラスのアドレス //============================================================================= SceneGame::SceneGame(Engine *pEngine) : Scene(pEngine) , CHIP_X(0) , CHIP_Y(480) , CHIP_WIDTH(32) , CHIP_HEIGHT(32) , TREASURE_MAX(10) { } |
「Start関数」で乱数を使って「宝」をセットしましょう。
//============================================================================= // シーンの実行時に1度だけ呼び出される開始処理関数 //============================================================================= void SceneGame::Start() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { m_field[i][j] = UNCHECKED; } } for (int i = 0; i < TREASURE_MAX; i++) { int idxX = rand() % COL; int idxY = rand() % ROW; m_field[idxY][idxX] = TREASURE; } } |
10回繰り返しながら、乱数で縦横の添え字を計算し、「宝」をセットしました。
とりあえず、実行してみましょう。
<実行結果 クライアント領域のみ>
色々とマズイところがありますね・・・
分かりやすいのは「宝が見えている」と言う事です。
分かりづらいのは「宝が9個しかない」と言う事です。
※常に9個ではなく、何度か実行すると10個では無い時があります。
先に「宝が9個しかない」方を対処します。
原因は「同じ乱数が発生し、宝がセット済みの場所に宝をセットしている」事です。
重複しないように、宝がセットされているかどうかを確認するように変更します。
//============================================================================= // シーンの実行時に1度だけ呼び出される開始処理関数 //============================================================================= void SceneGame::Start() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { m_field[i][j] = UNCHECKED; } } int idxX; int idxY; for (int i = 0; i < TREASURE_MAX; i++) { do { idxX = rand() % COL; idxY = rand() % ROW; } while (m_field[idxY][idxX] == TREASURE); m_field[idxY][idxX] = TREASURE; } } |
do〜while文を使う事で対応しました。
実行して確認してください。
<実行結果 クライアント領域のみ>
もう1つの問題「宝が見えている」への対応は色々考える必要があります。
C言語編の時は「宝」であった場合は「未選択」を表示する事で対応していました。
ルールによっては、この方法が使えないケースもありますので、ルールをはっきりさせましょう。
「宝」を1つ見つければクリア!では面白くないので、「宝」を5個見つけるまでに、何回クリックしたかを競う事にしましょう。
このルールの場合、「宝」を5個見つけるまで終わらないので「見つけた宝」は表示し続ける必要があります。
「見つけた宝」と「見つかっていない宝」を区別する方法が無ければ、↑のような事は出来ません。
フィールドを構造体にして、メンバ変数を増やす事で対応してみましょう。
「SceneGame.h」を開き、privateで構造体を宣言し、これまでのフィールド配列を変更します。
private: const int CHIP_X; const int CHIP_Y; const int CHIP_WIDTH; const int CHIP_HEIGHT; const int TREASURE_MAX; enum { UNCHECKED, CHECKED, TREASURE, }; //フィールド構造体 struct Field { int m_status; //状態 bool m_bTreasure; //宝フラグ }; enum { ROW = 10, COL = 10, }; Field m_field[ROW][COL]; }; |
これによって「SceneClear.cpp」にエラーが発生していますので直しましょう。
「Start関数」から直します。
//============================================================================= // シーンの実行時に1度だけ呼び出される開始処理関数 //============================================================================= void SceneGame::Start() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { m_field[i][j].m_status = UNCHECKED; m_field[i][j].m_bTreasure = false; } } int idxX; int idxY; for (int i = 0; i < TREASURE_MAX; i++) { do { idxX = rand() % COL; idxY = rand() % ROW; } while (m_field[idxY][idxX].m_bTreasure); m_field[idxY][idxX].m_bTreasure = true; } } |
宝フラグには最初に「false」を入れておきます。
その後、乱数を使って、10カ所だけ「true」に変えます。
「Update関数」も直します。
//============================================================================= // シーンの実行時に繰り返し呼び出される更新処理関数 //============================================================================= void SceneGame::Update() { if (m_pEngine->GetMouseButtonSync(DIK_LBUTTON)) { POINT point = m_pEngine->GetMousePosition(); if (point.x >= 0 && point.x < CHIP_WIDTH * COL && point.y >= 0 && point.y < CHIP_HEIGHT * ROW) { int x = point.x / CHIP_WIDTH; int y = point.y / CHIP_HEIGHT; m_field[y][x].m_status = CHECKED; } } } |
最後は「Draw関数」です。
//============================================================================= // シーンの実行時に繰り返し呼び出される描画処理関数 //============================================================================= void SceneGame::Draw() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { SetRect(&m_sour, CHIP_X + m_field[i][j].m_status * CHIP_WIDTH, CHIP_Y, CHIP_X + m_field[i][j].m_status * CHIP_WIDTH + CHIP_WIDTH, CHIP_Y + CHIP_HEIGHT); SetRect(&m_dest, j * CHIP_WIDTH, i * CHIP_HEIGHT, j * CHIP_WIDTH + CHIP_WIDTH, i * CHIP_HEIGHT + CHIP_HEIGHT); m_pEngine->Blt(&m_dest, TEXTURE_TREASURE, &m_sour); } } } |
とりあえず実行してみましょう。
<実行結果 クライアント領域のみ>
まだ「宝」は表示されませんが、動作する事だけ確認してください。
残りは次回に続きます。