Edited at

[Unity][ParticleSystem]3D座標を起点にuGUI上にパーティクルを発生・収束させる


サンプル動画

3Dアクションゲームなどで良くある、

「敵を攻撃したらパーティクルが発生して、UI上のゲージに向かって飛んでゲージが溜まる。」

というものを実装してみました。

(割と需要あると思っているのですが、どこにもサンプルが無かったので自作することに)


サンプルプロジェクト

以下のGithubに上げてあります

https://github.com/madoramu/ParticleHorming

※サンプル動画は別プロジェクトの物なので、上記URLのプロジェクトは少し内容が異なります。


環境

Unity2018.3

WindowsとAndroidで動作確認済み

重要:ScreenSpaceCamera設定のCanvasのみ対応しています


実装方法

サンプルプロジェクトの中身を抜粋して説明していきます。

細かい部分などはプロジェクトを閲覧してください。


メインカメラとUIカメラの用意・設定


メインカメラ

こちらはキャラクターの追尾、およびUI以外の描画を担当。


  • CullingMaskで「UI」を除外する。

  • Depthを「0」にする。


    • UIカメラより低ければおk




UIカメラ

UIの描画のみ担当。


  • メインカメラには絶対映らない場所に移動させる


    • AudioListenerは外しておく



  • ClearFlagsを「Depth Only」にする。

  • CullingMaskで「UI」だけにする。

  • Depthを「10」にする。


    • メインカメラより高ければおk



上記二つのカメラ設定により、メインカメラの描画の後にUIレイヤーの情報が上に描画されます。


キャンバスの用意と設定(重要)

UI表示用・座標変換用で2つのScreenSpaceCamera設定Canvasを用意します。


  • UI表示用にはUIカメラ、座標変換用にはメインカメラをそれぞれ設定する

  • RectTransform以外のパラメーターは全て同じにする事


    • 一応念のため



※何故設定カメラ以外同じパラメーターのCanvasを二つ使用したのかは感想に記載しています。


ParticleSystemの設定

ここは好みに合わせて弄って問題ありませんが、以下の2点は必須です。


  • ParticleSystemオブジェクトのLayerは「必ず「UI」に設定する事

  • UI表示用のCanvas直下に配置する事

今回は以下のように設定しました(プロジェクトから抜粋)

キャプチャ.PNG

「何故World設定か?」


  • 後述のパーティクル発生処理で解説しますが、ParticleSystemそのものを移動させるので、Localだとその移動にパーティクルも追従してしまうため。

「Rederer→Order in Layerについて」


  • 上記画像には映っていませんが、今回はUIより前面に出したかったため値を増やしました。ここはお好みでおk。


パーティクル発生処理

ParticleSystem.Emitを使用して、意図的にパーティクルを発生させることが出来ます。

https://docs.unity3d.com/ja/current/ScriptReference/ParticleSystem.Emit.html


処理の流れ


  • 3D座標からScreenSpaceCanvas上の2D座標に変換


    • ゲームだと主に敵に攻撃が当たった時の、敵座標位置が3D座標になります。

    • テラシュールブログを参考(というかほぼ丸パクリ)にして作成しました。

    • 下記ページの「World Space を Screen Space Cameraへ」です。

    • http://tsubakit1.hateblo.jp/entry/2016/03/01/020510



  • 変換した2D座標にParticleSystemを移動

  • Emitでパーティクル生成。

以下コード抜粋


ParticleHorming.cs

public void CreateParticle()

{
// 3D空間座標からカメラスクリーン上の座標に変換する
Vector3 basePos = m_Emit3DTransformList.GetRandom().position; // GetRandom()についてはListExtensionsを参照
Vector2 screenPos = m_Camera.WorldToScreenPoint(basePos);

// カメラスクリーン座標をキャンバス上のローカル座標に変換する
Vector2 cameraCanvasPos = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(m_RaycasterCameraCanvasRectTransform, screenPos, m_Camera, out cameraCanvasPos);

// 座標確認
Debug.LogFormat("rectPos{0}", cameraCanvasPos);

// ParticleSystemを放出位置に移動させてEmit
m_ParticleSystem.transform.localPosition = cameraCanvasPos;
m_ParticleSystem.Emit(m_EmitParticleCount);
}



パーティクル更新(収束)処理

普段パーティクルをスクリプト側で操作することは余り無いですが、今回は特定位置に収束させたかったのでググった結果、公式サイトで以下のページが出てきました。

https://docs.unity3d.com/ja/current/ScriptReference/ParticleSystem.GetParticles.html

このページを参考に以下の様な処理で更新させることにしました。


ParticleHorming.cs

// 更新

for(int i = 0; i < m_ActiveParticleCount; ++i)
{
float rate = (1.0f - m_ParticleList[i].remainingLifetime / m_ParticleList[i].startLifetime);
rate = Mathf.Pow(rate, m_ReactionDistance); // 指数関数を加えることにより、収束する勢いを変更できるようにしてる
m_ParticleList[i].position = Vector3.Lerp(m_ParticleList[i].position, m_TargetTransform.position, rate);
}
m_ParticleSystem.SetParticles(m_ParticleList, m_ActiveParticleCount);

今回は指数関数を使って、等速直線移動ではなく少しアレンジをしています。

ここに関しては書き方によって幾通りも表現が出来ると思います。


感想

当初はScreenSpaceCameraのCanvas一つで解決できないか試行錯誤していましたが、キャラを追尾する = Canvasが移動する関係上、パーティクルが毎フレームぶれたり座標がおかしなことになったりした結果、今の形に落ち着きました。

冒頭でも言った通りScreenSpaceOverlay設定のCanvasには対応できないので、結構使いどころは限定的かも。

ですが、ParticleSystem上でパーティクルをいつも通り設定できるメリットは大きいので、個人的には満足度高めです。