17.BaseCrossTool

1701.BaseCrossToolの使い方

BaseCrossToolってなんだ?

 BaseCross64のレポジトリにはBaseCrossToolというディレクトリがあり、その中にソリューションが入っています。
 このソリューションはほかのサンプルと違って、ユーザー(つまり皆さん)が、カスタマイズを前提とした、いわゆるひな型となっています。
 ではどういうひな型かというとツールを作るひな型です。

ツールとは

 たとえばBaseCross64を使ってゲームを作成する場合、ひたすらコードを書き続けなければなりません。
 しかし、例えばステージエディタで、GUI敵にオブジェクトを配置して、XMLデータなどを保存出来たら便利だとは思いませんか?
 多くのフレームワークはこういうGUIによるエディタを兼ね備えています。
 しかしBaseCross64は今のところありません。
 またFbxファイル独自のデータ形式のファイルに変換するツールがあると便利です。BaseCrossは前のバージョンではありましたが、今のバージョンBaseCross64では、まだありません。
 また、エフェクトを作成するツールがあると便利だとは思いませんか?
 このように、ゲーム制作にはあると便利なツールがたくさんあります。ツールによって、開発効率は大きく上がり、また修正も楽になります。

ツールが必要とする機能

 ここではBaseCross64で制作するのに便利なツール、を考えてみます。
 すると、まず必要なのはステージを再現できる機能です。ほかのサンプルで利用しているゲームオブジェクトコンポーネントが実装しやすくなければなりません。
 また、BaseCross64VisualC++で作成します。つまりC/C++でネイティブで記述できる機能が必要です。
 そのように考えた場合、現時点ではMFC(MicrosoftFundationClass)ライブラリを使ったC++ネイティブなアプリケーションを作成し、そこからBaseCross64に介入できる環境、がベターでしょう。
 しかしそのような環境を1から作成するのは結構厄介です。ですので、最低限の機能を備えたひな型があると便利です。
 それがBaseCrossToolです。

BaseCrossToolを使ってみよう

 それでは早速BaseCrossToolを使ってみましょう。
 BaseCrossToolはカスタマイズされるのが前提のソリューションですから、BaseCross64から分離させて、新しい環境を作り、そこで作業を開始します。
 そうしないと、今後GiHubのレポジトリからプルした場合、競合が発生してしまいます。

必要なディレクトリを取り出す

 まず作業用のディレクトリをデスクトップなどに作成して、その中にBaseCross64から、Libs、Assets、BaseCrossToolの3つのディレクトリをコピペします。
 以下の様な構成です。
[作業用のディレクトリ]-|-[Assets]
            -|-[Libs]
            -|-[BaseCrossTool]
 こんな感じです。コピペしたらBaseCrossToolの中のBaseCrossTool.slnVS2017で開いて、リビルドしてみましょう。
 ビルドが成功したら実行してみます。以下の画面が出れば成功です。

 

図1701a

 

 この画面は単なるプレートが配置されている画面です。しかし、他のサンプルとは違ってウインドウ枠がありメニューもあります。
 つまり普通のデスクトップアプリケーションゲーム画面が表示される形となっています。

オブジェクトを配置する

 では、試しにこのデスクトップアプリの世界からゲームオブジェクトを配置してみましょう。
 一般的なアプリケーションではメニューでいろんな機能を実装することが奨励されています。もっと複雑なアプリケーションではそのほかにツールボックスとか特別な操作用のウインドウが別に表示されていたりします。
 ここでは、単純な例として
1、ダイアログボックスでオブジェクトの大きさと場所を指定
2、OKボタンで画面上に球体を配置する
 という操作を実装してみます。

ダイアログボックスを作成する

 まず大きさと位置を指定するダイアログを作成します。
 リソースビューを開いてみましょう。以下のようになります。

 

図1701b

 

 ここからBaseCrossTol.rc-Dialogと下層に行って、Dialogを右クリックでリソースの追加を選びます。

 

図1701c

 

 すると以下の様なダイアログが出るので新規作成をクリックします。

 

図1701d

 

 すると以下の様なダイアログボックスの編集画面が出ます。

 

図1701e

 

 リソースビューにあるIDD_DIALOG1というのはこのダイアログのIDです。これではわかりにくいので、編集用ダイアログの上で右クリックして、プロパティを開きます。
 すると、  では、ここに入力用のコントロールを配置します。まず、入力を促すテキストスタティックテキストを設置します。
 編集のyのツールボックスからIDの項目を選択し

 

図1701f

 

 IDD_ADDOBJECTに変更します。

 

図1701g

 

 この要領でプロパティの下の方にCaptionという項目がありますのでオブジェクトの追加にします。するとダイアログのウインドウタイトルが変わります。
 続いて、ツールボックスを開いてStatic Textを選択します。その上で編集用ダイアログの上で、クリックするとスタティックというオブジェクトがダイアログ上に配置されます。このようにして、5つのスタティックテキストを配置します。
 その状態が以下です。

 

図1701h

 

 これらのスタティックテキストは選択状態でプロパティCaptionを変更すると文字列が変わりますので、そのようにして以下のように文字を変更しつつ配置しなおします。ダイアログのサイズも大きいので小さくします。

 

図1701i

 

 続いて、入力を受け付けるエディットコントロールを配置します。ツールボックスからEdit Controlを選択し、以下のように配置します。
 その際、それぞれのIDは、大きさのところがIDC_EDIT_SIZE、位置が、それぞれIDC_EDIT_POSX、IDC_EDIT_POSY、IDC_EDIT_POSZとします。また、OKボタンのCaption追加とします。

 

図1701j

 

ダイアログボックスのソースを記述する

 続いてこのダイアログにソースを記述します。編集用ダイアログを右クリックしクラスの追加を選択します。
 すると設定用のダイアログが出ますので、以下のように設定します。

 

図1701k

 

 クラス名のところに記述すると、自動的にファイル名も決定されます。OKを押すと、CAddObjectDialogクラスが生成されます。
 ここにソースを記述するわけですが、記述する前にそれぞれのエディットコントロールを変数化します。
 まずIDC_EDIT_SIZEのエディットボックスを右クリックし変数の追加を選びます。
 すると変数追加用のダイアログが出ますので、それぞれカテゴリとして、変数の種類はCStringにして、変数名を指定します。
 同様に、IDC_EDIT_POSX、IDC_EDIT_POSY、IDC_EDIT_POSZも変数化します。
 各変数名は以下の通り
IDC_EDIT_SIZE   m_Size
IDC_EDIT_POSX   m_PosX
IDC_EDIT_POSY   m_PosY
IDC_EDIT_POSZ   m_PosZ
 以下はIDC_EDIT_POSXの変数を追加しているところです。

 

図1701l

 

 すると、CAddObjectDialogクラスに自動的に変数が追加されます。以下はヘッダ部です。
class CAddObjectDialog : public CDialogEx
{
    DECLARE_DYNAMIC(CAddObjectDialog)

public:
    CAddObjectDialog(CWnd* pParent = nullptr);   // 標準コンストラクター
    virtual ~CAddObjectDialog();

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_ADDOBJECT };
#endif

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV サポート

    DECLARE_MESSAGE_MAP()
public:
    CString m_Size;
    CString m_PosX;
    CString m_PosY;
    CString m_PosZ;
};
 赤くなっているところが、自動的に追加された変数です。

ダイアログボックスのメッセージの追加

 ここまできたら、いったんファイル-すべて保存しましょう。保存は定期的にやっておきましょう。
 続いて、編集用ダイアログの追加ボタンをダブルクリックします。すると自動的に以下のソースがCAddObjectDialog.cppに作成されます。
void CAddObjectDialog::OnBnClickedOk()
{
    // TODO: ここにコントロール通知ハンドラー コードを追加します。
    CDialogEx::OnOK();
}
となってますので
void CAddObjectDialog::OnBnClickedOk()
{
    UpdateData(TRUE);
    // TODO: ここにコントロール通知ハンドラー コードを追加します。
    CDialogEx::OnOK();
}
 と赤くなっている部分を記述します。

ダイアログボックスを起動する

 このダイアログボックスを起動するためにメニューを修正します。
 リソースビューからMenuを選択しIDR_MAINFRAMEを選びます。

 

図1701m

 

 ダブルクリックするとメニューの編集画面が表示されます。

 

図1701n

 

 ここに入力という箇所がありますのでオブジェクト(&O)と入力します。
 またこのメニューを移動し編集の後に入れます。

 

図1701o

 

 ここで記入した(&O)は、ショートカットキーです。
 メニューにはなるべくショートカットキーを記述しましょう。
 続いてサブメニューに追加(&I)と記述します。そうしてその個所を右クリックし、イベントハンドラーの追加を選択します。

 

図1701p

 

 以下のようなダイアログが出ますので
クラスの一覧から、CBaseCrossToolAppを選択
関数ハンドラー名を、OnAddObject
 として追加して編集ボタンをクリックします。

 

図1701q

 

 すると、自動的に
void CBaseCrossToolApp::OnAddObject()
{
    // TODO: ここにコマンド ハンドラー コードを追加します。
}
 という関数が作られますので、まずBaseCrossTool.cppの上のヘッダインクルード部分に
#include "stdafx.h"
#include "afxwinappex.h"
#include "afxdialogex.h"
#include "BaseCrossTool.h"
#include "MainFrm.h"
#include "Project.h"
#include "CAddObjectDialog.h"
using namespace basecross;
 と赤くなっているように追加し、先ほどのメニュのハンドラに
void CBaseCrossToolApp::OnAddObject()
{
    // TODO: ここにコマンド ハンドラー コードを追加します。
    CAddObjectDialog dialog;
    if (dialog.DoModal() == IDOK) {
        ;
    }
}
 のように追加します。
 それで、ビルドし、メニューからオブジェクト-追加でダイアログボックスが出るかどうか、確認します。
 ここでは追加ボタンを押しても何もコードは記述してないので、ボタンを押してもダイアログが閉じるだけです。

オブジェクトの作成

 それでは、メニューとダイアログの準備ができたので、今度はBaseCross64側を作成します。
 まず、Charcter.hに以下を記述します。
    //--------------------------------------------------------------------------------------
    //  class MoveSphere : public GameObject;
    //--------------------------------------------------------------------------------------
    class MoveSphere : public GameObject {
        float m_Scale;
        Vec3 m_Position;
        float m_TotalTime;
    public:
        //構築と破棄
        MoveSphere(const shared_ptr<Stage>& StagePtr,
            const float Scale,
            const Vec3& Position
        );
        virtual ~MoveSphere();
        //初期化
        virtual void OnCreate() override;
        //操作
        virtual void OnUpdate() override;
    };
 次に、Charcter.cppに以下を記述します。
    //--------------------------------------------------------------------------------------
    //  class MoveSphere : public GameObject;
    //--------------------------------------------------------------------------------------
    //構築と破棄
    MoveSphere::MoveSphere(const shared_ptr<Stage>& StagePtr,
            const float Scale,
            const Vec3& Position
        ):
        GameObject(StagePtr),
        m_Scale(Scale),
        m_Position(Position),
        m_TotalTime(0.0f)
    {}
    MoveSphere::~MoveSphere() {}
    //初期化
    void MoveSphere::OnCreate() {
        auto ptrTransform = GetComponent<Transform>();
        ptrTransform->SetScale(Vec3(m_Scale));
        ptrTransform->SetRotation(Vec3(0));
        ptrTransform->SetPosition(m_Position);
        //影をつける(シャドウマップを描画する)
        auto shadowPtr = AddComponent<Shadowmap>();
        //影の形(メッシュ)を設定
        shadowPtr->SetMeshResource(L"DEFAULT_SPHERE");
        auto ptrDraw = AddComponent<BcPNTStaticDraw>();
        ptrDraw->SetMeshResource(L"DEFAULT_SPHERE");
        ptrDraw->SetTextureResource(L"SKY_TX");
        ptrDraw->SetFogEnabled(true);
        ptrDraw->SetOwnShadowActive(true);

    }
    //操作
    void MoveSphere::OnUpdate() {
        auto elapsedTime = App::GetApp()->GetElapsedTime();
        m_TotalTime += elapsedTime;
        if (m_TotalTime >= XM_2PI) {
            m_TotalTime = 0.0f;
        }
        auto ptrTransform = GetComponent<Transform>();
        Vec3 pos = m_Position;
        pos.x += sin(m_TotalTime);
        ptrTransform->SetPosition(pos);
    }
 次に、GameStage.hに以下を追加します。赤い部分です。
    class GameStage : public Stage {
        //リソースの作成
        void CreateResourses();
        //ビューの作成
        void CreateViewLight();
        //プレートの作成
        void CreatePlate();
    public:
        //構築と破棄
        GameStage() :Stage() {}
        virtual ~GameStage() {}
        //初期化
        virtual void OnCreate()override;
        //オブジェクト作成
        void CreateMoveSphere(float Size, const Vec3& Pos);

    };
 次に、GameStage.cppに以下を追加します。赤い部分です。
    //オブジェクト作成
    void GameStage::CreateMoveSphere(float Size, const Vec3& Pos) {
        AddGameObject<MoveSphere>(Size, Pos);
    }
 最後に、BaseCrossTool.cppCBaseCrossToolApp::OnAddObject()ハンドラを以下のように追記します。
void CBaseCrossToolApp::OnAddObject()
{
    // TODO: ここにコマンド ハンドラー コードを追加します。
    CAddObjectDialog dialog;
    if (dialog.DoModal() == IDOK) {
        float size = (float)_tcstod(dialog.m_Size, NULL);
        if (size <= 0.0f) {
            AfxMessageBox(L"サイズは0以下にできません");
            return;
        }
        Vec3 pos;
        pos.x = (float)_tcstod(dialog.m_PosX, NULL);
        pos.y = (float)_tcstod(dialog.m_PosY, NULL);
        pos.z = (float)_tcstod(dialog.m_PosZ, NULL);
        auto ptrStage = App::GetApp()->GetScene<Scene>()->GetActiveStage();
        auto ptrGameStage = dynamic_pointer_cast<GameStage>(ptrStage);
        if (ptrGameStage) {
            ptrGameStage->CreateMoveSphere(size, pos);
        }
    }
}
 ビルドして実行し、メニューからオブジェクト-追加を選択します。
 ダイアログで、以下のように設定します。

 

図1701r

 

 追加ボタンをクリックして、以下の画面になれば成功です。球体が行ったり来たりします。

 

図1701s

 

解説

 この項では、MFCの簡単な使い方について説明しました。また、MFCのプログラムからBaseCross64側への値などの渡し方も説明しました。
 比較的長いドキュメントになってしまいましたが、MFCの機能としては、まだまだ入り口に立ったくらいです。
 MFCを使うことで、Windowsアプリケーション作成の部分は、かなり高度なこともできます。
 MFCは有名なライブラリなので、サンプルやヒントはネットにあふれています。ぜひ、MFCとBaseCross64を組み合わせて便利なツールを開発してください。