Effekseerのエフェクトを、C++とDirectX11の組み合わせで使うよ
なんか最近みんなぜいたくになってきて、箱と箱が戦うゲームを作るのでは満足できなくなってきたようなので、味付けとして外部のエディタ(Effekseer)でせいさくしたエフェクトを読み込みたいと思う。
Effekseerのエフェクトデータの入手
Effekseerでエフェクトデータを作る方法は、本家のチュートリアルや、動画などがたくさんある。
んで、サンプルでも何でもいいので、エディタを使って以下のものを用意する。
(.efkprojを読み込んで、.efkefc、.efkファイルに変換する、Textureフォルダと、Modelフォルダに必要なものは言ってる場合はそれも忘れないようにね。)
Effekseer for CPPの入手 & コンパイル
次に、自分のC++プロジェクトにEffekseerの読み込み用.hと.libを組み込むためのライブラリを生成する。
(公式から、ダウンロードして中にあるDirectX11なんたら.batとかを、VisualStudio Developer Command Promptとかから、実行すると、15分ぐらい待ってると、install~ってフォルダにすべて構築されるはずです。
そいつができたら、あとは自分のプロジェクトに.hを読み込んで.libをリンクしていくだけでできます。
基本は、導入ガイドにある通りで読み込みできます。
今回一番時間がかかった作業は、このドキュメントを掘り当てる部分でした。(公式のどこからもリンクないんじゃ)
あとは、EffekseerのエフェクトをC++から読み込んでる人たちのgithubを参考に便利そうな機能を取り込んでいきましょう。
みんないろいろ工夫して使ってます。
とりあえず学生の皆さんは、ここからコンパイル済みのライブラリ(学校の環境用)を入手できます。
めんどくさいんで、プロジェクトのあるフォルダに一緒にぶっこんじゃいましょ。
読み込み用のソース、EffekseerVFX.cppとEffekseerVFX.hも一緒に入ってます。
ライブラリのリンク設定
std::filesystem(神)を使っているので、プロジェクトのプロパティ ⇒ 構成プロパティ ⇒ 全般 ⇒ C++標準言語を「C++20」に(たぶんC++17以降だと大丈夫だと思う)
ソースコード記述
あとは導入ガイドに従って、各自エフェクトの読み込みと表示の処理を書いていけばよい。
ためしに、導入用のライブラリを書いてみたのでおいておく。こいつはあくまで暫定版ですが、とりあえず読み込みだけはできてうれしいので公開します。
ループ再生を途中で止めたり、途中から再生したりとかは全然できない仕様になってます。あとTransformも現状の授業用エンジンと互換性がなく、ただのXMMATRIXになってます。
その辺は自分で、改良するなりすると、ポートフォリオとかにも書けるけど、そのうち僕も改良しようという気持ちは持ってます。
さっきのコンパイル済みの読み込み用ライブラリを展開すると、一番上のフォルダに以下のファイルがありますが、そいつらが、導入用のライブラリになります。
EffekseerVFX.h
EffekseerVFX.cpp
これを使って書いていく。
Main.cpp
まず、メインに、マネージャーの読み込みと初期化、全体の更新、描画などの処理を追加。
Listing. 1: Main.cppの変更点
- Main.cpp
//include部分にこれを追加
#include "../EffekseeLib/EffekseerVFX.h"
//(省略)
//オーディオ(効果音)の準備
Audio::Initialize(hWnd);
//マネージャーの生成と、初期化(これもスマポにした方がいいので、後で変更するね)
EFFEKSEERLIB::gEfk = new EFFEKSEERLIB::EffekseerManager;
EFFEKSEERLIB::gEfk->Initialize(Direct3D::pDevice_, Direct3D::pContext_);
//ルートオブジェクト準備
//すべてのゲームオブジェクトの親となるオブジェクト
RootObject* pRootObject = new RootObject;
pRootObject->Initialize();
//(省略)
//ライブラリのUpdateで使いたいので、deltaTという変数(前フレームからの時間経過)を作っとく
double deltaT = nowTime - lastUpdateTime;
//指定した時間(FPSを60に設定した場合は60分の1秒)経過していたら更新処理
if (deltaT * fpsLimit > 1000.0f)
{
//時間計測関連
lastUpdateTime = nowTime; //現在の時間(最後に画面を更新した時間)を覚えておく
FPS++; //画面更新回数をカウントする
//(省略)
//カメラを更新
Camera::Update();
//エフェクトの更新
//VFX::Update(); //いったん消しとく
//エフェクトのアップデート(エンジン側のカメラが確定してからする
//Update内部でエンジン側のカメラと、エフェクト側のカメラの位置合わせをしているため
EFFEKSEERLIB::gEfk->Update(deltaT / 1000.0);
//このフレームの描画開始
Direct3D::BeginDraw();
//全オブジェクトを描画
//ルートオブジェクトのDrawを呼んだあと、自動的に子、孫のUpdateが呼ばれる
pRootObject->DrawSub();
//エフェクトの描画
//VFX::Draw(); //いったん消す
//エフェクトの描画
EFFEKSEERLIB::gEfk->Draw();
//描画終了
Direct3D::EndDraw();
これで、全体のマネージャー設定は完了
エフェクトを使うクラス側での設定(この例ではPlayer.cppとか)
あとは、使うクラスでマネージャー(gEfk)にエフェクトデータ(.efk)を読み込んで、EFKTransformにパラメータ設定して、Playする。
Listing. 2: タンクゲームのタンククラスのヘッダ
- Tank.h
#pragma once
#include "Engine/GameObject.h"
#include "EffekseeLib/EffekseerVFX.h"
//戦車(本体)を管理するクラス
class Tank : public GameObject
{
//省略
public:
//省略
//EFKTransformへのスマートポインタを持っておく(使うエフェクトのインスタンス数分必要だよね!)
std::shared_ptr<EFFEKSEERLIB::EFKTransform> mt;
};
次に、実装側
Listing. 3: タンクゲームのプレイヤーのCPPファイル
- tank.cpp
void Tank::Initialize()
{
//省略
//エフェクトの読み込み
//以降,"TAMA"という名前で"tama.efk"を扱える
EFFEKSEERLIB::gEfk->AddEffect("TAMA", "tama.efk");
//トランスフォーム(エフェクトの初期データを設定)
EFFEKSEERLIB::EFKTransform t;//matrix isLoop, maxFrame, speed
DirectX::XMStoreFloat4x4(&(t.matrix), transform_.GetWorldMatrix());
t.isLoop = true; //繰り返しON
t.maxFrame = 80; //80フレーム
t.speed = 1.0; //スピード
mt = EFFEKSEERLIB::gEfk->Play("TAMA", t);
//戻り値で、生成されたエフェクトのインスタンスのトランスフォームへの参照が返ってくるので、
//以降このエフェクトの位置や寿命を変更したいときは、mtをいじるだけでいいのよ
}
//更新
void Tank::Update()
{
//移動
Move();
//地面に沿わせる
FollowGround();
//敵がもういなかったらクリア画面へ
if (FindObject("Enemy") == nullptr)
{
SceneManager* pSceneManager = (SceneManager*)FindObject("SceneManager");
pSceneManager->ChangeScene(SCENE_ID_CLEAR);
}
//エフェクトを移動させたり、消滅させたいときは、ここでmtを変更する
//消滅させたいときはmt->isLoop = falseにすると、次の再生が終わると消える
//その場、そのタイミングで消滅させるのは、今んところ対応していない。。。
XMMATRIX tr = XMMatrixTranslation(0, 1.0, 0.5);
XMMATRIX rt = XMMatrixRotationY(XM_PI);
DirectX::XMStoreFloat4x4(&(mt->matrix), rt*tr*this->GetWorldMatrix());
/* ボタン押したら再生とかは、Initializeとかでインスタンス化しないで、Updateとかでキー入力を検出したら
EFFEKSEERLIB::EFKTransform t;//matrix isLoop, maxFrame, speed
DirectX::XMStoreFloat4x4(&(t.matrix), transform_.GetWorldMatrix());//位置やスケールを設定
t.isLoop = false; //繰り返しON
t.maxFrame = 80; //80フレーム
t.speed = 1.0; //スピード
//一回再生して、インスタンス消滅するので、あえてトランスフォームを捕捉しない
EFFEKSEERLIB::gEfk->Play("TAMA", t);
*/
}
この流れで大体のことはできる気がする。
後、細かいところは自分で書き換えてみよう!
実行結果