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になってます。
その辺は自分で、改良するなりすると、ポートフォリオとかにも書けるけど、そのうち僕も改良しようという気持ちは持ってます。
さっきのコンパイル済みの読み込み用ライブラリを展開すると、一番上のフォルダに以下のファイルがありますが、そいつらが、導入用のライブラリになります。

これを使って書いていく。

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);
        */
}

この流れで大体のことはできる気がする。
後、細かいところは自分で書き換えてみよう!

実行結果

Fig. 1: 実行結果