【UE4】Niagaraで煙を出してみる
すごくお久しぶりです。今回はUE4のNiagaraを使用して煙のエフェクトを作成しようと思います。
完成物
完成するとこんな感じになります。
今回はNiagaraの使い方に注力したいのでテクスチャやマテリアルはデフォルトの物を使用しています。
また、今回はUE4.25を使用して作成しています。
今回の内容について
Niagaraには豊富なテンプレートがありますが、今回は何もないEmptyの状態から作成していきます。
扱っていく項目については以下の通りになっています。
・パーティクルの初期設定(寿命、初期スケール)
・パーティクルの放出設定
・生存時間によるαの制御方法
・生存時間によるスケールの制御方法
逆にスクリプトモジュールについては今回は扱っていません。
また、何か間違っている点等あればご指摘頂けると幸いです。
それではよろしくお願いします。
制作開始
1, NiagaraEmitterの作成
まず初めにNiagaraEmitterの作成します。
コンテンツブラウザ上で右クリックから「FX」->「ナイアガラエミッタ」を選択します。
「テンプレートに基づく新しいエミッタ」を選択し「次へ」。
「Empty」を選択し「終了」します。
名前は何でもいいですが、今回は分かりやすい様に「NEmi_Smoke」にしました。
2, パーティクルの放出
「NEmi_Smoke」を開いても何も表示されない状態なので、とりあえずパーティクルを出してみます。
まず初めに「エミッタの更新」の「+」をクリックし「Spawn Rate」モジュールを追加します。
こちらのモジュールは1秒間に放出するパーティクルの数を設定する事が出来ます。
今回は「SpawnRate」を「10」に設定します。
これでプレビューでパーティクルが確認出来る様になったので、次は パーティクルに速度を与えてみたいと思います。
「パーティクルのスポーン」に「Add Velocity in Cone」を追加します。
次に速度の強さを設定して行きます。設定自体は「Velocity Strength」から行えます。
今回は速度をある程度ランダムにしてみたいと思います。
「Velocity Strength」の右端にあるプルダウンを開き、「Uniform Ranged Float」を選択します。
すると、「Minimum」と「Maximum」の設定項目が出てくるので値を設定していきます。今回は「Minimum」が「70」、「Maximum」が「100」に設定してみました。
これでパーティクルが動き出しましたが「X+」方向に放出されているので、上向きに放出されるように修正します。
パーティクルの放出方向は「Cone Axis」で行えるのでこちらの値を以下の様に設定します。
これで上向きにパーティクルが放出されるようになりました。
ついでにパーティクルの放出角度を「30」に設定しておきます。設定は「Cone Angle」から行います。
3, パーティクルの初期設定
パーティクルの放出が終わったので、次に初期設定を行って行きます。
初期設定はデフォルトで追加されていた「Initialize Particle」モジュールから行います。
こちらの設定は新しく説明する事が無いので画像を見ながら設定してみて下さい。
ハイライトされている項目が設定した項目になります。
「Sprite Size」の設定について少し説明したいと思います。
この項目自体は「Vector 2D」の設定項目ですが「Vector 2DFrom Float」を使用する事で「X」と「Y」の値を一括で設定出来るようになります。
更に「Vector 2DFrom Float」の設定に「Uniform Ranged Float」を使用する事でfloat値でランダム範囲を制御する事が出来ます。
4, パーティクルの動作制御
次にパーティクルの動作を制御してもう少し煙っぽくしたいと思います。
具体的にはパーティクルのスケールとαを制御して行きます。
まず初めにスケールを制御してみます。
目標は産まれたては小さくて、徐々に大きくなる感じです。
「パーティクル更新」に「Scale Sprite Size」を追加します。
設定ですが今回はカーブを使用してみたいと思います。
まず「Scale Factor」をfloat値で設定出来るように「Vector 2DFrom Float」を設定します。
次に「Float From Curve」を選択します。
このままだと産まれたてが大きくて、消滅時には小さくなってしまっているので設定を逆にします。
これでもいい感じですがもう少し調整して行きます。
まず最初のキーの値を「0.2」に設定します。
次に「Shift」+「左クリック」でキーを追加し、「時間」を「0.25」に「値」を「0.5」に設定します
これでスケールの制御は完成です。
因みにカーブのX軸が何の値を基にしているかですが、こちらは「CurveIndex」から値を受け取っているようです。
デフォルトでは「NormalizedAge」が設定されているのでパーティクルの寿命の値でカーブの値を取得している感じだと思います。
「NormalizedAge」は0~1で正規化されたパーティクルの寿命になります。
最後にαを制御して行きます。
現状はパーティクルがパッと消えてしまうので寿命に合わせてα値を下げる事で消滅を自然にしたいと思います。
「パーティクル更新」に「Scale Color」を追加します。
「Scale Alpha」を寿命に合わせて制御すればいいのですが、今回はコードを直に書く方法とパラメータをリンクして設定する方法を紹介したいと思います。
まずは直にコードを書く方法です。
プルダウンから「新しい式」を選択します。
式はこちらを入力します。「1.0f - Particles.NormalizedAge」
こちらの方法はすぐに色々試せたりするのでありがたいですね。
次にもう一つの方法です。
「One Minus Float」を選択します。
「Float」に「Particles NormalizedAge」を設定します。
「One Minus Float」は「1.0 - (入力された値)」の結果が返ってくるので、0~1を反転する時に便利です。
以上、お好きな方で設定して貰えればと思います。
完成
今回の内容はこれで以上になります。お疲れさまでした。
テクスチャ使ってない割には煙っぽくなったのでは無いかと思います。
また、レベル上に配置するにはNiagara Systemを使用しないといけないので注意して下さい。
Niagara以前触った時よりもGUIが分かりやすくなってていい感じでした。
更新がめちゃめちゃ遅れたのには触れないでおきます。。
最後まで見て頂きありがとうございました。
それでは良いNiagaraライフを…。
【UE4】Cascadeで火花を散らしてみる
今回はエフェクト制作、Cascadeの勉強目的でサンプルを参考にしてエフェクトを作成してみました。
完成物
作ったエフェクトですが、出来上がりはこんな感じになります。
StarterContent の P_Sparks を参考にして作成しました。
下準備
Cascadeでエフェクトを作っていく前に使用するマテリアルやテクスチャを用意していきます。
マテリアル
次に使用するマテリアルを作成していきます。
Cascadeで色の調整が出来る様に ParticleColor の値をテクスチャに掛け合わせます。
あとは BlendMode を Additive に、ShadingModel をUnlitにしたらマテリアルの作成は終わりです。
制作開始のその前に
下準備が終わったので早速作っていきます。
…と言いたい所ですが、今は右も左も分からない状態なので P_Sparks の中身を見てどんな要素が入っているのか確認して置きます。
・絶え間無くパーティクルを放出している
・パーティクルが重力の影響を受けている
・パーティクルにコリジョンが設定してある
・パーティクルが僅かに軌跡を描いている
とりあえず以上の要素が含まれていればそれっぽくなる感じがします。
制作開始
パーティクルの挙動も確認出来たのでようやく作っていきます。
マテリアルの設定
まずRequiredモジュールでマテリアルの設定を行っていきます。
カラーの設定
カラーは Color Over Life モジュールにて変更が可能です。
是非好きな色に変えてみてください。
加速度の設定
重力っぽくしたいのでConst Accelerationモジュールを追加して設定していきます。
P_Sparks だとZ -2000なので同じ様に設定します。
これで重力を受けている感じの挙動になりました。
生成位置、速度の設定
次は生成位置とパーティクルが四方八方に飛んで行く様に設定していきます。
Location -> Sphereモジュールを追加します。
パラメータはこんな感じになります。
重要なのは下方向に速度が入らないように Negative Z のチェックを外す事と Velocity Scale を有効にする為に Velocity にチェックを入れる事ですかね。
いい感じにパーティクルがばらけてくれるようになりました。
軌跡を描いている感じにする
これ実際は奇跡を描いているのでは無くてパーティクルを細長くして、進行方向に向かせてる感じっぽいです。
細長くするには Initial Size モジュールにて設定を行います。
このままだとよく見えないのでパーティクルの配置方法を設定します。
Requiredモジュール -> Screen Alignment を PSA Velocity に設定します。
完成!
…という事で「制作開始のその前に」で確認した内容は全部達成出来ました!
お疲れさまでした!
もっといい感じにする為に、パーティクルの放出量を増やしたり、パーティクル消失時にスケールを小さくしてみたりするといいかもしれません。
以上で今回は終わりたいと思います。
今後もエフェクト系で何かやれればいいなと考えています。
それでは良いCascadeライフを…。
【UE4】C++でキー入力を配列にまとめてみる
今回の説明に出てくるソースが入ったプロジェクトです。
こちらを見ながら今回の記事を読んでいただけると分かりやすいと思います。
github.com
前置き
こんにちは、最近インフルエンザが流行っているらしいので気を付けようと思う今日この頃です。
今回はUE4の入力をC++の配列にまとめてグローバルに呼び出せるようにしてみたいと思います。
間違い等ありましたらご指摘いただけると幸いです。
APlayerControllerのサブクラスを作成
まずは入力を取りたいのでAPlayerControllerのサブクラスを作成します。
そして、入力を保持する為の配列をメンバ変数に追加します。
入力は毎フレーム更新を行うので、Tickをオーバーライドします。
配列のアクセスにマジックナンバーを使用したくないのでenumも定義しましょう。
あとはキャストを書くのが面倒なので、便利そうな関数をちょこっと書いておきます。。。
// MyPlayerController.h #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "MyPlayerController.generated.h" template<typename T> inline int32 ToInt32(T _val) { return static_cast<int32>(_val); } UENUM(BlueprintType) enum class EMY_INPUT_TYPE : uint8 { KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, MAX, }; UCLASS() class INPUTTESTPROJECT_API AMyPlayerController : public APlayerController { GENERATED_BODY() private: TArray<bool> PastPressed; // 1フレーム前の入力情報 TArray<bool> CurrentPressed; // 現フレームの入力情報 protected: public: AMyPlayerController(); virtual void Tick(float _deltaTime) override; };
次はcppの方で関数を実装していきます。
入力はIsInputKeyDownを使用して取っていきます。
指定したキーが押されてる場合はtrue、押されていない場合はfalseをくれます。
今回は十字キーの入力を取っていきます。
// MyPlayerController.cpp #include "MyPlayerController.h" AMyPlayerController::AMyPlayerController() { PastPressed.Init(false, ToInt32(EMY_INPUT_TYPE::MAX)); CurrentPressed.Init(false, ToInt32(EMY_INPUT_TYPE::MAX)); } void AMyPlayerController::Tick(float _deltaTime) { PastPressed = CurrentPressed; // 入力情報の更新 CurrentPressed[ToInt32(EMY_INPUT_TYPE::KEY_UP)] = IsInputKeyDown(EKeys::Up); CurrentPressed[ToInt32(EMY_INPUT_TYPE::KEY_DOWN)] = IsInputKeyDown(EKeys::Down); CurrentPressed[ToInt32(EMY_INPUT_TYPE::KEY_RIGHT)] = IsInputKeyDown(EKeys::Right); CurrentPressed[ToInt32(EMY_INPUT_TYPE::KEY_LEFT)] = IsInputKeyDown(EKeys::Left); }
入力判定の実装
今回はキーを押した瞬間と、押されている間と、離した瞬間の判定を実装したいと思います。
まずは先ほどのAMyPlayerControllerに以下の関数を追加します。
// MyPlayerController.h public: UFUNCTION(BlueprintCallable) bool IsPressed(EMY_INPUT_TYPE _inputType); UFUNCTION(BlueprintCallable) bool IsPressHold(EMY_INPUT_TYPE _inputType); UFUNCTION(BlueprintCallable) bool IsReleased(EMY_INPUT_TYPE _inputType);
実装はこんな感じになります。
// MyPlayerController.cpp bool AMyPlayerController::IsPressed(EMY_INPUT_TYPE _inputType) { bool pp = PastPressed[ToInt32(_inputType)]; bool cp = CurrentPressed[ToInt32(_inputType)]; return ((pp ^ cp) & (!pp)); } bool AMyPlayerController::IsPressHold(EMY_INPUT_TYPE _inputType) { return CurrentPressed[ToInt32(_inputType)]; } bool AMyPlayerController::IsReleased(EMY_INPUT_TYPE _inputType) { bool pp = PastPressed[ToInt32(_inputType)]; bool cp = CurrentPressed[ToInt32(_inputType)]; return ((pp ^ cp) & (pp)); }
実際に使ってみる
今回作成したプレイヤーコントローラーを機能させるにはレベルに設定しないといけないのですが、(めんどうなので…)説明は省略させて頂きます。ご了承ください。。
そして実際に使ってみたコードが以下になります。
AMyPlayerController* MyController = Cast<AMyPlayerController>(UGameplayStatics::GetPlayerController(this, 0)); if (MyController) { if (MyController->IsPressHold(EMY_INPUT_TYPE::KEY_UP)) { // ↑キーが押されているッ! } }
これで無事C++で楽にキー入力を取る事が出来ました!
ただ、入力周りはこれで行こうとすると、エディタで設定できるアクションマッピングは使わない方がいいかもしれません。単純にどっちで入力してるのか管理し辛くなるので…。
それでもBPで入力処理を実装したい時がどうしても出てくると思います。その度にTickからノード伸ばして…なんてやってられません!
なので、UFUNCTION(BlueprintImplementableEvent)で関数を作ってC++側でキー入力判定をした時に呼んであげればいい感じになると思います。
今回のプロジェクトのソースで言うとMyActorでやってますので、見て頂ければと思います。
それではお疲れ様でした。良いUnrealライフを…。
スマートポインタを使ってみる - std::shared_ptr -
前置き
スマポの存在や便利さは知ってるものの、結局生ポを使用してしまう今日この頃です。
そんな中先日、生ポで痛い目にあったのでこれを機に使い方を勉強しようと思います。
スマートポインタとは
動的確保したメモリを自動的に解放してくれるすごいヤツです。
std::shared_ptrとは
C++11で 追加されたスマポです。
リソースの所有権を共有し、所有者がいなくなると自動的にリソースの解放を行います。
内部に参照カウンタを持ち所有者が増えるとカウントアップし、所有者が消えるとカウントダウンします。
このカウントが0になった時点でリソースが解放されます。
使用例
所有者の増加と解放のタイミングを確認してみます。
#include <iostream> #include <memory> class Hoge { public: Hoge() { std::cout << "メモリが確保されました。" << std::endl; } ~Hoge() { std::cout << "メモリが解放されました。" << std::endl; } }; int main() { // 1人目の所有者 std::shared_ptr<Hoge> p1(new Hoge()); // 所有者数を確認 std::cout << "現在の所有者数 : " << p1.use_count() << std::endl; { // 2人目の所有者 auto p2 = p1; // 所有者数を確認 std::cout << "現在の所有者数 : " << p2.use_count() << std::endl; } // ここでp2が削除され、所有者が一人減る // 所有者数を確認 std::cout << "現在の所有者数 : " << p1.use_count() << std::endl; return 0; }
出力はこんな感じです。
メモリが確保されました。 現在の所有者数 : 1 現在の所有者数 : 2 現在の所有者数 : 1 メモリが解放されました。
無事、メモリが解放されることが確認出来ました。