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

このシーン切り替えはUnityだと土台がすでにあってちょっとコードを追加するだけで簡単にできるのだが、DXライブラリだと土台から自前で作る必要がある。
シーン切り替え機能の基本
基本はシーンを保存する変数を用意してswitch文で切り替えていく。そして分岐先に各シーンの処理を書く。
static int Scene; //シーンを保存する変数。初期化を忘れずに
void Scene_Management(){
switch (Scene) {
case 0:
//ここに各シーンの処理を書く
break;
case 1:
//ここに各シーンの処理を書く
break;
case 2:
//ここに各シーンの処理を書く
break;
//中略
default:
break;
}
}実際は切り替えの際に画像などのデータの読み込みや破棄などをすることになるので上のコードのように単純にswitch文1つでやるには無理がある。
シーン切り替え機能の実装
とりあえず先にコード例をペタリ。こちらのサイトのコードを参考にしました(ほとんどコピペですが)。
前回までに作ったSystem.cpp/h, Input.cpp/hの他に以下のファイルを追加してます。
- SceneMgr.cpp
- SceneMgr.h
- Menu.cpp
- Menu.h
- Game.cpp
- Game.h
- Config.cpp
- Config.h
ファイルの追加方法については前回の記事参考。
SceneMgr.h
#ifndef DEF_SCENEMGR_H
#define DEF_SCENEMGR_H
typedef enum {
eScene_Menu, //メニュー画面
eScene_Game, //ゲーム画面
eScene_Config, //設定画面
eScene_None, //無し
} eScene;
void SceneMgr_Initialize();//初期化
void SceneMgr_Finalize();//終了処理
void SceneMgr_Update();//更新
void SceneMgr_Draw();//描画
// 引数 nextScene にシーンを変更する
void SceneMgr_ChangeScene(eScene nextScene);
#endif
typedef enumの中にあるeScene ***が各画面(シーン)。この中に各画面を追加していく。
SceneMgr.cpp
#include "DxLib.h"
#include "SceneMgr.h"
#include "Menu.h"
#include "Game.h"
#include "Config.h"
static eScene mScene = eScene_Menu; // 現シーンの管理変数
static eScene mNextScene = eScene_None; //次のシーン管理変数
static void SceneMgr_InitializeModule(eScene scene);//指定モジュールを初期化する
static void SceneMgr_FinalizeModule(eScene scene);//指定モジュールの終了処理を行う
//初期化
void SceneMgr_Initialize() {
SceneMgr_InitializeModule(mScene);
}
//シーンの更新
void SceneMgr_Update() {
if (mNextScene != eScene_None) { //次のシーンがセットされていたら
SceneMgr_FinalizeModule(mScene); //現在のシーンの終了処理を実行
mScene = mNextScene; //次のシーンを現在のシーンセット
mNextScene = eScene_None; //次のシーン情報をクリア
SceneMgr_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 SceneMgr_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 SceneMgr_ChangeScene(eScene NextScene) {
mNextScene = NextScene; //次のシーンをセットする
}
//終了処理
void SceneMgr_Finalize() {
SceneMgr_FinalizeModule(mScene);
}
// 引数sceneモジュールを初期化する
static void SceneMgr_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 SceneMgr_FinalizeModule(eScene scene) {
switch (scene) { //シーンによって処理を分岐
case eScene_Menu: //指定画面がメニュー画面なら
Menu_Finalize(); //メニュー画面の終了処理処理をする
break;//以下略
case eScene_Game:
Game_Finalize();
break;
case eScene_Config:
Config_Finalize();
break;
}
}
switch文を使い、mSceneの値でシーンを切り替える。シーンを追加する場合はめんどうだが各switch文に処理を追加する。各シーンの処理につなげるヘッダーファイルの追加も忘れずに。
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 "SceneMgr.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キーが押されていたら
SceneMgr_ChangeScene(eScene_Game);//シーンをゲーム画面に変更
}
if (Input_GetKeyboardDown(KEY_INPUT_C)) {//Cキーが押されていたら
SceneMgr_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.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 "SceneMgr.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キーが押されていたら
SceneMgr_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.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 "SceneMgr.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キーが押されていたら
SceneMgr_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
#include "DxLib.h"
#include "System.h"
#include "Input.h"
#include "SceneMgr.h"
//DXライブラリなどの初期化
bool System_Intialize() {
// ウィンドウモードに設定
ChangeWindowMode(TRUE);
// DXライブラリ初期化処理
if (DxLib_Init() < 0) {
//エラーなら終了する
return false;
}
//描画先を裏画面にする
SetDrawScreen(DX_SCREEN_BACK);
// 画面モードの変更
SetGraphMode(WINDOWSIZE_X, WINDOWSIZE_Y, COLORBITNUM);
//シーンの初期化
SceneMgr_Initialize();
return true;
}
//描画処理
void System_Draw() {
DrawFormatString(0, 0, GetColor(255, 255, 255), "ESCキー長押しで終了します");
}
//終了処理
void System_Finalize() {
//シーンの終了処理
SceneMgr_Finalize();
// DXライブラリ終了処理
DxLib_End();
}
//メインループ
bool System_MainLoop() {
while (1) {
//裏画面を表画面に反映, メッセージ処理, 画面クリアの3つが正しく動作してるか確認
//どれか1つでも異常があれば強制終了
if (!(ScreenFlip() == 0)) return false;
if (!(ProcessMessage() == 0)) return false;
if (!(ClearDrawScreen() == 0)) return false;
//キーボードの入力状態を更新
Input_UpdateKeyboard();
//シーンの更新
SceneMgr_Update();
System_Draw();
//シーンの描写
SceneMgr_Draw();
//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()はデータの破棄を行う関数。InitializeおよびFinalizeはこの段階では書くことがないので空ですが重要な機能なので忘れないように。
ちなみにC++だとクラスを使うことでswitch文のところをもっとスマートに書けます。がここはCで書くのを前提としているので省略。


コメント