ホームへ戻る

 sp4章 メニュー画面の作り方.2

 前回に引き続き、メニュー画面を作っていきます。
本章では、前回のコードをシーンごとにファイル分割し、少しアレンジをしてみます。
それではまず必要なファイルを追加しましょう。

ソリューションエクスプローラーの「ソースファイル」を右クリックして「追加」>「新しい項目」を選びます。

「ヘッダーファイル」を選択し、「名前」欄に「Menu.h」を入力して「追加」をクリックします。
これで「Menu.h」のファイルが作られ、追加されたと思います。これを今回必要な分だけ繰り返します。
今回必要なファイルは

・Menu.h
・Menu.cpp
・Game.h
・Game.cpp
・Config.h
・Config.cpp
・SceneMgr.h
・SceneMgr.cpp

です。ファイルが多くてメンドクサイですが、頑張って追加して下さい。
面倒な人は、ページ下部のリンクで完成したプロジェクトを配布しているのでそこからDLして下さい。
# SceneMgrはシーン管理部を意味し「SceneManager」の略です。"Mgr"は何かのモジュールを管理する場合によく命名されます。

全部追加が終わったら、このように表示されているかと思います。

さて、今回はメインループからの呼び出しをこのように変更してみようと思います。

「メニュー、ゲーム、設定」という3種類のシーンがあり、そのシーンを管理部が管理している仕組みです。
また、モジュールはすべて「更新」と「描画」に処理を分けています。

# この辺の作りは「ゲームプログラミング設計」で説明したのと同じです。
# これから作るモジュール下の関数命名規則も設計も、先の設計に基づきますので、まだそちらを読んでない方は
# そちらを先にご覧ください。

そして、管理部下にあるモジュールは、一つだけ上のモジュールにメッセージを送る(関数コール)できるようにします。
管理部は伝えられたシーンを以降はコールするようになります。
例えばゲームモジュールから「次からメニューのシーンに切り替えて!」と通知すると
以後の処理はメニューモジュールへ行われるようになります。

メニュー画面やゲーム画面や設定画面を処理中、任意のタイミングで、ほかの画面に変更するには、
管理部に「シーンの変更」をお願いします。

それではこれを実現するためのソースコードを紹介します。
モジュールが合計4つあってコードの量は多いですが、非常に簡単なコードを安直に書いているだけです。

# pragma once とは二重includeを防止するためのものでヘッダファイルの最初に書くおまじないだと思ってOKです。
# 規格上保証された書き方ではありませんが、VC++2010を使っている限り問題ありません。


↓Config.h↓


#pragma once

//更新
void Config_Update();

//描画
void Config_Draw();

↓Config.cpp↓


#include "Config.h"
#include "SceneMgr.h"
#include "DxLib.h"

//更新
void Config_Update(){
    if(CheckHitKey(KEY_INPUT_ESCAPE)!=0){//Escキーが押されていたら
        SceneMgr_ChangeScene(eScene_Menu);//シーンをメニューに変更
    }
}

//描画
void Config_Draw(){
    DrawString(0, 0,"設定画面です。",GetColor(255,255,255));
    DrawString(0,20,"Escキーを押すとメニュー画面に戻ります。",GetColor(255,255,255));
}

↓Game.h↓ ゲーム画面


#pragma once

//更新
void Game_Update();

//描画
void Game_Draw();

↓Game.cpp↓


#include "Game.h"
#include "SceneMgr.h"
#include "DxLib.h"

//更新
void Game_Update(){
    if(CheckHitKey(KEY_INPUT_ESCAPE)!=0){ //Escキーが押されていたら
        SceneMgr_ChangeScene(eScene_Menu);//シーンをメニューに変更
    }
}

//描画
void Game_Draw(){
    DrawString(0, 0,"ゲーム画面です。",GetColor(255,255,255));
    DrawString(0,20,"Escキーを押すとメニュー画面に戻ります。",GetColor(255,255,255));
}

↓Menu.h↓ メニュー画面


#pragma once

//更新
void Menu_Update();

//描画
void Menu_Draw();

↓Menu.cpp↓


#include "Menu.h"
#include "SceneMgr.h"
#include "DxLib.h"

//更新
void Menu_Update(){
    if(CheckHitKey(KEY_INPUT_G)!=0){//Gキーが押されていたら
        SceneMgr_ChangeScene(eScene_Game);//シーンをゲーム画面に変更
    }
    if(CheckHitKey(KEY_INPUT_C)!=0){//Cキーが押されていたら
        SceneMgr_ChangeScene(eScene_Config);//シーンを設定画面に変更
    }
}

//描画
void Menu_Draw(){
    DrawString(0, 0,"メニュー画面です。",GetColor(255,255,255));
    DrawString(0,20,"Gキーを押すとゲーム画面に進みます。",GetColor(255,255,255));
    DrawString(0,40,"Cキーを押すと 設定画面に進みます。",GetColor(255,255,255));
}

↓SceneMgr.h↓ シーン管理部


#pragma once

typedef enum {
    eScene_Menu,    //メニュー画面
    eScene_Game,    //ゲーム画面
    eScene_Config,  //設定画面
} eScene ;

//更新
void SceneMgr_Update();

//描画
void SceneMgr_Draw();

// 引数 nextScene にシーンを変更する
void SceneMgr_ChangeScene(eScene nextScene);

↓SceneMgr.cpp↓


#include "DxLib.h"
#include "Config.h"
#include "Game.h"
#include "Menu.h"
#include "SceneMgr.h"

static eScene Scene = eScene_Menu;    //シーン管理変数

//更新
void SceneMgr_Update(){
    switch(Scene){       //シーンによって処理を分岐
    case eScene_Menu:    //現在の画面がメニューなら
        Menu_Update();   //メニュー画面の更新処理をする
        break;//以下略
    case eScene_Game:
        Game_Update();
        break;
    case eScene_Config:
        Config_Update();
        break;
    }
}

//描画
void SceneMgr_Draw(){
    switch(Scene){      //シーンによって処理を分岐
    case eScene_Menu:   //現在の画面がメニュー画面なら
        Menu_Draw();    //メニュー画面の描画処理をする
        break;//以下略
    case eScene_Game:
        Game_Draw();
        break;
    case eScene_Config:
        Config_Draw();
        break;
    }
}

// 引数 nextScene にシーンを変更する
void SceneMgr_ChangeScene(eScene NextScene){
    Scene = NextScene;
}

↓main.cpp↓


#include "DxLib.h"
#include "SceneMgr.h"

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
    ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

    while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 ){//画面更新 & メッセージ処理 & 画面消去

        SceneMgr_Update();  //更新
        SceneMgr_Draw();    //描画

    }
        
    DxLib_End(); // DXライブラリ終了処理
    return 0;
}

# 「static」と先頭についている変数はこのソースファイルからしかアクセスできないことを意味します。
# 外から参照する必要が無い変数や関数はアクセスさせないようにすることをC++ではカプセル化や隠蔽化といいます。
# staticは積極的に使っていき、安全なコーディングを意識しましょう。

ではまず実行して動きを確認してみて下さい。

本プログラムのプロジェクト一式はこちらからDLできます。

実行結果

プログラムはmain.cppのメインループをよりSceneMgrが呼ばれ、
それ以降、Sceneに設定されている「メニュー、ゲーム、設定」のどれかのモジュールが
コールされる仕組みになっていることをソースコード上で確認して下さい。
シーンの変更は、例えばメニュー画面で


void Menu_Update(){
    if(CheckHitKey(KEY_INPUT_G)!=0){//Gキーが押されていたら
        SceneMgr_ChangeScene(eScene_Game);//シーンをゲーム画面に変更
    }
    if(CheckHitKey(KEY_INPUT_C)!=0){//Cキーが押されていたら
        SceneMgr_ChangeScene(eScene_Config);//シーンを設定画面に変更
    }
}

このように書いて実現しています。
Gキーが押されていたらSceneMgrに「ゲーム画面に切り替えて下さい」とお願いします。同様に
Cキーが押されていたらSceneMgrに「設定画面に切り替えて下さい」とお願いします。
SceneMgrはScene変数に指定されたシーンの識別子を入れ、以降管理部はそのシーンのモジュールをコールするようになります。

このようにすると、シーンの管理が可能になります。
しかし、実際にシーンが切り替わる時は、
モジュールごとに素材のロードやパラメータの設定などの「Initialize処理」が必要になり、
終わる時は素材の解放などの「Finalize処理」が必要になります。
次の章ではその仕組みを追加します。

→分からないことがあれば掲示板で質問して下さい


- Remical Soft -