====== プレイ画面を作っていくぅ ====== 前回まででついに、前準備とゲームとしてのひな型ができたと思う。(基本的なゲームの状態移行)\\ 現在はプレイ画面に移行したときに、文字で"プレイ画面"って出てるだけなので、とりあえずボード(タイルの並び)が表示されるところまで作ろう\\ その前に、\\ ここで、''const''の説明をしていたようなしていなかったような、なので、しておこう。\\ === const修飾子 === 今までは、C言語では''#define''を使って定数を作ってきたと思います。(ここからちょっとずつC++に近づいていきましょう)\\ これは、単純にコンパイルするときにまるっと文字列を数字に置換しているだけです。\\ 変数ではないし、型もないので、コンパイルエラーの時はただの数字がおかしい、のようなエラーになってデバッグが大変です。\\ #define TILE_SIZE 80 これは **プリプロセッサ**という仕組みで、 **コンパイル前に文字を数字に置き換えているだけ** です。 つまり、ソースコードに書いた ''TILE_SIZE'' は 実際には **ただの数字 80** に変換されてしまいます。 * デバッグ時に「80」としか出ず、どの定数だったか分からない * 型がないので「整数?小数?」が曖昧 そこで、C99 以降では **型付きの定数** を作ることができます: const int TILE_SIZE = 80; これで ''TILE_SIZE'' は**「変更できない変数(定数)」**になります。 * **型を持つ**(ここでは int 型)ので安全 * デバッグ時に ''TILE_SIZE'' と名前が表示される * 誤って代入しようとするとエラーになる(安全) ==== ウィンドウサイズの変更 ==== 次にウィンドウサイズを変更していく、現在は1280x720という無駄にでかいサイズのウィンドウをぜいたくに使っている。\\ スライドパズル的にはこんなにでかくなくともよいので、適切なウィンドウサイズをタイルのサイズやレイアウトから逆算してみよう。\\ 早速''const''も使ってみるよ!\\ {{:game-engineer:classes:2025:something-else:summertime-special-cource:sunpo.png?500|}} 図のようにレイアウトしたとすると、ウィンドウのサイズを'' { SCREEN_W, SCREEN_H } ''は、どのように計算できるかな?\\ const int W = 4; const int H = 4; const int TILE_SIZE = 80; // タイルの一辺のサイズ(px) const int GAP = 6; // タイル間の隙間(px) const int MARGIN = 24; // 外枠と盤面の余白(px) /* 盤面・画面のサイズ計算(const 式) */ //BOARD_W、BOARD_Hは余白を除いた盤面の大きさ const int BOARD_W = W * TILE_SIZE + (隙間の数(横)) * 隙間の幅; const int BOARD_H = H * TILE_SIZE + (隙間の数(縦)) * 隙間の幅; const int SCREEN_W = BOARD_W + 左右のマージン(余白); const int SCREEN_H = BOARD_H + 上下のマージン(余白); サイズの計算が済んだなら、WinMainの初めの方で、ウィンドウのサイズ変更の関数を呼びます。\\ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){ ChangeWindowMode(TRUE); //SetGraphMode(横幅, 縦幅, 32);でサイズ変更 SetGraphMode(?????, ?????, 32); if(DxLib_Init()!=0) return -1; under construction \\ 後はコンソールで、文字で表示していたものを、どうグラフィックで表示するかって話になる。\\ とりあえず、BOARD_WIDTH xBOARD_HEIGHT = 4x4で、タイルっぽい矩形(四角)を表示する処理を書いてみようか。\\ ちなみに、キャラクターのサイズはファミコンのキャラが8x8, 16x16などの8の倍数規定だったこともあり、8の倍数で指定することが多い(画面のサイズも2の倍数とか8の倍数のことが多いよね)\\ タイルのサイズを128ぐらいで指定してみよう(128 x 4 = 512)。そうすると縦横に512x512の画面サイズが必要となる。\\ * Window::Resize({幅, 高さ}) で、ウィンドウの大きさを、指定した大きさに変更できるので、Mainのループに入る前の初期設定するときに大きさを、タイルに合わせて変えてあげよう!\\ //タイル1枚のサイズ 幅、高さ const Size TILE_SIZE = { 128, 128 }; //windowの大きさ 4x {128, 128} 縦横同じなのでどっちかかけてやる const Size WSIZE = BOARD_WIDTH * TILE_SIZE; void Main() {   //省略 背景書いたり、フォントアセット登録したりの次くらい Window::Resize(WSIZE); === 実行例 === 実行したときにウィンドウサイズが512x512になっていればOK\\
{{:game-engineer:classes:2023:something-else:summertime-special-cource:slidetitle.png?400|}} 大きさのアジャストされたタイトル画面
ついでにデバッグするたびに、スタートボタンを押すのはめんどくさいので、__初期設定のstateをPLAYにしておこう__!\\ 当然だけど、PLAY画面から始まるようになるよ\\ ==== タイルの描画 ==== 次はタイルを書いてみよう!\\ {{:game-engineer:classes:2023:something-else:summertime-special-cource:recta.png?400|}} Siv3DのRect型は、矩形の左上の座標と幅と高さ(正方形の場合はどっちか一方)を指定して.draw()してやると、矩形を描画してくれる。\\ 2重ループを使って、うまいことRect型を作ってみよう\\ ちょっと、プログラム的には無駄が多いけどstruct Boardに、Rectをタイル数分作ってあげよう(本当にタイルを収める枠を作る感じ)\\ struct Board { //  行 列 int tile[BOARD_HEIGHT][BOARD_WIDTH]; Rect tileRec[BOARD_HEIGHT][BOARD_WIDTH]; }; この枠に、実際の描画座標を割り当ててやる\\ ボードを初期化数関数InitBoardで、ついでにやってしまおう!\\ void InitBoard(Board& _board) { for (int j = 0; j < BOARD_HEIGHT; j++) { for (int i = 0; i < BOARD_WIDTH; i++) { _board.tile[j][i] = j * BOARD_WIDTH + i + 1; //[j][i] = [列][行]の位置のタイル番号 _board.tileRec[j][i] = Rect{ { X座標の計算, Y座標の計算}, 幅(高さ) }; } } } PlayDrawとPlayUpdateの処理には、Boardのデータが必要となる。\\ それらの関数を、Board型の引数をとるように書き換える。\\ 省略 void TitleUpdate(); void TitleDraw(); void PlayUpdate(Board& _board); void PlayDraw(Board& _board); void ClearUpdate(); void ClearDraw(); 省略 void PlayUpdate(Board& _board) { } void PlayDraw(Board& _board) { // 背景の色を設定する | Set the background color Scene::SetBackground(Palette::Lemonchiffon); //StringはSiv3Dだけでつかえる stringの上位互換型 String PlayStr = U"プレイ画面"; FontAsset(U"TITLE_FONT")(PlayStr).drawAt(Scene::Center(), Palette::Cadetblue); } 省略 んで、MainでBoard型の変数を作ってやって、初期化して、実際にPlayUpdateとPlayDrawに渡してゆくぅ\\ void Main() { //タイトル画面とスタートボタンのフォント(ンでそのままほかのシーンに使いまわし) FontAsset::Register(U"TITLE_FONT", FontMethod::SDF, 40, Typeface::Bold); FontAsset::Register(U"BUTTON_FONT", FontMethod::SDF, 20, Typeface::Mplus_Heavy); Window::Resize(WSIZE); Board myboard; InitBoard(myboard); while (System::Update()) { switch (state) { case TITLE: TitleUpdate(); TitleDraw(); break; case PLAY: PlayUpdate(myboard); PlayDraw(myboard); break; case CLEAR: ClearUpdate(); ClearDraw(); break; case GAMEOVER: break; default: break; } } } 次に、PlayDrawをInitBoardで作った枠を描画できるようにdrawFrameで輪郭を描画するよう変更する\\ **drawFrame(枠内側の太さピクセル数, 枠外側の太さピクセル数, 色);**\\ 実際に書き換えてゆくぅ、これはこのまま書けばよい\\ void PlayDraw(Board& _board) { // 背景の色を設定する | Set the background color Scene::SetBackground(Palette::Lemonchiffon); //StringはSiv3Dだけでつかえる stringの上位互換型 String PlayStr = U"プレイ画面"; FontAsset(U"TITLE_FONT")(PlayStr).drawAt(Scene::Center(), Palette::Cadetblue); for (int j = 0; j < BOARD_HEIGHT; j++) { for (int i = 0; i < BOARD_WIDTH; i++) { _board.tileRec[j][i].drawFrame(2, 0, Palette::Red); } } } 描画処理がうまくいくとこんな感じに表示される。\\
{{:game-engineer:classes:2023:something-else:summertime-special-cource:20230804-175753-041.png?400|}} タイル枠の表示
;#; [[game-engineer:classes:2023:something-else:summertime-special-cource:slidepuzle-siv3d-4|その4 プレイ画面の作成2]] ;#;