ページ 11

ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月22日(金) 19:00
by mizuna
 私は現在、DXライブラリを用いてゲームの製作を行っています。
その中で、シーンの切り換えの実装にてこずっているため質問させていただきます。

 OSはWindows、エディタはVisual Studio 2022を使用しています。
ライブラリは先ほども書いた通り、DXライブラリを使用しています。
 C言語については学校の講義で基本的な部分については学んでいます。
どれぐらい理解しているかの指標となるかは分かりませんが、構造体やポインタ渡しについては
理解しているつもりです。
一方で、C++については使用するのは今回が初めてで、クラスについて理解はできていません。
また、ゲーム製作の経験はUnityで簡単なアクションゲームを作った程度です。

 以下に示すようなMainファイルとSceneManagerファイル、Titleファイル、SelectSceneファイルを作成し、
MainファイルからSceneManagerファイルのScene_Manage関数を呼び出し、
Scene_Manage関数からさらにTitleファイルやSelectSceneファイルの関数を呼び出すことで
ゲームのシーンの切り換えを行おうとしています。
※以下では今回の質問に関係のない処理について省いて書いています。

コード:

/* === Main.cpp === */

#include "DxLib.h"
#include "SceneManager.h"
#include "Title.h"

int Game_Init(void);		// 諸々の初期化を行う関数
void Game_End(void);	// 諸々の終了処理を行う関数


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	SetOutApplicationLogValidFlag(false);							// Log.txtの出力の拒否
	ChangeWindowMode(true);									// ウィンドウモードに設定
	SetGraphMode(GameWindowWidth, GameWindowHeight,GameColor);	// 解像度の設定
	SetWindowSize(GameWindowWidth, GameWindowHeight);				// ウィンドウサイズの設定
	SetMainWindowText(GameTitle);								// ウィンドウのタイトル
	SetBackgroundColor(0, 0, 0);									// ウィンドウの背景色
	SetWaitVSyncFlag(true);										// 垂直同期の設定
	SetAlwaysRunFlag(true);										// 非アクティブでもゲーム実行


	/* +++ ゲームの初期化 +++ */
	if (Game_Init() == -1)	// 初期化に失敗したなら
	{
		return -1;	// ソフトを終了する
	}

	SetDrawScreen(DX_SCREEN_BACK);	// 裏画面に描画(ダブルバッファリング)


	/* +++ ゲームループ(無限ループ) +++ */
	while (true)
	{
		/* +++ メッセージを処理し続ける(マウスやキーの入力を受け付け続ける) +++ */
		if (ProcessMessage() != 0)		// エラー発生やウィンドウが閉じられた
		{
			break;	// ループの終了(ゲームの終了)
		}

		/* +++ 画面を書き換える(画面を消去する) +++ */
		if (ClearDrawScreen() != 0)		// エラー発生
		{
			break;	// ループの終了(ゲームの終了)
		}

		/* +++ キー入力の取得 +++ */
		if (Key_class.Key_Check() != 0)
		{
			break;
		}


		/* +++ ゲームの動作 +++ */
		{
			/* +++ SceneManageファイルのScene_Manage関数を実行 +++ */
			if (Scene_class.Scene_Manage() == -1)
			{
				break;
			}
		}

		/* +++ 裏画面を表画面に描画 +++ */
		if (ScreenFlip() != 0)		// エラー発生
		{
			break;	// ループの終了(ゲームの終了)
		}

		/* +++ Enterキーを押したらゲームループ終了 +++ */
		if (Key_class.key[KEY_INPUT_RETURN] == 1)
		{
			break;
		}
	}

	Game_End();		// ゲームの終了

	return 0;	// ソフト終了
}

/* +++ ゲームの初期化処理(ここに諸々の初期化処理を集める) +++ */
int Game_Init(void)
{
	/* --- Dxライブラリ初期化処理 --- */
	if (DxLib_Init() == -1)		// 初期化に失敗したなら
	{
		return -1;	// -1を返す(初期化関数を初期化失敗で終わらせる)
	}

	/* --- シーン処理の初期化 --- */
	if (Scene_class.Scene_Init(TitleScene) == -1)		// 初期化に失敗したら
	{
		return -1;	// -1を返す(初期化関数を初期化失敗で終わらせる)
	}

	return 0;	// 諸々の初期化関数の終了(正常終了)
}

/* +++ ゲームの終了処理(ここに諸々の終了処理を集める) +++ */
void Game_End(void)
{
	DxLib_End();	// DxLibの終了

	return;
}

コード:

/* === SceneManager.h === */

#ifndef SCENEMANAGER_H	// 2重インクルード防止
#define SCENEMANAGER_H

#include "DxLib.h"
#include "Title.h"

#define GameSceneNumber 4		// ゲームシーンの数

#define SceneChangeInterval 60	// シーン切換のインターバルフレーム

/* --- ゲームシーンの列挙 --- */
enum GameScene
{
	TitleScene,	// タイトル画面
	SelectScene,	// キャラセレクト画面
};

/* --- シーンマネージャーのクラス --- */
class SceneManager
{
public:
	GameScene NowGameScene;	// 現在のシーン
	GameScene NextGameScene;	// 次のシーン

	int SceneChangeFrameCount;		// 直前のシーン切換からの経過フレーム

	/* --- シーンの初期化関数 --- */
	int Scene_Init(GameScene sceneName)
	{
		SceneChangeFrameCount = 0;	// シーン切換からの経過フレームをリセット

		/* +++ 指定したシーンの初期化関数を呼び出す +++ */
		switch (sceneName)
		{
		case TitleScene:
			/* +++ タイトルシーンの場合 +++ */

			if (Title_class.Title_Init() == -1)	// タイトルシーンの初期化に失敗したら
			{
				return -1;	// シーンの初期化失敗(-1を返す)
			}
			break;
		case SelectScene:
			/* +++ キャラセレクトシーンの場合 +++ */

			if (SelectScene_class.SelectScene_Init() == -1)	// タイトルシーンの初期化に失敗したら
			{
				return -1;	// シーンの初期化失敗(-1を返す)
			}
			break;
		default:
			break;
		}

		return 0;
	}

	/* --- シーンの処理を管理する関数 --- */
	int Scene_Manage()
	{
		if (NextGameScene != NowGameScene)	// 現在のシーンと次のシーンが異なる(シーンが切り換わる)
		{
			/* +++ 切り換わり先のシーンに応じて引数を変えてシーン初期化関数を実行 +++ */
			switch (NextGameScene)
			{
			case TitleScene:
				/* +++ タイトルシーンの場合 +++ */

				/* +++ タイトルシーンを引数にシーンの初期化関数を実行 +++ */
				if (Scene_Init(TitleScene) == -1)		// シーン初期化に失敗した
				{
					return -1;	// シーンの処理の失敗(-1を返す)
				}
				break;
			case SelectScene:
				/* +++ キャラセレクトシーンの場合 +++ */

				/* +++ キャラセレクトシーンを引数にシーンの初期化関数を実行 +++ */
				if (Scene_Init(SelectScene) == -1)		// シーン初期化に失敗した
				{
					return -1;	// シーンの処理の失敗(-1を返す)
				}
				break;
			default:
				break;
			}

			NowGameScene = NextGameScene;	// 切換先のシーンを現在のシーンに設定
		}
		else
		{
			/* +++ 現在のシーンに応じて引数を変えてシーンの処理関数を実行 +++ */
			switch (NowGameScene)
			{
			case TitleScene:
				/* +++ タイトルシーンの場合 +++ */

				Title_class.Title_Manage();	// タイトルシーンの処理関数を実行
				break;
			case SelectScene:
				/* +++ キャラセレクトシーンの場合 +++ */

				SelectScene_class.SelectScene_Manage();	// キャラセレクトシーンの処理関数を実行
				break;
			default:
				break;
			}

			SceneChangeFrameCount++;	// シーン切換からの経過フレームを1増やす
		}

		return 0;	// シーン処理の管理関数の終了(0を返す)
	}

private:

};

extern SceneManager Scene_class;	// シーンマネージャー

#endif

コード:

/* === Title.h === */

#include "DxLib.h"
#include "Color.h"
#include "SceneManager.h"

/* --- タイトルクラス --- */
class Title
{
public:

	/* --- タイトルシーンの初期化関数 --- */
	int Title_Init()
	{
		/* 今後ここに初期化処理を書いていく */
	
		return 0;	// タイトルシーンの初期化の終了(0を返す)
	}

	/* --- タイトルシーンの処理の管理関数 ---- */
	void Title_Manage()
	{
		Title_Process();		// タイトルシーンの処理
		Title_Draw();		// タイトルシーンの初期化

		return;
	}

	/* --- タイトルシーンの処理関数 --- */
	void Title_Process()
	{
		/* 何かしらの条件分岐 */
		{
			// 次のシーンにキャラセレクトシーンを設定
			Scene_class.NextGameScene = SelectScene;
		}
	
		return;
	}

	/* +++ タイトルシーンの描画関数 +++ */
	void Title_Draw()
	{
		DrawString(305, 100, "Game Title", Color_class.Color_White);
		DrawString(300, 500, "Press Start", Color_class.Color_White);
		
		return;
	}

private:

};

extern Title Title_class;

コード:

/* === SelectScene.h === */

#include "DxLib.h"
#include "Color.h"
#include "SceneManager.h"

/* --- キャラセレクトシーンクラス --- */
class SelectScene
{
public:

	/* --- キャラセレクトシーンの初期化関数 --- */
	int SelectScene_Init()
	{
		/* 今後ここに初期化処理を書いていく */
	
		return 0;	// タイトルシーンの初期化の終了(0を返す)
	}

	/* --- キャラセレクトシーンの処理の管理関数 ---- */
	void SelectScene_Manage()
	{
		SelectScene_Process();	// キャラセレクトシーンの処理
		SelectScene_Draw();		// キャラセレクトシーンの初期化

		return;
	}

	/* --- キャラセレクトシーンの処理関数 --- */
	void SelectScene_Process()
	{
		/* 何かしらの条件分岐 */
		{
			// 次のシーンにタイトルシーンを設定
			Scene_class.NextGameScene = TitleScene;
		}
	
		return;
	}

	/* +++ キャラセレクトシーンの描画関数 +++ */
	void SelectScene_Draw()
	{
		DrawString(305, 100, "Character Select", Color_class.Color_White);
		
		return;
	}

private:

};

extern SelectScene SelectScene_class;
 このプログラムのデバッグを開始したところ、「C2065 'Title_class':定義されていない識別子です 」や
「C2065 'SelectScene_class':定義されていない識別子です 」といったエラーが出ました。
このエラーが出ている原因は何なのか、どうすれば解決できるかについてお教えいただきたいです。
また、より良い実装の仕方があれば、その方法についても教えていただきたいです。

 文章が長く、読みづらいことと思いますが、なにとぞご返信よろしくお願いします。

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月23日(土) 01:12
by usao
> C2065 'Title_class':定義されていない識別子です

そのエラーが出ている個所では "Title_class" というのが一体何なのかが不明,ということです.

C/C++ では何か(変数とか)を使う場所では,その何かの宣言が見えている(その場所よりも前に宣言がある)必要があります.
例えば Title_class という変数を触っている個所よりも前に Title_class の宣言が必要,みたいな話ですね.

例えば

コード:

int WINAPI WinMain( 略 )
{
  CatNapDisaster = 77;
}
とか書いたら,きっとコンパイラは「その "CatNapDisaster" ってのは何なの? 」っていうエラーを出すでしょう.
それと同じことが起きているので? っていう話です.
記述の順序的にそこらへんがうまくいっていない個所があるということでしょう.

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月23日(土) 01:30
by usao
オフトピック
あと,ここってもう全然人がいないので,どこか別の「今現在使われている場所」で質問することを検討した方が良いのではないかと.

私は,今何故か数か月ぶり(?)にたまたま通りかかっただけで,以降,ここを見に来るというわけでもないので,
他の人が来ない様子であれば,上記を考えることをお勧めします.

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月23日(土) 18:20
by mizuna
返信ありがとうございます。

調べてみたところ、SceneManager.hとTitle.hが互いに互いをインクルードすることで
2重インクルードになったことが原因のようでした。
よって、現在は2重インクルードを解消する方法を模索しています。

今後ほかの返信がないようであれば、おっしゃられたように別の媒体を利用することも
検討しようと思います。

ひとまず、アドバイスいただきありがとうございました。

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月23日(土) 18:23
by temee
すべてのヘッダファイルのextern〜〜;を削除し,
Title_classと書かれている場所すべてをTitleに,
Scene_classと書かれている場所すべてをSceneManagerに,
SelectScene_classと書かれている場所すべてをSelectSceneに,
してみてください!^_^

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月24日(日) 11:12
by mizuna
temee さんが書きました:
2日前
すべてのヘッダファイルのextern〜〜;を削除し,
Title_classと書かれている場所すべてをTitleに,
Scene_classと書かれている場所すべてをSceneManagerに,
SelectScene_classと書かれている場所すべてをSelectSceneに,
してみてください!^_^
ご提案いただいた修正を試してみたのですが、

コード:

Title.Title_Init()
のような書き方をしますと、「構文エラー: '.'」と出てしまいました。

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月24日(日) 16:30
by usao
> のような書き方をしますと、「構文エラー: '.'」と出てしまいました。

なんとなく気になって見に来たら,残念なことになってますね.
読んで意味が正確に分からない話(文字通りの変更を行えばコンパイル通らないだろうっていう話)を盲目的に実施する必要はないんじゃないかな,と思うよ.

---

示されているコードを見た限りだと「単に,メソッドの実装をヘッダ内に書かなきゃいいんじゃない?」という感じがするけど.

すなわち,
普通にメソッドの実装をそれ用の .cpp (例えば SceneManager.cpp だとか)に書くようにすれば,
「ヘッダの中でヘッダをinclude」というのを減らせる(→結果として互いにインクルードみたいな形を解消できる)んじゃないかな,と.

(例:「SceneManager のメソッドの内側の処理が Title 型を必要とするから → Title.h を include している」という話ならば,そのメソッドの実装を .cpp に移すならば Title.h の include も同時に .cpp に移すことができる.)

Re: ゲームのシーン切り換えの処理について質問です

Posted: 2025年8月25日(月) 16:13
by mizuna
返信ありがとうございます。

関数の処理の記述を.hファイルから.cppファイルに移し、
.cppファイルで各種ヘッダファイルのインクルードを行うようにしたところ、
エラーが起きずに望んでいた動作が実装できました。

親切なご返信、まことにありがとうございました。
今後はこのような問題が起こらないよう、C++について書籍などで勉強しながらゲーム製作を続けていきたいと思います。