テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】シーン内のGameObjectやAsset、メニューアイテムを検索できる「Unity Quick Search」

f:id:tsubaki_t1:20190301004750j:plain

プロジェクト内のオブジェクトやアセット、メニュー操作等を検索出来る「Unity Quick Search」がPackage Managerに追加されました。

Unityプロジェクト内の色々な物が検索できる

Unity Quick Searchは、OSXの「Spotlight」やWindowsの左下にあるなにか(検索ボックスというらしい)に似た機能です。この機能では、ショートカットキーからテキストボックスを起動し、色々なものが検索出来ます。

例えば

  • アセット(名前ベース)
  • シーン内のオブジェクト
  • メニューアイテム
  • ソースコード(名前のみ、Package含む)
  • 設定
  • アセットストア

等々、割と色々と探せます。

なおUnity 2019.1で確認しており、それ以前のバージョンで使えるかは見ていないです。

使い方

使い方は簡単、日本語キーボードだと「Alt + ^」(英語だとAlt + ' ?)を押して、メニューを開きます。

あとは検索したいキーワードを入力して、検索するだけです。SceneやAssetのオブジェクトは、ドラッグ&ドロップで配置等も出来ます(トップのGifアニメのように)

f:id:tsubaki_t1:20190301001615g:plain

f:id:tsubaki_t1:20190301003155g:plain

詳しい使い方は、マニュアルがGifアニメ大量で非常に分かりやすいので、そちらを

docs.unity3d.com

拡張してみた

この機能は拡張できるそうです。
せっかくなので、拡張してみます。

やってみたのは、指定のタグを使用しているオブジェクト一覧を取得というものです。タグはオブジェクトに依存した挙動を作る際には使うことがあるのですが、何故か未だにタグを検索する機能がありません。なので今回それを作ってみました。

検索で「tag:○○」と入力すれば、○○と一致するタグを見つけてきます。

コードのポイントは、SearchActionsProvider属性を持つメソッドと、SearchItemProvider属性を持つメソッドを定義しておけばOKです。

検索対象はfetchItemsに突っ込む内容で判断しています。

それ以外は基本コピペです。AssetとSceneの二通りの検索例があるので、その辺りでコピペすれば何とかなります(多分)

gist.github.com

補足

メニューの検索は、英語が前提です。日本語のメニューは検索にヒットしません。

f:id:tsubaki_t1:20190301004015j:plain

試した所、複数の要素で絞り込みは出来ませんでした。やり方が悪い?

感想

OSXのSpotlightをよく使う勢としては、割とありがたいです。

(ところでProject Viewの検索はもうちょっと賢くなっても良いと思う。具体的には新しく追加されたタイプに対応するとか)

【Unity】UnityでAlembicを使ってみる

Package ManagerにAlembicが追加されたので、試してみました。

Alembicとは?

Alembic for Unityの説明が分かりやすいので引用します。

Alembic は主に映像業界で使われているデータフォーマットで、巨大な頂点キャッシュデータを格納するのに用いられます。 映像業界では、スキニングやダイナミクスなどのシミュレーション結果を全フレームベイクして頂点キャッシュに変換し、それを Alembic に格納してレンダラやコンポジットのソフトウェアに受け渡す、というような使い方がなされます。
Alembic 本家: http://www.alembic.io/

要するにモデルをボーンで変形させるのではなく、頂点まるごとストリーム再生する的な感じでしょうか。おかげで、下のような水のシミュレーションも比較的低負荷で再生することが出来るみたいです。

f:id:tsubaki_t1:20190228135324g:plain
Alembic for Unityのページより引用

Unity PackageにAlembic Importerが追加

先日、Alembic ImporterがPackage Managerに追加されました。しかもPreviewをすっ飛ばして最初からリリースされています。

f:id:tsubaki_t1:20190227222731j:plain
PackageManagerから確認出来る

それもそのはずで、本質的にはGithubで公開されているAlembic for Unityを使用しているっぽいです。正確には若干異なるみたいですが、まぁ確かに実績のあるシステムなので、最初から問題が無いと判断したのでしょう。

ということでUnity 2018.3で導入してみると導入出来ません。

f:id:tsubaki_t1:20190227223146j:plain
Unity 2018.3でAlembicを導入してみた結果

どうやらPackage ManagerのAlembicはTimelineのパッケージを要求するみたいです。

つまりPackageのAlembicが使えるのは実質的には2019.1からで、まだTimelineがPackage化していないUnity 2018.3では使えません。その場合は普通にGithubのAlembic for Unityを使うのが良さそうです。

github.com

使い方

使い方に関して言えば、正直Githubのリポジトリにある説明が詳しいです。

日本語のマニュアルがあります。一部若干違う部分もあるかもしれませんが、多分フィーリングで何とかなります。

実際に試してみる

実際に試してみます。

とは言え、Alembic(.abc)ファイルを見つける事が出来なかったので、とりあえずAlembicをExportしてImportしてみます。

まず最初に シーンの動きをAlembicとして出力 してみます。

適当なオブジェクトに Exportコンポーネント(Package managerならば Alembic Exportコンポーネント)を追加します。あとはBegin Recordingボタンを押してゲームを再生→終了です。ゲームを終了するとレコードも停止します。

https://user-images.githubusercontent.com/1644563/53494896-17555900-3ae2-11e9-82df-0227bf332dc3.gif

これで .abc ファイルがOutputフォルダに出力されます。

f:id:tsubaki_t1:20190227225125j:plain
プロジェクトフォルダ/Outputにファイルが出力された

コレをUnityに再インポートして使ってみます。

Alembic 導入後は普通にドラッグ&ドロップで .abc が使えます。ただしインポート直後はマテリアルが剥がれているので、設定する必要があります。マテリアルの設定は普通のUnity的なマテリアル設定手順です。

マテリアル差し替える時に気づいたのですが、出力前はSkinnedMeshRendererだった物もすべてMeshRendererに差し替わっています。ボーンベースのスキニングではなく、頂点を動かしているという事でしょう。

f:id:tsubaki_t1:20190227225622j:plain
導入直後はマテリアルが剥がれている

マテリアル設定後はAlembic Stream PlayerのTimeを動かしたり、TimelineのAlembic Trackを使用することでストリーム再生が出来るみたいです。

f:id:tsubaki_t1:20190227230142g:plain

感想

自分の出来る範囲内では、以前紹介したRecorderと似たような立ち位置ですが、Alembicはストリームで再生出来るという点で良いように思います。代わりに .abcファイルはStreamingAssets等のUnityが観測しないIOに入れる事になりますが、逆にAssetBundleを使わず使えるので、利点の一つといえるのかもしれません。

正直、VRは幾つかのインタラクティブ要素を除いたらAlembic等をストリームで流し込む方が理に適ってるんじゃないかなと個人的には思わなくも(モバイルだとIO周りで詰まりそうですが)

関連

Recorderを使用するアプローチ

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

blogs.unity3d.com

www.youtube.com

【Unit】NativeListをIJobParallelForで使いたい

今回はNativeListIJobParallelForを使用して利用してみます。

NativeListをIJobParallelForで使う

NativeListは要素を増減出来るという点で言えば便利な機能です。ただしIJobを使用している場合には大体の機能を使えるのですがIJobParallelForで書込を行おうとすると、InvalidOperationException: The previously scheduled job Job名 reads from the NativeArray NativeList名. You must call JobHandle.Complete() のようなエラーが表示されて実行できません。

これを回避するには、NativeList.AsDeferredJobArray()を使用して、NativeListをNativeArrayとして扱います。これはコピーではないので、NativeArrayの変更はNativeListにも反映されます。

なお[ReadOnly]を使用している場合には、こんな事をせずともListのまま使用できます。また、NativeArrayに変換しているためAddは出来ません。
また、Jobのパッケージが必要です。

f:id:tsubaki_t1:20190224221935j:plain
JobパッケージのIJobParallelForDeferExtensionsが必要です

使い方(NativeListの用意)

まずNativeListの用意をします。NativeListのAddは並列処理出来ないので、そこは諦めます。

ジョブ側

struct AddJob : IJob
{
    public NativeQueue<int> queue;
    public NativeList<int> list;

    public void Execute()
    {
        while (queue.TryDequeue(out int item))
        {
            if (item % 2 == 0) // 2で割り切れる要素だけ追加
                list.Add(item);
        }
    }
}

呼び出す側

 // Listに登録する要素を
NativeQueue<int> queue = new NativeQueue<int>(Allocator.TempJob);
queue.Enqueue(3); queue.Enqueue(2);
queue.Enqueue(4); queue.Enqueue(5);

NativeList<int> list = new NativeList<int>(8, Allocator.TempJob);

// 内容の更新
var handle = new AddJob { queue = queue, list = list }.Schedule();

使い方(NativeListの内容を更新)

次にNativeListIJobParallelForで更新します。更新する際には上に書いたとおりAsDeferredJobArrayを使用します。特に指定せずとも自動で変換されるっぽいですが、精神の安全のために使用しておきます。

まずジョブですが、普通にNativeArrayを使用します。ここにNativeListの要素が格納されます。

struct UpdateJob : IJobParallelFor
{
    public NativeArray<int> array;

    public void Execute(int index)
    {
        array[index] += 1;
    }
}

使用する側は少しだけ特殊です。まずNativeListをNativeArrayに変換しているのが一点、そしてジョブの長さにNativeListを設定する点です。

後者は普通にNativeListの長さがジョブ発行時にはわからないので、そういった場合の対策といった感じでしょうか。

    handle = new UpdateJob {
        array = list.AsDeferredJobArray()   // AsDeferredJobArrayでNativeArrayにする
    }.Schedule(list, 4, handle);            // 要素数の部分にはlistを登録

これでListの中身を並列処理で書き換えました。

中身を確認する

最後に中身を確認します。こちらは普通に[Readonly]な NativeListで問題ありません。

struct ShowLogJob : IJobParallelFor
{
    [ReadOnly] public NativeList<int> list;

    public void Execute(int index)
    {
        Debug.Log(list[index]);
    }
}

使う側も概ね同じです。

handle = new ShowLogJob
{
    list = list
}.Schedule(list, 8, handle);

実行結果

処理の内容はコードは、

  1. 3/2/4/5をQueueに追加
  2. 2で割り切れる数(2/4)をListに追加
  3. Listの要素に1を追加(2/4 が 3/5になる)
  4. Listの中身をDebugLogで表示

という内容で、実際確認してみると、ちゃんと期待通りの数字が出ています。

f:id:tsubaki_t1:20190224223223j:plain

コード全文(を少し改造したもの)

gist.github.com

感想

NativeListで並列処理出来ないかと色々と確認した結果、最終的にコレになりました。Addが出来ないのは残念ですが、まぁ。

関連

tsubakit1.hateblo.jp

【Unity】ECSでComponentGroup内のComponentDataを取得する新API、ToComponentDataArrayとCopyFromComponentDataArray

ComponentDataArray(型)が非推奨になり、代わりにToComponentDataArray(...)CopyFromComponentDataArray()が追加されました。

これは今までのComponentDataArrayと異なり、NativeArrayを取得します。

ToComponentDataArrayとCopyFromComponentDataArrayはChunkIterationの面倒な記述をスッキリ書ける

このAPIでは、今までComponentDataArrayで取得していたような、ComponentGroup全体が要求するComponentDataの配列を取得できます。

機能は2つで、ComponentGroup.ToComponentDataArray()でComponentGroupが持つComponentData一覧をNativeArrayにコピーして使用できるようようします。また、ComponentGroup.CopyFromComponentDataArray()で変更を反映します。

public NativeArray<T> ToComponentDataArray<T>(Allocator allocator, out JobHandle jobhandle) where T : struct,IComponentData {...}

public void CopyFromComponentDataArray<T>(NativeArray<T> componentDataArray, out JobHandle jobhandle) where T : struct,IComponentData {...}

内部的にはGatherComponentDataJob<T> : IJobChunkで全Chunkが入るバッファを1回で作り、中身を一気に埋めていく感じです。チャンクがそれ程多くないなら、殆ど一回のMemcopyで終わるので効率的に見えます。ChunkIterationでやるような面倒くさい記述を割とスッキリとしてくれるので、割とありがたいです。

ForEachで面倒くさい(例えば複数のGroupを参照するような)ケースでは、こちらが便利そうです。

ということで、使い方を見ていきます。

まずは普通にComponentDataのNativeArrayを取得→反映までをメインスレッドのみで

普通にComponentGroupの要求を定義します。

        // コンポーネントの準備
        playerGroup = GetComponentGroup(
            ComponentType.ReadOnly<Position>(),
            ComponentType.ReadOnly<Player>(),
            ComponentType.Create<HitPoint>());

次にComponentDataを取得します。

ComponentGroup.ToComponentDataArray<T>(Allocator.TempJob);を使用します。

この時Allocatorを設定するのですが、内部的にコピー作業にジョブを使用しているので、実質TempJobもしくはPersistentの二択です。(もしくはTempJob 1択?)

なお内部的にジョブ(&Burst)を使用していますが、即座にCompleteしているので普通にメインスレッド(ComponentSystem)で使って問題ありません。

    protected override void OnUpdate()
    {
        // チャンクからNativeArrayを取得
        var playerPositions = playerGroup.ToComponentDataArray<Position>(Allocator.TempJob);
        var playerHitpoint = playerGroup.ToComponentDataArray<HitPoint>(Allocator.TempJob);
        var enemyPositions = enemyGroup.ToComponentDataArray<Position>(Allocator.TempJob);

これで取得したComponentDataのArrayを使用して、色々と演算出来ます。NativeArrayなのでジョブに投げる事も出来ます。

ただし演算に使用するデータはあくまでコピーされたデータなので、もし変更していた場合は設定を反映させる必要があります。

演算結果をComponentGroupに書き戻します。

下記戻す時にはComponentGroup.CopyFromComponentDataArray(...)を使用します。

        // ダメージを反映
        playerGroup.CopyFromComponentDataArray(playerHitpoint);

ジョブ対応する

ToComponentDataArrayとCopyFromComponentDataArrayは内部的にはIJobChunkのジョブを使用しています。この時JobHandleを渡してやれば、CompleteのタイミングをoutしてきたJobHandleにまかせてくれます。

少し厄介だったのが、JobHandleを受け渡すのが今までのIJob系と若干異なっていた点です。JobではSchedule時に渡しますが、今回はComponentGroup.AddDependency経由で渡します。

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        // チャンクからNativeArrayを取得
        playerGroup.AddDependency(inputDeps);
        var playerPositions = playerGroup.ToComponentDataArray<Position>(Allocator.TempJob, out JobHandle handle1);
        var playerHitpoint = playerGroup.ToComponentDataArray<HitPoint>(Allocator.TempJob, out JobHandle handle2);
        var enemyPositions = enemyGroup.ToComponentDataArray<Position>(Allocator.TempJob, out JobHandle handle3);
        jobhandle = JobHandle.CombineDependencies(handle1, handle2, handle3);

値の反映もジョブ経由です。こちらもAddDependency経由で渡します。正直なんでやねん感はありますが、何故かそうなっています。

        // ダメージを反映
        playerGroup.AddDependency(jobhandle);
        playerGroup.CopyFromComponentDataArray(playerHitpoint, out JobHandle handle4);

ジョブを使わない方の実装例

gist.github.com

ジョブを使う方の実装例

gist.github.com

f:id:tsubaki_t1:20190222005710j:plain
システム内にWaitForJobGroupIDは無くなった

ハイブリットで使いたい場合

ToComponentDataArrayGetComponentDataArray(旧)と異なりコンポーネント(e.g. Transform)を取得出来ません。

そういうのはGetComponentArrayなりGetGameObjectArrayなり、ForEachでやります。(どうせメインスレッドでしか動きませんし)

感想

またAPIが紛らわしいんだよオラァ!!

低レイヤーに直接アクセスを要求するようなAPIが減ってくれて嬉しい限り

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp