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()関数が宣言されています。
BaseCross64は
namespace 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アプリケーションの作成方法を紹介しました。
次項ではもう少し複雑になります。