====== Effekseerのエフェクトを、C++とDirectX11の組み合わせで使うよ ======
なんか最近みんなぜいたくになってきて、箱と箱が戦うゲームを作るのでは満足できなくなってきたようなので、味付けとして外部のエディタ(Effekseer)でせいさくしたエフェクトを読み込みたいと思う。\\
===== Effekseerのエフェクトデータの入手 =====
Effekseerでエフェクトデータを作る方法は、本家のチュートリアルや、動画などがたくさんある。\\
* [[https://effekseer.github.io/Help_Tool/ja/index.html|Effekseerチュートリアル(本家)]]
* [[https://youtu.be/2fTu7smqlVw?feature=shared|作成動画1:無料のエフェクト制作ツール「Effekseer」の使い方【Bakinで土煙を実装する】]]
* [[https://www.youtube.com/watch?v=CgOype336-0&list=PLGxLMLE3NfnLU3irpIX-w2KqlFBHhripQ|作成動画2:Effekseer Tutorials]]
んで、サンプルでも何でもいいので、エディタを使って以下のものを用意する。\\
(.efkprojを読み込んで、.efkefc、.efkファイルに変換する、Textureフォルダと、Modelフォルダに必要なものは言ってる場合はそれも忘れないようにね。)\\
===== Effekseer for CPPの入手 & コンパイル =====
次に、自分のC++プロジェクトにEffekseerの読み込み用.hと.libを組み込むためのライブラリを生成する。\\
(公式から、[[https://github.com/effekseer/Effekseer/releases/download/170e/EffekseerForCpp170e.zip|ダウンロード]]して中にあるDirectX11なんたら.batとかを、VisualStudio Developer Command Promptとかから、実行すると、15分ぐらい待ってると、install~ってフォルダにすべて構築されるはずです。\\
そいつができたら、あとは自分のプロジェクトに.hを読み込んで.libをリンクしていくだけでできます。\\
基本は、[[https://github.com/effekseer/Effekseer/blob/master/docs/Help_Cpp/18x/Guide_Cpp_Ja.md|導入ガイド]]にある通りで読み込みできます。\\
今回一番時間がかかった作業は、このドキュメントを掘り当てる部分でした。(公式のどこからもリンクないんじゃ)\\
あとは、EffekseerのエフェクトをC++から読み込んでる人たちのgithubを参考に便利そうな機能を取り込んでいきましょう。\\
* https://github.com/reiji0128/ActionGame/tree/4cbcd2cf71989b3773c5b24020e202e8b2fca1f0/Game/Game
* https://github.com/shirokuma1101/game-libraries/tree/main/Inc/ExternalDependencies/Effekseer
* https://github.com/Keisuke0619/Danmaku02/blob/main/ShaderProject/Effect.h
みんないろいろ工夫して使ってます。\\
とりあえず学生の皆さんは、{{ :game-engineer:classes:2024:game-development-3:first-term:6:effekseelib.zip |ここ}}からコンパイル済みのライブラリ(学校の環境用)を入手できます。\\
めんどくさいんで、プロジェクトのあるフォルダに一緒にぶっこんじゃいましょ。\\
__読み込み用のソース、EffekseerVFX.cppとEffekseerVFX.hも一緒に入ってます。__
===== ライブラリのリンク設定 =====
std::filesystem(神)を使っているので、プロジェクトのプロパティ => 構成プロパティ => 全般 => C++標準言語を「C++20」に(たぶんC++17以降だと大丈夫だと思う)\\
* プロジェクト設定 => 追加のインクルードディレクトリ、または、外部インクルードディレクトリ、に以下を追加
* => $(ProjectDir)\EffekseeLib\install_msvc2022_x64\include\Effekseer
* プロジェクト設定 => ライブラリディレクトリ、または、リンカー => 全般 => 追加のライブラリディレクトリに以下を追加
* => $(ProjectDir)\EffekseeLib\install_msvc2022_x64\lib
===== ソースコード記述 =====
あとは導入ガイドに従って、各自エフェクトの読み込みと表示の処理を書いていけばよい。\\
ためしに、導入用のライブラリを書いてみたのでおいておく。こいつはあくまで暫定版ですが、とりあえず読み込みだけはできてうれしいので公開します。\\
ループ再生を途中で止めたり、途中から再生したりとかは全然できない仕様になってます。あとTransformも現状の授業用エンジンと互換性がなく、ただのXMMATRIXになってます。\\
その辺は自分で、改良するなりすると、ポートフォリオとかにも書けるけど、そのうち僕も改良しようという気持ちは持ってます。\\
さっきのコンパイル済みの読み込み用ライブラリを展開すると、一番上のフォルダに以下のファイルがありますが、そいつらが、導入用のライブラリになります。
* EffekseerVFX.h
* 名前空間 EFFEKSEERLIB => この読み込みライブラリの名前空間
* EffekseerManager *gEfk;
* エフェクシアマネージャーのポインタ(グローバル)こいつを使って、全体からごにょごにょする
* EFKTranform
* エフェクトのトランスフォーム+寿命やスピードなどが書いてある
* エフェクトのインスタンスに関連付けされたトランスフォームをいじることで移動や消滅のコントロールをする。
* EFKData
* エフェクトの読み込みと生成を管理をしているクラス
* エフェクトに自分で名前を付けて、ファイルパス(通常はAssetがベースになっているのでそこからの相対パス)を書くと読み込める
* 後述のマネージャークラスから呼び出されるので単独で使うことはあまりない
* EFKInstance
* エフェクトのインスタンスを管理するクラス
* マネージャーと連携して、今まで読み込んだのと同じデータが読み込まれようとすると、ハンドルとインスタンスを関連付けて無駄を防ぐ
* インスタンスは、トランスフォームクラス(EFKTransform)を持っていて、データは同じだけどそれぞれ寿命や位置が変わっても管理できる
* EffekseerManager
* エフェクトのマネジメントクラス、これを使ってエフェクトの読み込みや再生を行う。
* エフェクトは、ループしなければ、時間後自動消滅する(スマートポインタを使ってるはずなのでメモリは勝手に消えるはず)
* Initialize
* ライブラリのイニシャライズをする、デバイスと、デバイスコンテキストのポインタが必要(か?)
* Update
* エフェクトの更新をする、授業用のエンジンと違って、引数に前フレームからの経過時間が欲しい(秒?)
* Draw
* 描画を行う
* 画面クリア => 自分のオブジェクトの描画 => エフェクトの描画(Draw()) => 画面更新 の順番に行うとうまくいく(はず)
* AddEffect
* エフェクトの読み込みと、データプールへの追加
* エフェクトに名前を付けて、Assetフォルダからのパスで指定
* Play
* 授業用エンジンでいうところのInstantiateにあたる(そういう意味では、親のポインタもらってきてtransformに適用してあげるとよかったなぁ)
* 戻り値として、EFKTransformのshared_ptrを返す。(まぁポインタだね)
* SetFPS
* デフォルトではFPS60でやってるけど、変更したいときはありそうなので
* EffekseerVFX.cpp
* 実はほとんど何もしてない。ほぼダミーのファイル
これを使って書いていく。\\
== 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する。\\
タンクゲームのタンククラスのヘッダ
#pragma once
#include "Engine/GameObject.h"
#include "EffekseeLib/EffekseerVFX.h"
//戦車(本体)を管理するクラス
class Tank : public GameObject
{
//省略
public:
//省略
//EFKTransformへのスマートポインタを持っておく(使うエフェクトのインスタンス数分必要だよね!)
std::shared_ptr mt;
};
次に、実装側\\
タンクゲームのプレイヤーの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);
*/
}
この流れで大体のことはできる気がする。\\
後、細かいところは自分で書き換えてみよう!\\
===== 実行結果 =====
{{ :game-engineer:classes:2024:game-development-3:first-term:6:fps_59_2024-06-24_12-13-47.mp4 |実行結果}}
実行結果