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以降だと大丈夫だと思う)
- プロジェクト設定 ⇒ 追加のインクルードディレクトリ、または、外部インクルードディレクトリ、に以下を追加
- ⇒ $(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する。
- Tank.h
#pragma once #include "Engine/GameObject.h" #include "EffekseeLib/EffekseerVFX.h" //戦車(本体)を管理するクラス class Tank : public GameObject { //省略 public: //省略 //EFKTransformへのスマートポインタを持っておく(使うエフェクトのインスタンス数分必要だよね!) std::shared_ptr<EFFEKSEERLIB::EFKTransform> mt; };
次に、実装側
- 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); */ }
この流れで大体のことはできる気がする。
後、細かいところは自分で書き換えてみよう!