第3回 マウスでタイルを動かす
第3回 マウスでタイルを動かそう
今回やること
前回は、コンソール版のBoardを使い、タイル画像で盤面をDxLibのウィンドウへ表示しました。
今回は、マウスでタイルを選んで動かせるようにします。
コンソール版では、
scanf()
↓
タイル番号
↓
MoveTile()
という流れでした。
DxLib版では、
マウスクリック
↓
クリックした場所のタイル番号
↓
MoveTile()
に変えます。
MoveTile()そのものは、コンソール版で作ったものをそのまま使います。
つまり今回は、
タイル番号をどうやって決めるか
の部分だけを、キーボード入力からマウス入力へ置き換えます。
1.PLAY画面の仮処理を外そう
第1回では状態遷移を確認するために、PLAY画面をクリックするとCLEAR画面へ進む仮処理を作りました。
void PlayUpdate(enum GameState* state)
{
if (IsMouseLeftPressed())
{
*state = GAME_STATE_CLEAR;
}
}
今回はPLAY画面のクリックを、タイルを選ぶために使います。
そのため、この仮処理をいったん外します。
まずプロトタイプ宣言を、
void PlayUpdate(enum GameState* state);
から、
void PlayUpdate(struct Board* board);
へ変更してください。
次に、今までのPlayUpdate()の定義を、いったん次の形へ置き換えます。
void PlayUpdate(struct Board* board)
{
}
最後に、ゲームループの、
PlayUpdate(&state);
を、
PlayUpdate(&board);
へ変更します。
ここで一度ビルドしよう
実行すると、
- TITLEからPLAYへ進める
- PLAY画面には盤面が表示される
- PLAY画面をクリックしてもCLEARへ進まない
状態になります。
ここまでできれば、PLAY画面のクリックを新しい操作に使う準備ができました。
2.マウスの座標を取ってみよう
DxLibでは、
GetMousePoint(&mouseX, &mouseY);
で現在のマウス座標を取得できます。
たとえば、
int mouseX;
int mouseY;
GetMousePoint(&mouseX, &mouseY);
とすると、
mouseX → マウスの横方向の位置
mouseY → マウスの縦方向の位置
が入ります。
GetMousePoint()がmouseXとmouseYへ値を書き込むので、
&mouseX
&mouseY
と変数の場所を渡しています。
これは、これまでInitBoard(&board)などで使ってきた&と同じ考え方です。
3.マウスがタイルの中にあるか調べよう
前回、タイル1枚の左上を、
left
top
で求めました。
タイルの大きさはTILE_SIZEなので、タイル1枚の範囲は次のようになります。
left left + TILE_SIZE
│ │
├──────────────────────────────┤
│ │
│ タイル │
│ │
├──────────────────────────────┤
top top + TILE_SIZE
マウスがこの中にあるか調べるには、横方向と縦方向の両方を確認します。
左端より右にある
mouseX >= left
右端より左にある
mouseX < left + TILE_SIZE
上端より下にある
mouseY >= top
下端より上にある
mouseY < top + TILE_SIZE
4つを全部満たしていれば、マウスはそのタイルの中にあります。
if (mouseX >= left &&
mouseX < left + TILE_SIZE &&
mouseY >= top &&
mouseY < top + TILE_SIZE)
{
// マウスはこのタイルの中
}
4.GetClickedTile()を作ろう
クリックした場所にあるタイル番号を返す関数を作ります。
まず、WinMain()より前へプロトタイプ宣言を追加します。
int GetClickedTile(
struct Board* board,
int mouseX,
int mouseY);
この関数では、盤面のマスを順番に調べて、
マウスが入っているマスを見つけたら、その場所のタイル番号を返す
ようにします。
タイルが見つからなかった場合は-1を返します。
穴埋め
int GetClickedTile(
struct Board* board,
int mouseX,
int mouseY)
{
for (int y = 0; y < BOARD_HEIGHT; y++)
{
for (int x = 0; x < BOARD_WIDTH; x++)
{
// xとyから配列の添字を求める
int index = ______________________________;
// xから、このタイルの左端を求める
int left = ______________________________;
// yから、このタイルの上端を求める
int top = ______________________________;
// マウスが左端より右にあるか調べる
int insideLeft =
______________________________;
// マウスが右端より左にあるか調べる
int insideRight =
______________________________;
// マウスが上端より下にあるか調べる
int insideTop =
______________________________;
// マウスが下端より上にあるか調べる
int insideBottom =
______________________________;
// 4方向すべてでタイルの内側にあるか調べる
if (____________________________________)
{
// 空白タイルなら、タイルを選ばなかったことにする
if (________________________________)
return -1;
// この場所に入っているタイル番号を返す
return ______________________________;
}
}
}
// どのタイルにも当たらなかった
return -1;
}
模範解答を見る
int GetClickedTile(
struct Board* board,
int mouseX,
int mouseY)
{
for (int y = 0; y < BOARD_HEIGHT; y++)
{
for (int x = 0; x < BOARD_WIDTH; x++)
{
int index =
y * BOARD_WIDTH + x;
int left =
BOARD_X + x * (TILE_SIZE + TILE_GAP);
int top =
BOARD_Y + y * (TILE_SIZE + TILE_GAP);
int insideLeft =
mouseX >= left;
int insideRight =
mouseX < left + TILE_SIZE;
int insideTop =
mouseY >= top;
int insideBottom =
mouseY < top + TILE_SIZE;
if (insideLeft &&
insideRight &&
insideTop &&
insideBottom)
{
if (board->tile[index] == BLANK_TILE)
return -1;
return board->tile[index];
}
}
}
return -1;
}
4つの条件を別々の変数へ入れたのは、
左から入っている?
右から入っている?
上から入っている?
下から入っている?
を1つずつ確認しやすくするためです。
慣れてきたら、4つの条件を直接ifへ書いても構いません。
5.まずクリックした番号を確認しよう
いきなりタイルを動かす前に、
クリックした場所から、正しいタイル番号を取得できているか
を確認します。
PlayUpdate()へマウス入力を追加します。
穴埋め
void PlayUpdate(struct Board* board)
{
// マウスの左ボタンを押した瞬間だけ処理する
if (______________________________)
{
int mouseX;
int mouseY;
// 現在のマウス座標を取得する
______________________________;
// クリックした場所のタイル番号を調べる
int number =
______________________________;
// 動作確認のため、取得した番号を一時的に表示する
printfDx("clicked = %d\n", number);
}
}
模範解答を見る
void PlayUpdate(struct Board* board)
{
if (IsMouseLeftPressed())
{
int mouseX;
int mouseY;
GetMousePoint(&mouseX, &mouseY);
int number =
GetClickedTile(board, mouseX, mouseY);
printfDx("clicked = %d\n", number);
}
}
ここで使っているprintfDx()は、動作確認のための一時的な表示です。
printfDx()は呼び出すたびに簡易表示の履歴へ行が追加されるので、1番、5番、15番とクリックすると、確認した番号が順番に残ります。
今回は入力結果を確認するためだけに使い、確認が終わったら次の節でprintfDx()の呼び出しを削除します。
確認してみよう
- 1番をクリック →
1 - 5番をクリック →
5 - 15番をクリック →
15 - 空白をクリック →
-1 - タイルとタイルの隙間をクリック →
-1
となるか確認してください。
6.取得した番号をMoveTile()へ渡そう
番号が正しく取れたら、printfDx()を削除して、本当の移動処理へ置き換えます。
ここで使うのが、コンソール版で作った、
MoveTile()
です。
穴埋め
void PlayUpdate(struct Board* board)
{
if (IsMouseLeftPressed())
{
int mouseX;
int mouseY;
GetMousePoint(&mouseX, &mouseY);
// クリックしたタイル番号を取得する
int number =
______________________________;
// タイルが見つかったときだけ処理する
if (______________________________)
{
// 取得した番号のタイルを動かす
______________________________;
}
}
}
模範解答を見る
void PlayUpdate(struct Board* board)
{
if (IsMouseLeftPressed())
{
int mouseX;
int mouseY;
GetMousePoint(&mouseX, &mouseY);
int number =
GetClickedTile(board, mouseX, mouseY);
if (number != -1)
{
MoveTile(board, number);
}
}
}
7.どうして動かせないタイルは動かない?
PlayUpdate()では、
MoveTile(board, number);
を呼んでいるだけです。
ここでは、
そのタイルが空白の隣にあるか
を調べていません。
これはMoveTile()の中ですでに、
if (CanMoveTile(board, number) == 0)
return;
と判定しているからです。
処理の流れは次のようになります。
マウスをクリック
↓
GetClickedTile()
↓
タイル番号
↓
MoveTile()
↓
CanMoveTile()
↓
動かせるときだけ交換
マウス入力のために、CanMoveTile()やMoveTile()を書き直す必要はありません。
8.動作確認しよう
完成状態の盤面は、次のようになっています。
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 [空白]
この状態では、空白の隣にある、
- 15番
- 12番
だけを動かせます。
テスト1
プログラムを起動し直してPLAYへ進み、15番をクリックします。
15番が右へ移動すれば成功です。
テスト2
もう一度プログラムを起動し直してPLAYへ進み、12番をクリックします。
12番が下へ移動すれば成功です。
そのほかの確認
- 空白から離れたタイルをクリック → 動かない
- 空白をクリック → 何も起こらない
- タイルの隙間をクリック → 何も起こらない
- マウスを押しっぱなしにしても、1回のクリックで何枚も動かない
ここまで動けば、第3回は完成です。
9.第3回の完成コード
途中で分からなくなった場合は、完成した形と比べて確認してください。
第3回の完成コードを見る
#include "DxLib.h"
#include <stdlib.h>
#define BOARD_WIDTH 4
#define BOARD_HEIGHT 4
#define BLANK_TILE (BOARD_WIDTH * BOARD_HEIGHT)
#define SHUFFLE_COUNT 100
#define TILE_SIZE 80
#define TILE_GAP 6
#define BOARD_X 24
#define BOARD_Y 80
#define BOARD_PIXEL_WIDTH \
(BOARD_WIDTH * TILE_SIZE + (BOARD_WIDTH - 1) * TILE_GAP)
#define BOARD_PIXEL_HEIGHT \
(BOARD_HEIGHT * TILE_SIZE + (BOARD_HEIGHT - 1) * TILE_GAP)
#define SCREEN_WIDTH \
(BOARD_X * 2 + BOARD_PIXEL_WIDTH)
#define SCREEN_HEIGHT \
(BOARD_Y + BOARD_PIXEL_HEIGHT + 40)
enum GameState
{
GAME_STATE_TITLE,
GAME_STATE_PLAY,
GAME_STATE_CLEAR
};
struct Board
{
int tile[BOARD_WIDTH * BOARD_HEIGHT];
};
struct Position
{
int x;
int y;
};
int IsMouseLeftPressed(void);
void InitBoard(struct Board* board);
struct Position SearchTile(struct Board* board, int number);
int CanMoveTile(struct Board* board, int number);
void MoveTile(struct Board* board, int number);
int IsClear(struct Board* board);
void ShuffleBoard(struct Board* board);
int GetClickedTile(
struct Board* board,
int mouseX,
int mouseY);
void LoadTileImages(int tileGraph[]);
void DeleteTileImages(int tileGraph[]);
void DrawBoard(struct Board* board, int tileGraph[]);
void TitleUpdate(enum GameState* state);
void TitleDraw(void);
void PlayUpdate(struct Board* board);
void PlayDraw(struct Board* board, int tileGraph[]);
void ClearUpdate(enum GameState* state);
void ClearDraw(void);
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
struct Board board;
int tileGraph[BLANK_TILE];
enum GameState state = GAME_STATE_TITLE;
ChangeWindowMode(TRUE);
SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
SetMainWindowText("Slide Puzzle");
if (DxLib_Init() != 0)
return -1;
SetDrawScreen(DX_SCREEN_BACK);
LoadTileImages(tileGraph);
InitBoard(&board);
while (ProcessMessage() == 0)
{
ClearDrawScreen();
DrawBox(
0, 0,
SCREEN_WIDTH, SCREEN_HEIGHT,
GetColor(70, 100, 110),
TRUE);
switch (state)
{
case GAME_STATE_TITLE:
TitleUpdate(&state);
TitleDraw();
break;
case GAME_STATE_PLAY:
PlayUpdate(&board);
PlayDraw(&board, tileGraph);
break;
case GAME_STATE_CLEAR:
ClearUpdate(&state);
ClearDraw();
break;
}
ScreenFlip();
}
DeleteTileImages(tileGraph);
DxLib_End();
return 0;
}
int IsMouseLeftPressed(void)
{
static int previousInput = 0;
int currentInput = GetMouseInput();
int pressed =
(currentInput & MOUSE_INPUT_LEFT) != 0 &&
(previousInput & MOUSE_INPUT_LEFT) == 0;
previousInput = currentInput;
return pressed;
}
void InitBoard(struct Board* board)
{
for (int i = 0; i < BOARD_WIDTH * BOARD_HEIGHT; i++)
{
board->tile[i] = i + 1;
}
}
struct Position SearchTile(struct Board* board, int number)
{
struct Position result = { -1, -1 };
for (int y = 0; y < BOARD_HEIGHT; y++)
{
for (int x = 0; x < BOARD_WIDTH; x++)
{
int index = y * BOARD_WIDTH + x;
if (board->tile[index] == number)
{
result.x = x;
result.y = y;
return result;
}
}
}
return result;
}
int CanMoveTile(struct Board* board, int number)
{
struct Position blank = SearchTile(board, BLANK_TILE);
struct Position tile = SearchTile(board, number);
int dx = blank.x - tile.x;
int dy = blank.y - tile.y;
if (dx * dx + dy * dy == 1)
return 1;
return 0;
}
void MoveTile(struct Board* board, int number)
{
if (CanMoveTile(board, number) == 0)
return;
struct Position blank = SearchTile(board, BLANK_TILE);
struct Position tile = SearchTile(board, number);
int blankIndex = blank.y * BOARD_WIDTH + blank.x;
int tileIndex = tile.y * BOARD_WIDTH + tile.x;
int temp = board->tile[blankIndex];
board->tile[blankIndex] = board->tile[tileIndex];
board->tile[tileIndex] = temp;
}
int IsClear(struct Board* board)
{
for (int i = 0; i < BOARD_WIDTH * BOARD_HEIGHT; i++)
{
if (board->tile[i] != i + 1)
return 0;
}
return 1;
}
void ShuffleBoard(struct Board* board)
{
while (IsClear(board))
{
for (int i = 0; i < SHUFFLE_COUNT; i++)
{
int number =
rand() % (BLANK_TILE - 1) + 1;
if (CanMoveTile(board, number))
{
MoveTile(board, number);
}
}
}
}
int GetClickedTile(
struct Board* board,
int mouseX,
int mouseY)
{
for (int y = 0; y < BOARD_HEIGHT; y++)
{
for (int x = 0; x < BOARD_WIDTH; x++)
{
int index =
y * BOARD_WIDTH + x;
int left =
BOARD_X + x * (TILE_SIZE + TILE_GAP);
int top =
BOARD_Y + y * (TILE_SIZE + TILE_GAP);
int insideLeft =
mouseX >= left;
int insideRight =
mouseX < left + TILE_SIZE;
int insideTop =
mouseY >= top;
int insideBottom =
mouseY < top + TILE_SIZE;
if (insideLeft &&
insideRight &&
insideTop &&
insideBottom)
{
if (board->tile[index] == BLANK_TILE)
return -1;
return board->tile[index];
}
}
}
return -1;
}
void LoadTileImages(int tileGraph[])
{
tileGraph[0] = -1;
tileGraph[1] = LoadGraph("tile_images/tile_01.png");
tileGraph[2] = LoadGraph("tile_images/tile_02.png");
tileGraph[3] = LoadGraph("tile_images/tile_03.png");
tileGraph[4] = LoadGraph("tile_images/tile_04.png");
tileGraph[5] = LoadGraph("tile_images/tile_05.png");
tileGraph[6] = LoadGraph("tile_images/tile_06.png");
tileGraph[7] = LoadGraph("tile_images/tile_07.png");
tileGraph[8] = LoadGraph("tile_images/tile_08.png");
tileGraph[9] = LoadGraph("tile_images/tile_09.png");
tileGraph[10] = LoadGraph("tile_images/tile_10.png");
tileGraph[11] = LoadGraph("tile_images/tile_11.png");
tileGraph[12] = LoadGraph("tile_images/tile_12.png");
tileGraph[13] = LoadGraph("tile_images/tile_13.png");
tileGraph[14] = LoadGraph("tile_images/tile_14.png");
tileGraph[15] = LoadGraph("tile_images/tile_15.png");
}
void DeleteTileImages(int tileGraph[])
{
for (int i = 1; i < BLANK_TILE; i++)
{
DeleteGraph(tileGraph[i]);
}
}
void DrawBoard(struct Board* board, int tileGraph[])
{
for (int y = 0; y < BOARD_HEIGHT; y++)
{
for (int x = 0; x < BOARD_WIDTH; x++)
{
int index =
y * BOARD_WIDTH + x;
int left =
BOARD_X + x * (TILE_SIZE + TILE_GAP);
int top =
BOARD_Y + y * (TILE_SIZE + TILE_GAP);
int number = board->tile[index];
if (number != BLANK_TILE)
{
DrawGraph(
left,
top,
tileGraph[number],
TRUE);
}
}
}
}
void TitleUpdate(enum GameState* state)
{
if (IsMouseLeftPressed())
{
*state = GAME_STATE_PLAY;
}
}
void TitleDraw(void)
{
DrawString(
100, 150,
"SLIDE PUZZLE",
GetColor(255, 255, 255));
DrawString(
100, 200,
"CLICK TO START",
GetColor(255, 255, 255));
}
void PlayUpdate(struct Board* board)
{
if (IsMouseLeftPressed())
{
int mouseX;
int mouseY;
GetMousePoint(&mouseX, &mouseY);
int number =
GetClickedTile(board, mouseX, mouseY);
if (number != -1)
{
MoveTile(board, number);
}
}
}
void PlayDraw(struct Board* board, int tileGraph[])
{
DrawString(
24, 24,
"SLIDE PUZZLE",
GetColor(255, 255, 255));
DrawBoard(board, tileGraph);
}
void ClearUpdate(enum GameState* state)
{
if (IsMouseLeftPressed())
{
*state = GAME_STATE_TITLE;
}
}
void ClearDraw(void)
{
DrawString(
100, 150,
"CLEAR!",
GetColor(255, 255, 255));
}
今回できるようになったこと
マウス座標からタイル番号を取得して、コンソール版のMoveTile()へ渡せるようになりました。
マウス
↓
GetMousePoint()
↓
GetClickedTile()
↓
タイル番号
↓
MoveTile()
入力方法は変わりましたが、
SearchTile()
CanMoveTile()
MoveTile()
はコンソール版で作ったものをそのまま使えています。
次回は、盤面をシャッフルして本当の問題を作り、クリアしたらCLEAR画面へ進むようにします。


No Comments