グラフィック版スライドパズルを作っていくよ

ここまでたどり着いた人は、多分だけどコンソール版ではスライドパズルが完成しているはず。
昔からゲーム作ってる人は良くやるんだけど、とりあえずコンソールで配列とかを駆使してプロトタイプ(試作品)を作って、それをグラフィック表示にしていくという開発手順である。
これをやってみることで、とりあえず

  • アルゴリズムや必要な関数の洗い出し
  • ゲームの流れをどう制御するか
  • ゲーム自体がちゃんと成り立つか

が確認できる。
後は、できたゲームにグラフィックを割り当てていったり、座標系とか描画とかグラフィック特有の処理を解決すればゲーム完成!
って流れである。(簡単だね)
後は、画面を作って、現在の盤面を表示できるようにDraw関数を作る。

Fig. 1: ゲーム画面

画面のクリックした位置のパネル番号を取得して、コンソール版の移動可能か調べる関数に渡して、
動かせそうなら、コンソール版のパネルをスペースと入れ替える関数に渡す。
なんかできそうじゃない?

後からやるととってもめんどくさいことになるので、まずゲームの流れをまるっと作ってゆく。
今回のゲームは、パズルゲームなのでとても簡単流れを考えると以下のようになると思う。。

  1. タイトル画面(⇒プレイ画面へ移行)
  2. プレイ画面(⇒クリアしたらクリア画面へ移行、⇒ゲームオーバーならゲームオーバー画面へ)
  3. クリア画面(⇒タイトルへ移行)
  4. ゲームオーバー画面(⇒タイトル画面へ移行)

スライドパズルのレベルデザイン的な部分は、難易度、手数制限、時間制限等で考えられるが、今回はクリアはあるけどゲームオーバー(手数とか時間制限はなし)で考える(ここは後付けでも簡単だし!)
授業でやったように、ゲームのステート(状態)を移行していくことでその場面に必要な関数を呼び分けるという作戦をとる。
すなわち、こんな感じのデザインになるかなと思う

"状態遷移"
enum GAME_STATE
{
	TITLE,
	PLAY,
	CLEAR,
	GAMEOVER,
};
 
GAME_STATE state = GAME_STATE::TITLE; //状態の初期化
 
 
	while (System::Update())
	{
		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
    • 今回は使わないので、状態は書いておくけどスルー

とりあえずSiv3Dのプロジェクトを作って、こいつらを追加しちゃいます。
ほんとは、ソースコードとヘッダファイル分けたほうがいいけど、今回は全部メインに書いていきます。

ここでちょっとプロトタイプ宣言の話

宣言と定義は別だよねって話は授業の時にしたと思います。
プロトタイプ宣言は、関数の戻り値の型と、関数名、引数のリストを書いたものです。
(関数定義から、関数ブロック(処理内容)とってかわりにセミコロンつければいいよ。)
プロトタイプ宣言と、関数定義を書くと2度手間な気がするが、役割が違う。

  • プロトタイプ宣言
    • コンパイラや以降の関数に、こんな関数使うから知っといてとお知らせ(宣言)する
    • int sum(int a, double b); みたいな感じの書き方
    • 宣言しておくことによって、定義をどの順番でしても大丈夫になるし呼び出しもできる
    • (何しろ使うことを宣言してあるからね)
  • 関数定義
    • 関数の中ではこんな処理をして、戻り値として何を返すか、処理の内容を記述した部分
    • プログラム中に出てくる順番に書くならプロトタイプ宣言はいらない
"全体像"
enum GAME_STATE
{
	TITLE,
	PLAY,
	CLEAR,
	GAMEOVER,
};
 
//ゲーム状態の変数と、状態の初期化
GAME_STATE state = GAME_STATE::TITLE; 
 
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 });
 
	while (System::Update())
	{
		switch (state)
		{
		case TITLE:
			TitleUpdate();
			TitleDraw();
			break;
		case PLAY:
			PlayUpdate();
			PlayDraw();
			break;
		case CLEAR:
			ClearUpdate();
			ClearDraw();
			break;
		case GAMEOVER:
			break;
		default:
			break;
		}
	}
}

ここでやったのは、

  • GAME_STATEを用意
  • GAME_STATE型のゲーム状態を表すstateを用意
  • 各関数のプロトタイプ宣言
  • メインループの中で、switch-caseを使って、状態ごとの処理を実行
    • 今んところ、state=GAME_STATE::TITLEで初期化してあるんで、タイトル画面にしか行かない

関数の準備

プロトタイプ宣言まで完了したが、関数本体=定義、がないため呼び出せない。
関数宣言の中身を空にしてると、呼び出してもスルーするだけなのでエラーにはならない。
なので、とりあえず処理部を空にして定義をすべて作っておく。
プロトタイプ宣言をまるっとドラッグで選択して、右クリックして、「クイックアクションとリファクタリング」⇒「宣言/定義の作成」をえらぶと、空の定義が自動でできるので便利!
そうするとこの状態になる。

"関数定義まで"
//上のほう省略
//プロトタイプ宣言たち
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()
{
}

タイトル画面の作成

Mainの中で、タイトル画面用のFontAssetを2つほど用意して、タイトル画面を作ってみよう。
(みんな好きなように作っていいよ。この説明のタイトル画面は、とりあえずの説明用なので文字だけにします)
上の関数定義のソースコードにシラーっとFontAsset2個登録してるので参考にしてください。
(これは授業でやったことあるから大丈夫だよね? Mainの中でFontAssetを登録するとプログラムのどこからでも使えるFontが生成されるんだったね)

Fig. 2: タイトル画面の作成

とにかくこんな感じでタイトルを作る!
実際の処理は、TitleDrawの中に描画処理を書いてやります。
画像読んだり、アニメーションしたりいろいろやってみて!

"タイトル画面の描画"
void TitleUpdate()
{
	//特になし
}
 
void TitleDraw()
{
	String TitleStr = U"THE SLIDE PUZZLE";
	String StartStr = U"Push Here to Start GAME ";
 
	Point margin{ 3,3 };
	Point ajustSpace{ 0, -50 };
	Point startPoint = { Scene::Center().x, Scene::Center().y + 50 };
	FontAsset(U"TITLE_FONT")(TitleStr).drawAt(Scene::Center() + ajustSpace + margin, Palette::Lightslategray);
	FontAsset(U"TITLE_FONT")(TitleStr).drawAt(Scene::Center() + ajustSpace, Palette::Blueviolet);
	FontAsset(U"BUTTON_FONT")(StartStr).drawAt(TextStyle::Outline(0.2, Palette::Black),
		                                     startPoint + ajustSpace, Palette::White);
}

これでタイトル画面が表示されるか確認する!

グラフィック編 その2へ

  • game-engineer/classes/2023/something-else/summertime-special-cource/slidepuzle-siv3d-1.txt
  • 最終更新: 3年前
  • by root