====== 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 |実行結果}} 実行結果