20190310のUnityに関する記事は6件です。

【Unity】EditorGUIUtility.SetIconSizeを使うよりIconSizeScopeを使おう

概要

EditorGUIUtilityにはGUILayout.LabelなどGUIContentで表示するアイコンのサイズを変更できるSetIconSizeという関数があります。この関数を呼ぶことで、この場面ではアイコンを小さくしたい、この場面では大きくしたいなど状況に応じて変更を行うことができます。

ただ、デフォルトのサイズに戻したいときに、変更前にGetIconSizeでデフォルトのサイズを取得して変数に保持しておくのは手間に感じました。これを改善できないか調べた所IconSizeScopeを使うことで、上記の問題が解決しそうだということがわかりました。

IconSizeScopeとは

IconSizeScopeとは、GUI.Scopeを継承したScopeです。
GUI.Scopeはusingを使うことでコンストラクタで初期化処理を、using内の処理が終わったあとに呼ばれるIDisposable.Dispose()で解放処理を呼ぶことができるものです。
IconSizeScopeではコンストラクタにVector2を渡すことでアイコンのサイズを設定し、Disposeで変更前のサイズに戻してくれます。

実装

例として32x32の大きさでアイコンを表示し、それ以降はデフォルトのサイズで表示するように実装したいと思います。まずIconSizeScopeを使わなかった場合はこうなります。

NoneIconSizeScopeTest.cs
//あらかじめデフォルトのサイズを取得
var defaultIconSize = EditorGUIUtility.GetIconSize();

//32x32で表示するように設定
EditorGUIUtility.SetIconSize(new Vector2(32,32));

//アセットの表示に使われているアイコンを持ってきて表示
var texture = AssetDatabase.GetCachedIcon("アセットのパス");
GUILayout.Label(texture);

//デフォルトのサイズに戻す
EditorGUIUtility.SetIconSize(defaultIconSize);

事前にデフォルトのサイズを取得しないといけなかったり、最後に元のサイズに戻さないと行けなかったりと意識しないといけない所がありますね。

ではIconSizeScopeを使うとどうなるかというと、こうなります。

IconSizeScopeTest.cs
//using内では32x32のサイズで表示するように設定
using(var iconSizeScope = new EditorGUIUtility.IconSizeScope(new Vector2(32,32)))
{
    //アセットの表示に使われているアイコンを持ってきて表示
    var texture = AssetDatabase.GetCachedIcon("アセットのパス");
    GUILayout.Label(texture);
}

//usingの外に出たためDisposeが呼ばれ、以降はデフォルトのサイズで表示される

意識しないといけなかった所がなくなって、呼び出すだけでよくなったためシンプルな構造になりました。
また、usingを使っているためこの中では32x32のサイズになっていると明示的に確認できるようになったため、他のアイコンも一緒に表示したいときにもこの中に入れれば表示されるというのがわかりやすくなったのも良い点だと思いました。

まとめ

IconSizeScopeはGUI.Scopeを継承しているため、コンストラクタでアイコンのサイズを変更し、解放処理で元のサイズに戻すことができました。
Scope系は他にもあると思いますので、調べたらまた記事にしたいと思います。

参考

IconSizeScope

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】ゲームオブジェクトを毎回違う場所に配置する方法

  
  
  
この記事は

『プログラミング完全未経験からUnityでの開発現場に迎え入れてもらえた世界一の幸せ者』

の記事です。そのつもりでお読みください。

ランダムではない

毎回違う場所に配置すると書きましたが、
ランダムとは少しだけ違います。

私がやりたかったことは2回連続で同じ場所には絶対に出ないという実装です。

なのでちょっと面倒でした。

↓↓↓補足(2019.3/11)↓↓↓

同じエリアに連続出現しないだけで座標は毎回ランダムにしたい

これです!まさにこれ!
コメントありがとうございます!

考え方

まずゲームオブジェクトを出現させたい位置を分割します。
randomArea.png

上のとてもわかりやすい図のように分けた場合、
もし0に出現したら次は必ず1、2、3のどこかに出現するようにします。

もっと効率の良い方法があるのかもしれませんが、ちょっと急いでたのでこのやり方で実装しました。

値を返すメソッドを用意

ランダムな値を渡すとその値と同じ値を返すメソッドを用意しました。
これにより、一度出現したエリアの番号を記録できます。

 int? RandomArea(int patternNum)
    {
        if (patternNum == 0)
        {
            holeParentPosition = new Vector3(randomX_A, holeParentPosition.y, randomZ_A);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 0;
        }

        if (patternNum == 1)
        {
            holeParentPosition = new Vector3(randomX_B, holeParentPosition.y, randomZ_B);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 1;
        }

        if (patternNum == 2)
        {
            holeParentPosition = new Vector3(randomX_C, holeParentPosition.y, randomZ_C);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 2;
        }

        if (patternNum == 3)
        {
            holeParentPosition = new Vector3(randomX_D, holeParentPosition.y, randomZ_D);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 3;
        }

        return null;

    }

最終的なコード

    public GameObject hole;

    Vector3 holeParentPosition;

    int[] patternNumber = { 0, 1, 2, 3 };
    int patternArray;


    float randomX_A;
    float randomZ_A;

    float randomX_B;
    float randomZ_B;

    float randomX_C;
    float randomZ_C;

    float randomX_D;
    float randomZ_D;

    void Start()
    {
        holeParentPosition = this.gameObject.transform.localPosition;
    }


 void Update()
    {
        patternArray = Random.Range(0, patternNumber.Length);

        randomX_A = Random.Range(0.5f, 4.0f);
        randomZ_A = Random.Range(-5f, -1.5f);

        randomX_B = Random.Range(0.5f, 4.0f);
        randomZ_B = Random.Range(-9f, -5.5f);

        randomX_C = Random.Range(-4.0f, -0.5f);
        randomZ_C = Random.Range(-9f, -5.5f);

        randomX_D = Random.Range(-4.0f, -0.5f);
        randomZ_D = Random.Range(-5f, -1.5f);

        //コルーチン開始(一回で抜ける)
        if (ThisProjectSingleton.Instance.isGameStart)
        {
            StartCoroutine(HoleRandomCoroutine());
            ThisProjectSingleton.Instance.isGameStart = false;
        }


    }

    int? RandomArea(int patternNum)
    {
        if (patternNum == 0)
        {
            holeParentPosition = new Vector3(randomX_A, holeParentPosition.y, randomZ_A);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 0;
        }

        if (patternNum == 1)
        {
            holeParentPosition = new Vector3(randomX_B, holeParentPosition.y, randomZ_B);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 1;
        }

        if (patternNum == 2)
        {
            holeParentPosition = new Vector3(randomX_C, holeParentPosition.y, randomZ_C);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 2;
        }

        if (patternNum == 3)
        {
            holeParentPosition = new Vector3(randomX_D, holeParentPosition.y, randomZ_D);
            this.gameObject.transform.localPosition = holeParentPosition;

            return 3;
        }

        return null;

    }

    IEnumerator HoleRandomCoroutine()
    {
        int? tmpNum = 0;
        int? memoryNum = 0;
        while (/*ゲームオーバーの条件を記述*/)
        {
            yield return new WaitForSeconds(0.5f);
            tmpNum= RandomArea(patternNumber[patternArray]);

            //もしランダムな数字( tmpNum)が前回(memoryNum)と一致したら通過。別の数字をtmpNumに代入して抜ける
            while (tmpNum == memoryNum)
            {
                yield return new WaitForEndOfFrame();
                tmpNum = RandomArea(patternNumber[patternArray]);
            }
            memoryNum = tmpNum;
            hole.SetActive(true);


            //ホールにボールが入るまで待つ
            yield return new WaitUntil(() => ThisProjectSingleton.Instance.isHoleEnter);
            ScoreText.ScoreAdd();

            //ホールが消える
            yield return new WaitForSeconds(0.5f);
            hole.SetActive(false);
            ThisProjectSingleton.Instance.isHoleEnter = false;
        }
    }

いま自分で読み返しても読みづらいしもっといい方法がありそうです。
こんな方法もあるよ~って方、コメントください。

unity1week

unity1weekという企画がありまして、
その名の通りUnityを用いて一週間でゲームを作ろうというおもしろい企画です。

私も参加するつもりなのですが、
そこで作るのはWebGLというブラウザ上で起動する形式です。

せっかく作ったのにビルドとかアップロードの仕方がわからない!
と本番でならないようにunity1dayと称して、
簡単なゲームを作りunityroomにテストアップロードしました。

今回紹介したゲームオブジェクトを毎回違う場所に配置する方法も用いているので
よかったら覗いてみてください。↓

Lava is hot(訳:溶岩はあったかいよ)

人のコード見た後にそのゲームをプレイするの結構おもしろいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSに3D姿勢推定を実装してみた

昨年から機械学習の勉強として仕事にも使えるかと思い姿勢推定に挑戦しています。まだ全然理解できていないですが勢いで3Dの姿勢推定にも挑戦しています。Twitterに途中経過をアップしていますが、思いの外問い合わせが増えてきたので、まだモデルも自分も学習の途中ですが簡単にここにまとめます。

下の動画が2019/3/10時点での最新の結果です。昨年のゴールデンウイークから機械学習の勉強を初めて、今年の正月休みに3Dのコアになる所を一気に作りました(昨年進めてた2Dの環境を3D向けに改修した)。その為かなりバグやミスが多かったので、なおし直し(だましだまし)モデルの学習を進めてきたので、以下の説明でうまく行かなかった等の話はそれらのバグが原因も十分考えられますので、その点はご了承ください。

構成

学習環境は、Windows10でVS Code上でPyTorch0.4。
実行環境は、iPhone XSMaxを使用しています。XSMaxを使用しているのはA12プロセッサが非常に強力だからです。ですのでXSでもXRでも良いと思いますが、iPhone8等のA11プロセッサではCoreMLの実行速度が半分以下に落ちてしまうようです。コーディングでもっと速度を稼ぐことができるのかも知れませんが、今の所それに挑戦はしていません。

Model

モデルはMobileNetV1をベースにしています。層が深い方が精度が上がるという話を聞いたので何層か追加して試してみました。目に見える結果はほとんどなかったのですが、なんとなくそのまま使っています。時間があれば色々比較してみたいんですが時間もないのでそのままです。
推定はSinglePersonに限定しています。MultiPersonは出力から座標を求めるのが結構面倒なのでまずはSingleのみです。
入力は224サイズの画像です。出力は2Dと3Dの両方を出力するようにしています。2Dは14x14x関節数のHeatMapと14x14x関節数x2(x,y座標)のオフセット値の出力です。このあたりはPoseNetを参考にしました。3Dも同様の発想で、14x14x14x関節数のHeatMapと14x14x14x関節数x3(x,y,z座標)のオフセットとなっています。3Dも回帰での推定です。今は関節数が24なのでかなり大きな出力になってます。3Dの姿勢の推定には、14x14の2DのHeatMapからx,y,zのオフセットで補正をかけて推定した方が出力サイズも小さく後の処理も楽で良いのですが、どうにも奥行き方向の感度が上がらなかったので、3DのHeatMapを使用するようにしました。

Dataset

データセットも2Dと3Dのデータセットを用意しました。2DのデータセットはLeeds Sports Pose Dataset, Leeds Sports Pose Extended Training Dataset, MPII Human Pose Dataset, Microsoft COCOです。3Dのデータセットはオリジナルです。Unityで3Dのキャラクターのアニメーションを作り、カメラオブジェクトに写っているキャラクタの画像とボーンの各座標(肩・肘・手首・親指・中指の付根・足・膝・足首・爪先・耳・目・鼻・身体の中心(へそ位置))を出力するようにしました。キャラクタは十数体用意しました。フリーの物やAssetStoreで購入したデータ、もちろんUnityChanも含まれています。ライセンス的にややこしくなるのでこのデータセットは公開できません。UnityChanは問題ないと思うので下のような画像です。
im07078.png

学習

CGなのでキャラクタのテクスチャやポーズはかなり自由に変えれます。初めは各エポック毎にデータセットの内容が変わるようにすれば汎化性能が上がるかなと期待していたのですが、全然効果がなかった(毎回変わるとテストもしずらい)ので10万枚くらいの画像を作りその画像を学習に使用しています。
この3Dの画像でもそれなりには学習するのですが、どうしても似通った画像になってしまい期待した性能が得られません。”なんとか学習”という2つの学習をすると性能が上がるという話を聞いたので2Dの姿勢推定の学習も同時に行うようにしました。2Dのデータセットはその為に使っています。
学習はPyTorchを使っています。オプティマイザはAdam、なんかAdaBoundなんてものがあるそうなのでそのうち試してみたいです。

実装

PyTorchで学習したモデルをonnxにexportして、coremltoolsでCoreMLのモデルに変換します。この時点で同じ画像の推定を行っても結果が異なっているので、精度がどこまで出ているのかわかりません(よくなってるって事はないと思う)。これをMacに持って行ってXCodeでiPhone用のアプリに実装します。バックカメラの画像をリアルタイムにキャプチャして3Dの推定を行うと、私のXSMaxでは40fpsくらいで動かせるくらいの速度で実行できます。ですが、しばらく使っていると本体があったかくなってきて30fpsくらいに落ちてきます。流石に重いんだと思います。2Dのみ学習したモデルだと100fps近くで動作しています。
3Dで表示する際にカメラから見えない部位も表示したい為、ヒートマップの判定の閾値をほぼ0に下げています。どういうことかと言うと、例えば普通に腕が見えている場合であればヒートマップの一番大きいところは0.5以上になります(Maxは1.0)。そして流石に腕が見えていないとそこにありそうと思っても0.2とか0.1になってしまいます。その為閾値を下げていますが、結果的にどこを見ても人がいると判定している状態になります。

今後

動画を見ていただいたらわかると思いますが、それっぽく動いてますが細かい所は全然残念な感じです。もう少し精度をあげたいので、もう少し大きいモデルを使う、人間ぽさを出す為にGANを使うなどを考えています。あと、1epoch計算するのに7,8時間かかるのをなんとかしたい。これにGANとか入れたら何時間になるのか。。。
それとどこかで落ち着いたらswiftのコードだけでもGitにあげたいと思います。

参考

Real-time Human Pose Estimation in the Browser with TensorFlow.js
PoseNetのBlog記事。これならできんじゃね?と思って機械学習の勉強を始めたので個人的に思い入れがあるページ。

PyTorchでDeepPoseを実装してみた
そもそも機械学習どころかPythonでどう書くねんと言うときに参考にさせていただいたページ。なので必然的に今もPyTorchを使ってます。この記事がなかったら挑戦してなかったかも。

PoseNetをマルチプラットフォームで実装してみた
この記事を読んで、ソースまで上げてくれてるしこれなら俺でもiPhoneで動かせるんじゃね?と思って試して見たページ。穴が開くほど読みました。

Microsoft/human-pose-estimation.pytorch
Microsoftによる、PyTorchを使用した姿勢推定のコード。MPIIとCOCO用。とても参考になりました。

DeepPose: Human Pose Estimation via Deep Neural Networks
DeepPoseの論文の説明。日本語なので論文読むよりわかりやすいです。

apple/coremltools
Appleのcoremltoolsです。これがないとCoreMLのモデルになりません。願わくばもっと簡単に変換できるようになるとうれしいです。

ミソジサラリーマンMisozi-Salaryman-game style action-
ミソジサラリーマン様のアクションの動画。フリー素材として使用可と言うことでありがたく使用させていただいています。アクションのキレが良すぎて姿勢推定にはなかなかハードルが高い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityのHDRP環境でTerrainツールを使ってみたまとめ

概要

記事執筆時点
2019/03/09

エンジニアがUnityのTerrainツール使って背景っぽいものを作るのにあたって書き残したメモ。
HDRPに途中から切り替えたので、そのとき当たった壁の解消法も残しています。

作業環境

Platform Unity Version
Windows 2018.3.8f1
  • Terrainツール
    • UnityTerrainEditor
  • RenderPipeline
    • HighDefinition Render-Pipeline
  • PostProcess
    • PostProcessStack

だいたいこんな感じのものが出来あがる想定。
image.png

なんでUnityのTerrainツール?

なんとなく使ってみたかったので。
RenderPipelineとかShaderGraphとか使うならついでに勉強できるかなという期待

参考にしたマニュアル

Unity - Manual: Terrain Engine

  • 日本語マニュアルも存在するが、バージョンが古いためツールの操作の参考にはならない

    • わからない単語とかTerrainオブジェクト設定のプロパティの確認に使うのがよさそう
  • Terrainオブジェクトを新しく作ったり高さ全リセットするとベイクが走る

    • そこそこ時間がかかる
    • とは言え広さと解像度によると思うけど数分で終わる
    • HDRPにすると時間が増えた気がする

Unity - Manual: Terrain Layers

TextureのLayerの設定
下記で紹介しているTerrainToolkit使うとほとんどLayerを直接触らなくなった

2018.3 Terrain Update: Getting Started – Unity Blog

2018.3でアップデート、追加されたTerrainの機能。

AssetStore, Packageでインポートしたアセット

Terrain Toolkit 2017 - Asset Store

Terrainのテクスチャとかディティールとか自動で作ったりするツール群
高さに応じたテクスチャのレイヤーのブレンドもやってくれる

Post Processing Stack - Asset Store

Unity公式のPostProcessアセット。
ポストエフェクト。
レンダリング時に色調補正したりDOFを加えたりするのに使用する
Layer設定でDefaultのままだとパフォーマンスに影響出るかもって警告を出力されるのでとりあえず変えておく

RenderPipelineの変更

HD レンダーパイプライン:アーティストのためのクイックスタートガイド – Unity Blog

手順は上記UnityBlogの記事が参考になります。
プロジェクトを途中からHDRPに変更する際にやることとしては、下記項目になります。

  • PackageManagerでRenderPipelineのパッケージをインストールする
    • Render-PipelineCore
    • HighDefinition RenderPipeline
  • ProjectSettingのRenderpipelineアセットにHDRenderpipelineアセットをアサイン
  • マテリアルをHDRP対応したものにアップデートする
    • メニューから一括でアップデート出来るようになっているので使う
  • 出力するカラースペースをGammaからLinearに変更する

High Definition Render Pipeline overview · Unity-Technologies/ScriptableRenderPipeline Wiki
HDRPの機能リファレンス

注意点

パッケージをインストールする順番
PackageManagerから入れるRenderpipelineは、最初にCoreをインストールしましょう。
.
RenderpipelineCore

HDRenderPipeline
.
の順番になります。

Consoleに下記のエラーが出力される

High Definition Render Pipeline doesn't support Gamma mode, change to Linear mode

Unity - Manual: Linear or gamma workflow

単純にHDRPがガンマ出力をサポートしてないみたいなので、Linear出力するようにプロジェクトの設定を変更しましょう。
プロジェクト作成時にHDRP選択してないとデフォルトはガンマ出力になってるっぽいです。

作ってたTerrainオブジェクトが表示されなくなる

マテリアルがピンクになるでもなく、単純に表示されなくなりました。
試しに新規でTerrainオブジェクトを作ってみると表示されます。
新しく作ったところと古いオブジェクトの設定を見比べるとマテリアルに差がありました。

image.png

ということで、HDRP対応のTerrainマテリアルをアサインする必要があります。
上図のようにTerrainのプロパティの設定からMaterialをCustomにしてHDTerrainMaterialを選択。

AssetStoreの大体のアセットが対応してない
まだPreview版ですしね。
フォーラムに色々書き込んだらUnityの人とかが対応してくれるかもしれない。

でもRenderPipelineが公式対応されるとアセットストアで配布してる人はRPの対応を迫られるんでしょうか。
疑問。

ここまでの成果物

ポストプロセス適用

image.png

だいたいモデル完成

image.png

モデル作成中

デバッグテクスチャで境目をわかりやすく表示
image.png

ラフ絵

image.png

その他メモ

作業中のメモ書き
作業のメモ書きにQiitaを使用。
テキストが書ければ何でも良かったけど、WEB上で書いて勝手に下書き保存してくれるのでなんとなく選択。
あとでどうせQiitaに投稿するつもりでいたので最初からそこで書いてしまった方が早いなということで。

WindowsのPCで実作業、MacbookとかiPhoneでQiitaとSlack見て振り返りとかする
ただし資料もだいたいWEBのものを見るのでChromeのタブが無限にできる。
メモリが!足りない!
メインメモリ最低16GBはエンジニアの人権に最低限本当に必要。

自分との会話

若干怪しげな単語に見えますが、Qiitaにメモるまでもない思いついたこととかTodoを
Slackの自分だけのグループチャンネルにバンバン投稿してメモっています。
しばらくした後の自分がそのメモったスレッドに返信していく感じ。

何するか、何をしようとしていたか、今の状況のスクショとかをSlackで書いていってます。
後で「なんだっけ・・・」「何しようとしたんだっけ・・・」ってなったときに一応見返せる。
自分用だから文章もかしこまらないでOK。
ちゃんとした作業ログを残すならそういう目的を持った別のサービスを使った方がいいとは思います。

参考

Unity - Manual: Terrain Engine
UnityのTerrainで大地を創る - Qiita
Terrain Toolkit 2017 - Asset Store
【Unity】HDRPを使ってみる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity初心者やってみた2

前回の続き https://qiita.com/akagane99/items/92a1ca9bfb841d9b63a8

Unity 5でコインプッシャーゲームを作ろう(前編) | Think IT(シンクイット)

https://thinkit.co.jp/story/2015/07/22/6223

Unity 5の記事だけど、Unity最新版(2018.3.8f1)でも使える

AddComponent > [Physics]→[Rigidbody]

rigidbodyはオブジェクトに物理演算を適用するための機能です。これを追加するだけで、自然落下運動などの物理運動を再現してくれます。

行動予定: Unityで作った途中経過をWebで公開する。

参考記事

UnityのWebGLで書きだしたゲームをGitHubを使って公開する - Qiita
https://qiita.com/kenta71/items/c32760e1cc2ba01e8f9a

操作

  1. メニューバー > File > Build Settings
  2. WebGLを選択 > Swich Platformボタン押下
  3. Player settingsボタン押下
  4. 設定変更
  5. Buildボタン押下
  6. ビルド中(結構時間かかる)

ビルド中

image.png

細かめメモ

  • コーディングルールっぽいもの
    • Hierarchyのオブジェクト名は、とりあえず頭大文字で統一

// 行動予定・行動結果

  • 行動予定
    • 記事投稿日は、随時更新
  • 行動結果
    • 30分行動した(1.5時間/20時間)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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上でパーティクルをいつも通り設定できるメリットは大きいので、個人的には満足度高めです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む