====== DxLibグラフィック版スライドパズルを作っていくよ ====== ===== コンソール版による試作品製作の意義 ===== ここまでたどり着いた人は、多分だけどコンソール版ではスライドパズルが完成しているはず。\\ 昔からゲーム作ってる人は良くやるんだけど、とりあえずコンソールで配列とかを駆使して**プロトタイプ(試作品)**を作って、それをグラフィック表示にしていくという開発手順である。\\ これをやってみることで、とりあえず\\ * アルゴリズムや必要な関数の洗い出し * ゲームの流れをどう制御するか * ゲーム自体がちゃんと成り立つか が確認できる。\\ 後は、できたゲームにグラフィックを割り当てていったり、座標系とか描画とかグラフィック特有の処理を解決すればゲーム完成!\\ って流れである。(簡単だね)\\ 後は、画面を作って、現在の盤面を表示できるようにDraw関数を作る。\\
{{:game-engineer:classes:2023:something-else:summertime-special-cource:20230802-133428-394.png?400|}} ゲーム画面
画面のクリックした位置のパネル番号を取得して、コンソール版の移動可能か調べる関数に渡して、\\ 動かせそうなら、コンソール版のパネルをスペースと入れ替える関数に渡す。\\ コンソール版の動きと同じ処理をグラフィックで書いてあげるだけ(簡単に言うけどそれが難しいって話も。。。) なんかできそうじゃない? ==== 準備1 ゲーム全体の流れを作る ==== 後からやるととってもめんどくさいことになるので、まずゲームの流れをまるっと作ってゆく。\\ 今回のゲームは、パズルゲームなのでとても簡単流れを考えると以下のようになると思う。。\\ - タイトル画面(⇒プレイ画面へ移行) - プレイ画面(⇒クリアしたらクリア画面へ移行、⇒ゲームオーバーならゲームオーバー画面へ) - クリア画面(⇒タイトルへ移行) - ゲームオーバー画面(⇒タイトル画面へ移行) スライドパズルのレベルデザイン的な部分は、難易度、手数制限、時間制限等で考えられるが、今回はクリアはあるけどゲームオーバー(手数とか時間制限はなし)で考える(ここは後付けでも簡単だし!)\\ 授業でやったように、**ゲームのステート(状態)を移行していくことでその場面に必要な関数を呼び分ける**という作戦をとる。\\ すなわち、こんな感じのデザインになるかなと思う(CでもC++でも)\\ enum GAME_STATE { TITLE, //タイトル画面(シーン) PLAY, //プレイ画面(シーン) CLEAR, //クリア画面(シーン) GAMEOVER, //ゲームオーバー画面(シーン) }; GAME_STATE state = GAME_STATE::TITLE; //状態の初期化 stateってグローバル変数に現在の状態を保存 while (true) { switch (state) { case TITLE: TitleUpdate(&board); TitleDraw(); break; case PLAY: PlayUpdate(&board); PlayDraw(&board); break; case CLEAR: ClearUpdate(&board); ClearDraw(); break; case GAMEOVER: break; default: //ここには来ない。。。はず break; } } ==== タイトル画面を表示するまで ==== とりあえずタイトル画面を出したいね。\\ 各状態で呼び出す関数を以下のように作っていこうと思う。\\ * **GAME_STATE::TITLE** * **void TitleUpdate()** * タイトル画面の変数とかをアップデートする関数 * スタートボタン押したら、状態をGAME_STATE::PLAYに移行させるのもここ * **void TitleDraw()** * タイトル画面の描画処理はここでやる * **GAME_STATE::PLAY** * **void PlayUpdate()** * メインになるプレイ画面の更新処理 * クリアしたら状態をGAME_STATE::CLEARに移行させるのもここ * **void PlayDraw()** * プレイ画面の描画処理はここ * **GAME_STATE::CLEAR** * **void ClearUpdate()** * 同じくクリア画面の更新(ry * 何秒か経ってから、画面をクリックされたら、タイトルに戻りたい * **void CearDraw()** * クリア画面の表示 * **GAME_STATE::GAMEOVER** * 今回は使わないので、状態は書いておくけどスルー とりあえずDxLibのプロジェクトを作って、こいつらを追加しちゃいます。\\ ほんとは、ソースコードとヘッダファイル分けたほうがいいけど、今回は全部メインに書いていきます。\\ === ここでちょっとプロトタイプ宣言の話 === 宣言と定義は別だよねって話は授業の時にしたと思います。\\ プロトタイプ宣言は、関数の戻り値の型と、関数名、引数のリストを書いたものです。\\ (関数定義から、関数ブロック(処理内容)とってかわりにセミコロンつければいいよ。)\\ プロトタイプ宣言と、関数定義を書くと2度手間な気がするが、役割が違う。\\ * プロトタイプ宣言 * コンパイラや以降の関数に、こんな関数使うから知っといてとお知らせ(宣言)する * int sum(int a, double b); みたいな感じの書き方 * 宣言しておくことによって、定義をどの順番でしても大丈夫になるし呼び出しもできる * (何しろ使うことを宣言してあるからね) * 関数定義 * 関数の中ではこんな処理をして、戻り値として何を返すか、処理の内容を記述した部分 * プログラム中に出てくる順番に書くならプロトタイプ宣言はいらない ==== 初めのひな型 ==== // DxLib 雛形:状態遷移 TITLE/PLAY/CLEAR/GAMEOVER #include "DxLib.h" #include #include //--------------------------- // 状態定義 //--------------------------- enum GAME_STATE { TITLE, PLAY, CLEAR, GAMEOVER, }; // ゲーム状態の変数と、状態の初期化 enum GAME_STATE state = TITLE; //--------------------------- // 画面サイズ(中央寄せ用) //--------------------------- #define SCREEN_W 1280 #define SCREEN_H 720 //--------------------------- // フォントハンドル //--------------------------- int gFontTitle = -1; // 大タイトル int gFontLarge = -1; // 大見出し int gFontMedium = -1; // サブ見出し int gFontSmall = -1; // 説明小さめ //--------------------------- // 関数プロトタイプ //--------------------------- void TitleUpdate(void); void TitleDraw(void); void PlayUpdate(void); void PlayDraw(void); void ClearUpdate(void); void ClearDraw(void); void GameOverUpdate(void); void GameOverDraw(void); // 中央寄せ文字描画(影つき) void DrawCenteredString(const char* text, int y, int font, unsigned int colMain, unsigned int colShadow, int shadowOffset); //--------------------------- // メイン(WinMain) //--------------------------- int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { ChangeWindowMode(TRUE); SetGraphMode(SCREEN_W, SCREEN_H, 32); SetWindowText("DxLib State Template - Centered Bold UI"); if (DxLib_Init() != 0) return -1; SetDrawScreen(DX_SCREEN_BACK); // フォント作成(太め&アンチエイリアス) // CreateFontToHandle(名前, サイズpx, 太さ, タイプ, 文字セット, エッジ幅) gFontTitle = CreateFontToHandle("Meiryo", 96, 6, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); gFontLarge = CreateFontToHandle("Meiryo", 64, 5, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); gFontMedium = CreateFontToHandle("Meiryo", 36, 4, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); gFontSmall = CreateFontToHandle("Meiryo", 24, 2, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); while (ProcessMessage() == 0) { ClearDrawScreen(); // 背景(淡い色) DrawBox(0, 0, SCREEN_W, SCREEN_H, GetColor(153, 204, 179), TRUE); // 状態に応じて描画(更新は空のまま) switch (state) { case TITLE: TitleUpdate(); // ←中身は空 TitleDraw(); break; case PLAY: PlayUpdate(); // ←中身は空 PlayDraw(); break; case CLEAR: ClearUpdate(); // ←中身は空 ClearDraw(); break; case GAMEOVER: GameOverUpdate(); // ←中身は空 GameOverDraw(); break; default: break; } ScreenFlip(); } // フォント破棄 if (gFontTitle != -1) DeleteFontToHandle(gFontTitle); if (gFontLarge != -1) DeleteFontToHandle(gFontLarge); if (gFontMedium != -1) DeleteFontToHandle(gFontMedium); if (gFontSmall != -1) DeleteFontToHandle(gFontSmall); DxLib_End(); return 0; } //==================== ここから Update(空) ==================== void TitleUpdate(void) {} void PlayUpdate(void) {} void ClearUpdate(void) {} void GameOverUpdate(void){} ここでやったのは、 * GAME_STATEを用意 * GAME_STATE型のゲーム状態を表すstateを用意 * 各関数のプロトタイプ宣言 * メインループの中で、switch-caseを使って、状態ごとの処理を実行 * 今んところ、state=GAME_STATE::TITLEで初期化してあるんで、タイトル画面にしか行かない * ''DrawCenteredString''は以下のものを使う。中身は特に考えないで、これで大きい文字描けるんだなぁぐらいで使おう。 mainの前にいかを挿入\\ //==================== ヘルパー:中央寄せ(影つき) ==================== void DrawCenteredString(const char* text, int y, int font, unsigned int colMain, unsigned int colShadow, int shadowOffset) { int w = GetDrawStringWidthToHandle(text, (int)strlen(text), font); int h = GetFontSizeToHandle(font); int x = (SCREEN_W - w) / 2; // 影(オフセットがあれば) if (shadowOffset > 0) { DrawStringToHandle(x + shadowOffset, y + shadowOffset, text, colShadow, font); } // 本体 DrawStringToHandle(x, y, text, colMain, font); } ここで指定するフォントは、ソースコード内で生成している\\ //--------------------------- // フォントハンドル //--------------------------- int gFontTitle = -1; // 大タイトル int gFontLarge = -1; // 大見出し int gFontMedium = -1; // サブ見出し int gFontSmall = -1; // 説明小さめ // フォント作成(太め&アンチエイリアス) // CreateFontToHandle(名前, サイズpx, 太さ, タイプ, 文字セット, エッジ幅) gFontTitle = CreateFontToHandle("Meiryo", 96, 6, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); gFontLarge = CreateFontToHandle("Meiryo", 64, 5, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); gFontMedium = CreateFontToHandle("Meiryo", 36, 4, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); gFontSmall = CreateFontToHandle("Meiryo", 24, 2, DX_FONTTYPE_ANTIALIASING_8X8, -1, -1); こいつらを使います。\\ \\ === 各Update、Draw関数の準備 === プロトタイプ宣言まで完了したが、関数本体=定義、がないため呼び出せない。\\ 関数宣言の中身を空にしてると、呼び出してもスルーするだけなのでエラーにはならない。\\ なので、とりあえず処理部を空にして定義をすべて作っておく。\\ プロトタイプ宣言をまるっとドラッグで選択して、右クリックして、「クイックアクションとリファクタリング」⇒「宣言/定義の作成」をえらぶと、空の定義が自動でできるので便利!\\ そうするとこの状態になる。 //上のほう省略 //プロトタイプ宣言たち void TitleUpdate(); void TitleDraw(); void PlayUpdate(); void PlayDraw(); void ClearUpdate(); void ClearDraw(); void Main() { // 背景の色を設定する | Set the background color Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 }); //タイトル画面とスタートボタンのフォント(ンでそのままほかのシーンに使いまわし) FontAsset::Register(U"TITLE_FONT", FontMethod::SDF, 40, Typeface::Bold); FontAsset::Register(U"BUTTON_FONT", FontMethod::SDF, 20, Typeface::Mplus_Heavy); while (System::Update()) {        //省略 } } //この関数定義が自動で生成されるよ! void TitleUpdate() { } void TitleDraw() { } void PlayUpdate() { } void PlayDraw() { } void ClearUpdate() { } void ClearDraw() { } === タイトル画面の作成 === ''DrawCenteredString''関数は、 DrawCenteredString("描画する文字", 描画する高さ, フォントハンドル, 前景色, 影の色, 影の位置); で、画面の中央に文字を描画する関数です。適当にこれ使ってそれぞれでタイトル画面作ってみよう。\\ (それか授業でやったみたいに、画像を表示してもいいよ)\\
{{:game-engineer:classes:2025:something-else:summertime-special-cource:スクリーンショット_2025-08-25_153016.png?400|タイトル画面}} タイトル画面の作成(サンプル)
とにかくこんな感じでタイトルを作る!\\ 実際の処理は、TitleDrawの中に描画処理を書いてやります。\\ 画像読んだり、アニメーションしたりいろいろやってみて! void TitleUpdate() { //特になし } void TitleDraw(void) { // タイトル DrawCenteredString("THE SLIDE PUZZLE", SCREEN_H/2 - 60, gFontTitle, GetColor(255,255,255), GetColor(0,0,0), 4); // サブテキスト DrawCenteredString("Click to START", SCREEN_H/2 + 60, gFontMedium, GetColor(0,0,0), GetColor(255,255,255), 0); } __これでタイトル画面が表示されるか確認する!__ |< 500px 33.3% 33.3% - >| ^ [[https://wiki.yz-learning.com/doku.php?id=game-engineer:classes:2025:something-else:summertime-special-cource:start|メニューへ]] ^ [[game-engineer:classes:2025:something-else:summertime-special-cource:slidepuzle-dxlib-2|グラフィック編 その2へ]] ^