11.チュートリアル

1100.EmptyProject

 これから、フルバージョンのサンプルの説明を始めます。このサンプルはFullSample100というディレクトリに含まれます。
 BaseCrossDx11VS2017.sln、BaseCrossDx11VS2019.slnというソリューションを開くとDx11版が起動します。
 BaseCrossDx12VS2017.sln、BaseCrossDx12VS2017.slnというソリューションを開くとDx12版が起動します。
 VS2017、VS2019はVisualStdioのバージョンです。

 実行結果は以下のような画面が出ます。濃い青の画面です。

 

図1100a

 

EmptyProjectとは

 ゲームに限らず、何らかのアプリケーションを作成する場合、どこから始めるかが重要になります。
 BaseCross64には多くのサンプルが用意されていますので、それらを土台にすることもできますが、そうした場合、それぞれのサンプルの実装にどうしても引きずられる格好になります。
 それでは本末転倒なので、このサンプルEmptyProjectから作成を始めるのをお勧めします。
 EmptyProjectはその名の通り空っぽのプロジェクトです。BaseCross64の機能を使う準備はできていますが、ほとんどなにも実装されません。
 例えばほかのサンプルに参考にしたいオブジェクトやコードがあれば、このEmptyProjectに必要なものだけ実装していくべきです。そのほうが、独自の実装を組み込みやすくなりますし、何よりも勉強になります。

ソース解説

 このソリューションを開くと、GameSourcesというプロジェクトがあります。これがゲーム本体と言えます。
 このプロジェクトはHeadersというフィルタとSourcesというフィルタに分かれています。
 以下はDx11版のソリューションの内容です。

 

図1100b

 

 以下はDx12版のソリューションの内容です。

 

図1100c

 

 このように、Dx11とDx12の違いライブラリDx11FullLibとDx12FullLib、そしてDx11LibとDx12Libのみ違います。
 Fullがつくかつかないかはフルバージョンかシンプルバージョンかの違いです。シンプルバージョンの場合は、違いはDx11LibとDx12Libのみとなります。
 ただシンプルバージョンの場合は、個別の実装の部分で描画処理を記述しなければならなので、おのずから互換性は薄くなります。
 ほかの部分のライブラリDxLib、DxShaders、SharedLibそしてGameSourcesは共有して使います。

 サンプルではおおむねファイルはこのような構造になっています。
 それぞれのファイルの役割を簡単に記します。

Scene.h/cpp

 BaseCross64では、ゲーム上のまとめ役のオブジェクトとしてAppクラスシーンそしてステージという3つ階層で管理します。
 Scene.h/cppはそのシーンです。ここではステージを管理し切り替えます。

GameStage.h/cpp

 これはゲームステージです。シーンにより管理されます。サンプルでは主にゲーム画面を実装しますが、通常はメニュー画面リザルト画面など、複数のステージで構成されます。その場合、メニューなどもGameStage.h/cppに記述することも可能ですが、複数のファイルヘッダとcppのペアを作成し、それぞれ別のステージを管理することも可能です。

Character.h/cpp

 ここには、主にプレイヤー以外のステージに配置されるオブジェクトを記述します。実際のゲームでは、サンプルのように単純ではなく、いろんなタイプのオブジェクトを記述する必要があります。ですので、Character.h/cppのようなヘッダとcppのペアを作成して、そこに記述します。
 例えば敵キャラなどはEnemy.h/cppなどのファイルを記述するとわかりやすいでしょう。

Player.h/cpp

 ここにはプレイヤーを記述します。プレイヤーは、コントローラキーボード、マウスなどの反応してリアルタイムに細かな処理を行う必要があります。当然、ファイルは大きくなります。ですのでCharacter.h/cppとは別に作成します。
 実際のゲームでは必ずしもPlayer.h/cppというファイル名でなくてもかまいません。

ProjectBehavior.h/cpp

 例えば敵キャラなどに、AI処理を加える場合、敵のタイプは違っていても、同じような行動Behaviorを使いまわしたいことがあります。
 敵ごとに似たようなソースを記述するのは無駄ですし、効率もよくありません。こんな時に、BaseCross64では行動クラスというのを作成することができます。つまり行動のアリゴリズムのみをクラス化し、それを使いまわせる機能です。
 ProjectBehavior.h/cppには、このようにプロジェクトに共通して利用できるアルゴリズムを記述します。
 行動クラスはあらかじめライブラリに組み込まれているのもあり、それらの使用方法や独自の行動クラスの作成方法は、別のサンプルで紹介します。
 また行動クラスばかりではなく、共通に使用できる関数なども記述してもよいでしょう。

ProjectShader.h/cpp

 ここにはコンテンツで定義したシェーダークラスを記述します。シェーダーは特別なファイルです。ライブラリでも基本的なシェーダは実装されてますが、独自に作成する場合.hlslファイルを作成します。このProjectShader.h/cppは、これらのシェーダをゲーム内で使用するために作成する中間的なC++ファイルを記述します。
 シェーダそのもの(.hlslファイル)を作成する場合はフィルタを新しく作成してその中に記述することをお勧めします。

Project.h

 ここにはコンテンツで使用するヘッダをまとめておきます。こうしておくと、コンテンツ内の記述はProject.hをインクルードすればすべてのファイル内に記述されたオブジェクトを使用することができます。
 ただしC++には前方宣言という文法があり、使用するためにはあらかじめ宣言が(場合によっては実体も)必要になります。
 そのためProject.h内は、宣言する順番が重要になります。ポインタや参照を使用するだけなら、クラス宣言や構造体宣言を使用すれば前方宣言は成り立ちますが、それらのメンバ関数呼び出しなどを記述する場合は、Project.h内の順番が上になければなりません。そのあたりを考慮しながら記述します。

WinMain.cpp

 ここにはWindowsアプリケーションのエントリポイントであるWinMain関数を記述します。この内容を書き換えるのは、例えばキーボードやマウスを使用する場合に使用するキーを登録します。
 BaseCross64はコントローラはデフォルトで使用できる形ですが、キーボードやマウスを使用するためにはこの中に記述します。
 WinMain関数すべての始まりですから、この記述を書き換えれば、BaseCross64の枠を簡単に超えられます。
 例えばBaseCross64のライブラリは使用したいが使用方法は独自に管理したいなどの場合は、ここを変更すればシーンやステージあるいはAppクラスの考え方は全く使わずに独自の管理をすることが可能です。

 以上、サンプル内に含まれるファイルの役割でした。

このサンプル独自の内容

 このサンプルは、ゲームエンジン(Dx11もしくはDx12)を実装する最低限の処理が実装されています。
 まず、GameStage.hを以下のように記述します。
#pragma once
#include "stdafx.h"

namespace basecross {

    //--------------------------------------------------------------------------------------
    //  ゲームステージクラス
    //--------------------------------------------------------------------------------------
    class GameStage : public Stage {
        //ビューの作成
        void CreateViewLight();
    public:
        //構築と破棄
        GameStage() :Stage() {}
        virtual ~GameStage() {}
        //初期化
        virtual void OnCreate()override;
    };


}
//end basecross
 ここではゲームステージを記述します。ステージメニューステージなどもそうですが、最低限ビューとライトというオブジェクトを実装する必要があります。ここでは、それらを実装する関数void CreateViewLight()関数が宣言されています。
 BaseCross64namespace basecrossというネームスペースに入れます。こうしておくとほかのグローバルな関数などとクラス名などを分離することができます。
 ここに
        //初期化
        virtual void OnCreate()override;
 という仮想関数があります。このOnなんたらという関数名は、ライブラリから呼び出される関数です。GameStageのOnCreate()関数GameStageのインスタンスが作成されるときに、ライブラリから呼び出されます。
 このような仮想関数はほかにOnUoodate()関数、OnDraw関数などがあります。
 使用する場合はこれらの関数を多重定義します。

   続いてGameStage.cppです。以下のように記述されています。
#include "stdafx.h"
#include "Project.h"

namespace basecross {

    //--------------------------------------------------------------------------------------
    //  ゲームステージクラス実体
    //--------------------------------------------------------------------------------------
    void GameStage::CreateViewLight() {
        auto PtrView = CreateView<SingleView>();
        //ビューのカメラの設定
        auto PtrCamera = ObjectFactory::Create<Camera>();
        PtrView->SetCamera(PtrCamera);
        PtrCamera->SetEye(Vec3(0.0f, 5.0f, -5.0f));
        PtrCamera->SetAt(Vec3(0.0f, 0.0f, 0.0f));
        //マルチライトの作成
        auto PtrMultiLight = CreateLight<MultiLight>();
        //デフォルトのライティングを指定
        PtrMultiLight->SetDefaultLighting();
    }

    void GameStage::OnCreate() {
        try {
            //ビューとライトの作成
            CreateViewLight();
        }
        catch (...) {
            throw;
        }
    }

}
//end basecross
 このようにGameStage::CreateViewLight()関数ではビューとライトを設定しています。ビューというのは、ビューポートとカメラを組み合わせたものです。1つのビューポートには1つのカメラが結びついています。複数のビューを使うこともできます。ライトシャドウマップ3Dの陰影をつけるために必要なものです。
 これらの設定を行うGameStage::CreateViewLight()関数GameStage::OnCreate()関数から呼び出します。
 GameStage::OnCreate()関数内にビューやライトの作成を記述してもいいのですが、今後オブジェクトが増えていくと何が何だか分からなくなってしまうので、関連するグループあるいはオブジェクトごとに初期化関数を別に書いたほうが可読性は上がります。

 最後にScene.h/cppを記述します。以下はScene.hです。
#pragma once
#include "stdafx.h"

namespace basecross{

    //--------------------------------------------------------------------------------------
    /// ゲームシーン
    //--------------------------------------------------------------------------------------
    class Scene : public SceneBase{
    public:
        //--------------------------------------------------------------------------------------
        /*!
        @brief コンストラクタ
        */
        //--------------------------------------------------------------------------------------
        Scene() :SceneBase(){}
        //--------------------------------------------------------------------------------------
        /*!
        @brief デストラクタ
        */
        //--------------------------------------------------------------------------------------
        virtual ~Scene();
        //--------------------------------------------------------------------------------------
        /*!
        @brief 初期化
        @return なし
        */
        //--------------------------------------------------------------------------------------
        virtual void OnCreate() override;
        //--------------------------------------------------------------------------------------
        /*!
        @brief イベント取得
        @return なし
        */
        //--------------------------------------------------------------------------------------
        virtual void OnEvent(const shared_ptr<Event>& event) override;
    };

}

//end basecross
 ここでは、OnCreate()仮想関数のほかにOnEvent()仮想関数があります。これは、イベントといってオブジェクト同士の指標などのやり取りに使用できる仕組みです。
 なぜ、シーンにこのような仕組みが実装されているかは理由がありますが後ほど説明します。
 まずは、Scene.cppを見てください。以下のようになってます。
#include "stdafx.h"
#include "Project.h"

namespace basecross{

    //--------------------------------------------------------------------------------------
    /// ゲームシーン
    //--------------------------------------------------------------------------------------
    void Scene::OnCreate(){
        try {
            //クリアする色を設定
            Col4 Col;
            Col.set(31.0f / 255.0f, 30.0f / 255.0f, 71.0f / 255.0f, 255.0f / 255.0f);
            SetClearColor(Col);
            //自分自身にイベントを送る
            //これにより各ステージやオブジェクトがCreate時にシーンにアクセスできる
            PostEvent(0.0f, GetThis<ObjectInterface>(), GetThis<Scene>(), L"ToGameStage");
        }
        catch (...) {
            throw;
        }
    }

    Scene::~Scene() {
    }

    void Scene::OnEvent(const shared_ptr<Event>& event) {
        if (event->m_MsgStr == L"ToGameStage") {
            //最初のアクティブステージの設定
            ResetActiveStage<GameStage>();
        }
    }

}
//end basecross
 シーンもステージ同様構築時に呼ばれるのはScene::OnCreate()関数です。
 まず、背景色を濃い青に設定してます。Col4は色を管理するクラスです。内部では、各色、0.0fから1.0fまでの値を持つように設計されています。
 続いては、ここで、ゲームステージをアクティブステージにするという処理は行わずに、
    //自分自身にイベントを送る
    //これにより各ステージやオブジェクトがCreate時にシーンにアクセスできる
    PostEvent(0.0f, GetThis<ObjectInterface>(), GetThis<Scene>(), L"ToGameStage");
 という処理を行っています。PostEvent()関数イベント発行の関数です。ここでは自分自身にイベントを送っています。
 これは、コメントにもあるように、ステージからシーンにアクセスできるようにするためです。
 例えば、ゲーム実行して取得したスコアライフ値など、ゲーム全体で管理する変数は、シーンに置いておいたほうが便利なことが多いです。(どのオブジェクトからもアクセスしやすいので)。
 しかしScene::OnCreate()関数がよばれる時点では、シーンはまだ完全には構築されてないので、そこでステージを構築すると、ステージのOnCreate()関数内ではシーンにアクセスできないのです。
 そのため、いったんシーンの構築を済ませてからステージの構築を行うというテクニックを使用します。
 PostEvent()関数は、そのパラメータはいったんライブラリ内に保持されて、次のターンの最初に指定のオブジェクトに送られます。送られたイベントはOnEvent()仮想関数で受け取ります。シーンの場合はScene::OnEvent()関数です。
 ですので、その中でゲームステージの構築であるResetActiveStage<GameStage>();の呼び出しを行います。
 この関数は、指定の型のステージを構築しその中でステージのOnCreate()を呼び出します。

 しかし、このサンプルのように共有するデータなどがなければ直接構築しても問題はありません。
 その場合はScene.hは以下のように記述します。
#pragma once

#include "stdafx.h"

namespace basecross{

    //--------------------------------------------------------------------------------------
    /// ゲームシーン
    //--------------------------------------------------------------------------------------
    class Scene : public SceneBase{
    public:
        //--------------------------------------------------------------------------------------
        /*!
        @brief コンストラクタ
        */
        //--------------------------------------------------------------------------------------
        Scene() :SceneBase(){}
        //--------------------------------------------------------------------------------------
        /*!
        @brief デストラクタ
        */
        //--------------------------------------------------------------------------------------
        virtual ~Scene(){}
        //--------------------------------------------------------------------------------------
        /*!
        @brief 初期化
        @return なし
        */
        //--------------------------------------------------------------------------------------
        virtual void OnCreate() override;
    };

}
//end basecross
 そしてScene.cppは以下のように構築します。
#include "stdafx.h"
#include "Project.h"

namespace basecross{

    //--------------------------------------------------------------------------------------
    /// ゲームシーン
    //--------------------------------------------------------------------------------------

    void Scene::OnCreate(){
        try {
            //クリアする色を設定
            Col4 Col;
            Col.set(31.0f / 255.0f,30.0f / 255.0f,71.0f / 255.0f,255.0f / 255.0f);
            SetClearColor(Col);
            //最初のアクティブステージの設定
            ResetActiveStage<GameStage>();
        }
        catch (...) {
            throw;
        }
    }



}
//end basecross
 ステージや、ステージに配置するオブジェクトから初期化時にシーンにアクセスする必要がなければこのような記述でも問題ありません。

 この項では、一番単純なBaseCross64アプリケーションの作成方法を紹介しました。
 次項ではもう少し複雑になります。