DXライブラリでゲームタイトルからゲーム本編、データ選択、オプション(コンフィグ)など画面(シーン)を切り替える機能の実装。Unityでいうシーンの切り替えを実装するお話。
画面(シーン)を切り替えたい
色んなジャンルのゲームはあれどゲームを起動したら普通はタイトル(メニュー)画面になって、そこからゲーム本編(もしくはセーブデータ選択)あるいはオプション(コンフィグ)を選択して画面が切り替わると思う。
2Dアクションゲームだとこんな感じが多いか(ロード画面は非同期処理の話なのでここでは省略)。

このシーン切り替えはUnityだと土台がすでにあってちょっとコードを追加するだけで簡単にできるのだが、DXライブラリだと土台から自前で作る必要がある。
シーン切り替え機能の基本
基本はシーンを保存する変数を用意してswitch文で切り替えていく(別にif-else文でもいい)。そして分岐先に各シーンの処理を書く。以下一番簡単な例。
static int Scene; //シーンを保存する変数。初期化を忘れずに
void Scene_Management(){
switch (Scene) {
case 0:
//ここに各シーンの処理を書く
break;
case 1:
//ここに各シーンの処理を書く
break;
case 2:
//ここに各シーンの処理を書く
break;
//中略
default:
break;
}
}
実際はシーン切り替えの際の初期化や終了処理の他にもシーンごとの更新や画像・文字などの描画の必要があるので上のコードのようにswitch文1つでやると無理がある。
なのでシーンの初期化・更新・描画・終了処理を行う関数をそれぞれ作って制御していく。
サンプルコード
とりあえず先にコード例をペタリ。こちらのサイトのコードを参考にしました(ほとんどコピペですが)。
今回は以下のcpp/hファイルを追加した。ファイルの追加方法についてはこちらで。
- SceneMgr.cpp/h (シーン切り替えを行う)
- Menu.cpp/h (メニューあるいはタイトル画面の処理)
- Game.cpp/h (ゲームのメイン処理)
- Config.cpp/h (設定画面の処理)
SceneMng.cpp/h
SceneMng.h
#ifndef DEF_SCENEMNG_H
#define DEF_SCENEMNG_H
typedef enum {
eScene_Menu, //メニュー画面
eScene_Game, //ゲーム画面
eScene_Config, //設定画面
eScene_None, //無し
} eScene;
void SceneMng_Initialize();//初期化
void SceneMng_Finalize();//終了処理
void SceneMng_Update();//更新
void SceneMng_Draw();//描画
// 引数 nextScene にシーンを変更する
void SceneMng_ChangeScene(eScene nextScene);
#endif
enum(列挙体)を使って各画面に番号を割り当てている。上の例だと一番上から順に0、1、2、…となる。
SceneMng.cpp
#include "DxLib.h"
#include "SceneMng.h"
#include "Menu.h"
#include "Game.h"
#include "Config.h"
static eScene mScene = eScene_Menu; // 現シーンの管理変数
static eScene mNextScene = eScene_None; //次のシーン管理変数
static void SceneMng_InitializeModule(eScene scene);//指定モジュールを初期化する
static void SceneMng_FinalizeModule(eScene scene);//指定モジュールの終了処理を行う
//初期化
void SceneMng_Initialize() {
SceneMng_InitializeModule(mScene);
}
//シーンの更新
void SceneMng_Update() {
if (mNextScene != eScene_None) { //次のシーンがセットされていたら
SceneMng_FinalizeModule(mScene); //現在のシーンの終了処理を実行
mScene = mNextScene; //次のシーンを現在のシーンセット
mNextScene = eScene_None; //次のシーン情報をクリア
SceneMng_InitializeModule(mScene); //現在のシーンを初期化
}
//現在のシーンによって処理を分岐
switch (mScene) {
case eScene_Menu:
Menu_Update(); //メニュー画面の更新処理をする
break;
case eScene_Game:
Game_Update();
break;
case eScene_Config:
Config_Update();
break;
default:
break;
}
}
//各シーンの描画
void SceneMng_Draw() {
//現在のシーンによって処理を分岐
switch (mScene) {
case eScene_Menu:
Menu_Draw(); //メニュー画面の更新処理をする
break;
case eScene_Game:
Game_Draw();
break;
case eScene_Config:
Config_Draw();
break;
default:
break;
}
}
//引数 nextScene のシーンを変更する
void SceneMng_ChangeScene(eScene NextScene) {
mNextScene = NextScene; //次のシーンをセットする
}
//終了処理
void SceneMng_Finalize() {
SceneMng_FinalizeModule(mScene);
}
// 引数sceneモジュールを初期化する
static void SceneMng_InitializeModule(eScene scene) {
switch (scene) { //シーンによって処理を分岐
case eScene_Menu: //指定画面がメニュー画面なら
Menu_Initialize(); //メニュー画面の初期化処理をする
break;//以下略
case eScene_Game:
Game_Initialize();
break;
case eScene_Config:
Config_Initialize();
break;
}
}
// 引数sceneモジュールの終了処理を行う
static void SceneMng_FinalizeModule(eScene scene) {
switch (scene) { //シーンによって処理を分岐
case eScene_Menu: //指定画面がメニュー画面なら
Menu_Finalize(); //メニュー画面の終了処理処理をする
break;//以下略
case eScene_Game:
Game_Finalize();
break;
case eScene_Config:
Config_Finalize();
break;
}
}
シーンの初期化・更新・描画・終了処理を行う関数をそれぞれ作り、初期化以外の関数でswitch文を使って各シーンごとに処理を行う。
各シーンの処理につなげるヘッダーファイルの追加も忘れずに。
Menu.cpp/h
Menu.h
#ifndef DEF_MENU_H #define DEF_MENU_H void Menu_Initialize();//初期化 void Menu_Finalize();//終了処理 void Menu_Update();//更新 void Menu_Draw();//描画 #endif
Menu.cpp
#include "Menu.h"
#include "SceneMng.h"
#include "DxLib.h"
#include "Input.h"
const static int MENU_X = 32;
const static int MENU_Y = 32;
//初期化
void Menu_Initialize() {
}
//更新
void Menu_Update() {
if (Input_GetKeyboardDown(KEY_INPUT_G)) {//Gキーが押されていたら
SceneMng_ChangeScene(eScene_Game);//シーンをゲーム画面に変更
}
if (Input_GetKeyboardDown(KEY_INPUT_C)) {//Cキーが押されていたら
SceneMng_ChangeScene(eScene_Config);//シーンを設定画面に変更
}
}
//描画
void Menu_Draw() {
DrawString(MENU_X, MENU_Y, "メニュー画面です。", GetColor(255, 255, 255));
DrawString(MENU_X, MENU_Y + 20, "Gキーを押すとゲーム画面に進みます。", GetColor(255, 255, 255));
DrawString(MENU_X, MENU_Y + 40, "Cキーを押すと 設定画面に進みます。", GetColor(255, 255, 255));
}
//終了処理
void Menu_Finalize() {
}
メニュー(タイトル)画面の処理。こちらも初期化・更新・描画・終了処理を行う関数をそれぞれ作成している。
Game.cpp/h
Game.h
#ifndef DEF_GAME_H #define DEF_GAME_H void Game_Initialize();//初期化 void Game_Finalize();//終了処理 void Game_Update();//更新 void Game_Draw();//描画 #endif
Game.cpp
#include "Game.h"
#include "SceneMng.h"
#include "DxLib.h"
#include "Input.h"
const static int GAME_X = 32;
const static int GAME_Y = 32;
//初期化
void Game_Initialize() {
}
//更新
void Game_Update() {
if (Input_GetKeyboardDown(KEY_INPUT_M)) { //Mキーが押されていたら
SceneMng_ChangeScene(eScene_Menu);//シーンをメニューに変更
}
}
//描画
void Game_Draw() {
DrawString(GAME_X, GAME_Y, "ゲーム画面です。", GetColor(255, 255, 255));
DrawString(GAME_X, GAME_Y + 20, "Mキーを押すとメニュー画面に戻ります。", GetColor(255, 255, 255));
}
//終了処理
void Game_Finalize() {
}
ゲームのメイン処理を行う部分。こちらも初期化・更新・描画・終了処理を行う関数をそれぞれ作成している。
Config.cpp/h
Config.h
#ifndef DEF_CONFIG_H #define DEF_CONFIG_H void Config_Initialize();//初期化 void Config_Finalize();//終了処理 void Config_Update();//更新 void Config_Draw();//描画 #endif
Config.cpp
#include "Config.h"
#include "SceneMng.h"
#include "DxLib.h"
#include "Input.h"
const static int CONFIG_X = 32;
const static int CONFIG_Y = 32;
//初期化
void Config_Initialize() {
}
//更新
void Config_Update() {
if (Input_GetKeyboardDown(KEY_INPUT_M)) { //Mキーが押されていたら
SceneMng_ChangeScene(eScene_Menu);//シーンをメニューに変更
}
}
//描画
void Config_Draw() {
DrawString(CONFIG_X, CONFIG_Y, "設定画面です。", GetColor(255, 255, 255));
DrawString(CONFIG_X, CONFIG_Y + 20, "Mキーを押すとメニュー画面に戻ります。", GetColor(255, 255, 255));
}
//終了処理
void Config_Finalize() {
}
キーコンフィグや音量調整などを行う部分。上と同じく初期化・更新・描画・終了処理を行う関数をそれぞれ作成している。
System.cpp
上の追加が終わったらSystem.cppにコードを追加してシーン切り替えができるようにする。
//以下のヘッダーファイルをインクルード
#include "SceneMng.h"
//DXライブラリなどの初期化
bool System_Intialize() {
// 略
//シーンの初期化(追加部分)
SceneMng_Initialize();
return true;
}
//描画処理
void System_Draw() {
DrawFormatString(0, 0, GetColor(255, 255, 255), "ESCキーで終了します");
}
//終了処理
void System_Finalize() {
//シーンの終了処理(追加部分)
SceneMng_Finalize();
DxLib_End(); // DXライブラリ終了処理
}
//メインループ
bool System_MainLoop() {
while (1) {
//裏画面を表画面に反映, メッセージ処理, 画面クリアの3つが正しく動作してるか確認
//どれか1つでも異常があれば強制終了
if (!(ScreenFlip() == 0)) return false;
if (!(ProcessMessage() == 0)) return false;
if (!(ClearDrawScreen() == 0)) return false;
//フレームレート制御
FpsControll_Update();
//シーンの更新(追加部分。エラー処理・フレームレート制御の更新後に)
SceneMng_Update();
//キーボードの入力状態を更新
Input_UpdateKeyboard();
//各シーンの描画
SceneMng_Draw();
//テキストなどの描画
System_Draw();
//フレームレート表示
FpsControll_Draw();
//フレームレート待機
FpsControll_Wait();
//ESCキーが30フレーム以上押されていれば終了
//押された瞬間で判定する場合は if(Input_GetKeyboardDown())
//離された瞬間で判定する場合は if(Input_GetKeyboardUp()) とする
if (Input_GetKeyboard(KEY_INPUT_ESCAPE) > 30) {
break;
}
}
return true;
}
//ソフトの処理を実行
bool System_Main() {
//初期化 失敗(false)なら終了
if (!System_Intialize()) return false;
//メインループ
if (!System_MainLoop()) return false;
//終了処理
System_Finalize();
//正常終了
return true;
}
補足
今回追加したソースファイルに共通して書いた
- 初期化を行う ***_Initialize()
- 更新を行う ***_Update()
- 描画を行う ***_Draw()
- 終了処理を行う ***_Finalize()
これらの関数は他のソースファイルを書く時(例えばプレイヤーや敵関連の処理をするソースファイル)も必要となる関数なので忘れないように。
ちなみにC++だとクラスを使うことでswitch文のところをもっとスマートに書けます。が、ここではC言語で書くことを前提しているので割愛。

コメント