テラシュールブログ

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

【Unity】Addressableのローカルサーバーを使って、AssetBundleをダウンロードする動作を確認する

今回はAddressableのホスティングサービスを試します。

本記事は Addressable Asset System ver 0.6.7 preview を使用しました。

動作確認にサーバーを用意するのが面倒くさい

Addressable(Addressable Asset System)は概ね外部リソースと内部リソースの区分無く使えるシステムです。またAssetBundleを構築せず動作を確認出来るという点で非常に手軽に変更を確認出来ます。

とはいえ、AssetBundleを実際にビルドしないと何らかの不具合を確認出来ない事もあります。また、外部リソースにはダウンロードという手順が必ず存在するため、そういった部分の確認にも出来ればアセットを配置するサーバーを立てて実際に確認したい所です。

なので、ローカル環境にサーバーを立てて動作を確認するホスティングサービスの機能を使ってみます。ファイルはサーバー上に配置し、Addressable経由でファイルをダウンロード、使用するという感じにする訳です。

手順1:動作確認

Addressableの最低限のセットアップ(パッケージのインポート及び初期アセットの登録的なもの)はスキップします。

強いて言うならグループの出力先はリモートに設定しておきます。

f:id:tsubaki_t1:20190313114024j:plain

さて、最初にやるべきは動作確認です。Packは最も厳密な動作なので事前に Virtual Mode で動作を確認し、スクリプト的にちゃんと動作することを確認します。今回はキャラクターを1体表示するだけの機能を試してみました。実行すると、ユニティちゃんのPrefabが表示されます。

これで正常に動作するならばスクリプト的には概ね問題はなく、問題があれば設定やAddressable側の問題になります。

f:id:tsubaki_t1:20190312231850j:plain
Virtual Modeで動作を確認

f:id:tsubaki_t1:20190312232338g:plain
Virtual Modeで正常に動作するなら、スクリプト的には概ね問題はない

手順2:ホスティングサービスの起動

ホスティングサービスを起動します。

  1. Hostingボタンを押す
  2. Add Serviceボタンを押した後、 Addボタンを押してサービスを登録
  3. Enable Serviceボタンを押してサービスを起動

これでファイルサービスが起動します。

f:id:tsubaki_t1:20190312232805j:plain

f:id:tsubaki_t1:20190312233010j:plain

f:id:tsubaki_t1:20190312233019j:plain

手順3:AddressableのRemote接続先を設定

Addressableの接続先を変更します。つまりホスティングサービスに接続するようにします。

  1. Inspector Prifile Settingsを開く
  2. Profile To Editの内容を変更する
    1. + ボタンを押し、プロファイル名を変更
    2. RemoteLoadPathを変更。例えば http://[PrivateIpAddress_3]:[HostingServicePort]のような感じに
  3. GeneralRebuild Remote Catalogにチェックを入れ、Remote Catalog Build PathRemote Catalog Load PathRemote Build PathRemote Load Pathへ変更
  4. GeneralPlayer Version Overrideに適当な値を設定。例えば 00001 等。 この設定をするとCatalog名が固定されます

これで接続先の設定が出来ました。

f:id:tsubaki_t1:20190312233842j:plain

f:id:tsubaki_t1:20190312233709g:plain
RemoteLoadPath接続先を設定

f:id:tsubaki_t1:20190312234638j:plain

初期設定では[BuildTarget]が入ってるしServerDataフォルダにはプラットフォームのフォルダがあるので必要かと思いますが、実際には不要です。

手順4:Addressableの接続先変更~ビルドまで

Addressableの接続先を変更して、ソコ向けにビルドします。

  1. Profile : Defaultを、先程作成したプロファイルに変更する(自分の場合はMy Profile
  2. Play Mode ScriptPacked Play Modeに変更
  3. Build>Build Player Contentを実行

プロファイルは設定を書き換える度にビルドする必要があります。

f:id:tsubaki_t1:20190312235201g:plain
プロファイルの切り替えからビルドまで

手順5:実際に動かしてみる

実際に動かしてみます。これでちゃんと動作するなら問題無しです。

もし404が出たら、手順2に戻ってホスティングサービスをEnable Serviceの隣にある Removeボタンで一旦削除し、やり直すと治ることがあります。

f:id:tsubaki_t1:20190312235619j:plain

ビルドしてみる

あとは実際にビルドしてみます。ホスティングで指定したURLによっては普通にアクセス出来ます。

またCatalog名を固定して生成しているので、Unityエディター側でアクセスするAssetBndleの中身を差し替えるとリソースを差し替えられます。

https://user-images.githubusercontent.com/1644563/54213423-b8530380-4527-11e9-9d0c-f3466a4288eb.gif

感想

本格的になると外部サービスになるかもですが、動作確認ならコレでもまぁ十分かなと。

関連

マニュアル docs.unity3d.com

外部のサーバーに配置する場合 section31.jp

Addressableの概要 qiita.com

本当は今回でAddressable 1.0になハズだったんですけどね

Unity - Release Announcements and Notes - Unity Forum

【Unity】ECSで複数のComponentGroupを使う場合

複数のComponentGroupを使う

ComponentGroupは、Systemが処理するEntity(及びComponent)を取得する為に使用します。データ処理の起点とも言って良いです。

このComponentGroupですが、一つのSystem内に複数持つことが出来ます。例えばキャラクターとアイテムといった 異なる特性のEntity同士をかけ合わせて計算したい場合 など、色々な要因で複数のComponentGroupを使いたくなります。

全く異なるComponentGroupを複数定義する場合はRequireForUpdateを使う

Systemは有り難い事に、ComponentGroupが要求するEntityが一つも無い場合は、動作しないようになっています。完全にシステムを停止する訳ではないのでコストが完全に消滅する訳ではないですが、ほぼ負荷は無くなります。 これは外部からオブジェクトの状態を観測してSystemをON/OFFするよりは理に適っているかなと個人的には思います。

f:id:tsubaki_t1:20190310214225j:plain

問題は、ComponentGroupを複数要求していた場合 どれか一つでも条件を達成してしまっていれば動いてしまう という点です。

例えば「プレイヤーの位置(Group1)」を「敵(Group2)」が追跡するようなプログラムの場合、「プレイヤー(Group1)」が消滅してもプレイヤーを追跡するプログラムは停止しません。

f:id:tsubaki_t1:20190310214122j:plain

こういった場合には、RequireForUpdate( ComponentGroup )RequireSingletonForUpdate<T>()を使用します。このAPIOnCreateManager()等で呼んでおけば、該当のComponentGroupが全て揃わなければSystemは動作しません

  • 単純に複数のComponentGroupを定義した場合:OR
  • RequireForUpdateを使用した場合:AND

と思っておけば良さそうです。まぁANDとORを組み合わせた複雑な条件分岐は出来ないんですが。

f:id:tsubaki_t1:20190310215932j:plain

gist.github.com

設定違いという点で複数のComponentGroupを使用したい場合は、ChunkIterationを考える

用途は同じだがパラメーターが違う的な意味で複数のComponentGroupが必要という場合もあります。

例えばColliderの形状で、BoxもあればSphereもあります。この2つの役割は本質的には同じですが、保有するパラメーターは全く別物になるため、異なるComponentDataが適応されます。

こういったケースでは以前は複数のComponentGroupを定義していましたが、 現状だとChunkIterationを使用することが推奨される と思います。

tsubakit1.hateblo.jp

もちろん、ComponentData内に複数のケースでのCollider設定を突っ込むことは可能でしょうが、メモリレイアウト的に無駄が多いのと、同じ処理を繰り返せないという点からECS向きではありません。

感想

IJobProcessComponentDataなど、ジョブの発行それ自体に地味なコストがある場合は、RequireForUpdateで不要なればSystemが動作しないように停止しておきたい所です。まぁOnUpdateの最初でComponentGroupの長さを測って即returnすれば同じようなものでしょうが。

ところでChunkIterationがある今、複数のComponentGroupでORになるって、使うケースあるんですかね

【Unity】GameObjectとコンポーネントをEntityとComponentDataに変換してECSで使えるようにする

SubSceneで独自のコンポーネントや、まだ変換先の無いコンポーネントをComponentDataに差し替えるアプローチについてです。

GameObjectベースのデータをECSベースへ変換する

ECS 0.0.12 preview 26からGameObjectをEntityに変換して使用するといったワークフローが充実してきました。今まではGameObjectEntityなど、どちらかと言えば ComponentをEntityに登録するアプローチ、言うなれば GameObjectワークフローをECSで使用する為のアプローチ*1 でした。

そしてpreview 26において*2 GameObjectをECS用に変換するアプローチが登場します。 特に分かりやすい機能の使い方としてはSubSceneで、既存のシーンをECS用のデータに変換するというものです。

https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20190306/20190306232854.jpg

GameObjectをEntityへ変換するAPI

GameObjectをECSへ変換するためのAPIは幾つかあります。

まずGameObjectConversionUtility.ConvertGameObjectHierarchy(...)です。これは指定のGameObjectをEntityへ変換します。基本的に個別の制御になっています。 なお変換後はRenderBounds等が無い為に表示はされませんが、ECSとしては生成されている状態です。複数回呼ぶ場合はキャッシュするなりするのが良さそうに見えます。

Entity entity = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab, World.Active);
World.Active.EntityManager.Instantiate(entity);

このAPIコンポーネント版が、ConvertToEntityです。登録したオブジェクトを自動的にEntityへ変換します。

これはGameObjectEntityと異なりTransform等の変換可能なコンポーネントは全て変換されます。またECSの計算結果をGameObjectへ戻す機能は提供されていません。

f:id:tsubaki_t1:20190309150306j:plain
ConvertToEntity

それとGameObjectConversionUtility.ConvertSceneがありますが、ランタイムで呼ぶようなものではないのでSubSceneワークフローで変換したものを使うのが良さそうです。

MonoBehaviourをComponentDataに変換してもらうためのコード

MonoBehaviourを自分で記述しているならば、IConvertGameObjectToEntityを利用してEntityを変換可能にします。

実装方法はこんな感じです。ComponentDataProxyと異なり、一つのコンポーネントで複数のComponentDataを追加出来る点がポイントかもしれません。

public class MyDataProxy : MonoBehaviour, IConvertGameObjectToEntity
{
    [SerializeField] int m_Value;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        // コンポーネントを追加
        dstManager.AddComponentData(entity, new PlayerTag());
        dstManager.AddComponentData(entity, new MyData { Value = m_Value });
    }
}

[System.Serializable]
public struct MyData : IComponentData { public int Value; }

public struct PlayerTag : IComponentData { }

このアプローチでは、他のEntityを参照するEntityも作れます。

public class Follow : MonoBehaviour, IConvertGameObjectToEntity
{
    [SerializeField] GameObject obj;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var target = conversionSystem.GetPrimaryEntity(obj); // GetPrimaryEntityで対象のEntityを取得
        dstManager.AddComponentData(entity, new Target { Value = target });
    }
}

public struct Target : IComponentData
{
    public Entity Value;
}

コンポーネントをComponentDataに変換してもらうためのコード

Hierarchyを汚さず、かつコンポーネントのカスタムが出来ない場合はGameObjectConversionSystemを使います。

例えば下のはBoxColliderを自分自身の定義したMyBoxColliderへ変換するコードです。IConvertGameObjectToEntityと異なり「コンポーネントの組み合わせで差し替える対象を変更出来る」「既存のコンポーネントを差し替えられる」「実行順番を制御できる」という点で、有利です。

public class ColliderConversionSystem : GameObjectConversionSystem
{
    protected override void OnUpdate()
    {
            Entities.ForEach((BoxCollider collider) => {
            var entity = GetPrimaryEntity(collider);
            var myBoxCollider = new MyBoxCollider
            {
                Size = collider.size,
                IsTrigger = collider.isTrigger ? 1 : 0
            };

            DstEntityManager.AddComponentData(entity, myBoxCollider);
        });
    }
}

感想

ある意味、実装と設定が明確に別れたといった感じでしょうか。

少し面倒ですが、大分納得できる感じになりました。

現在マニュアルで紹介されているサンプルは全てこのアプローチで構成されている辺り、主流になって欲しいアプローチなのかもしれません。

関連

Entityを生成するサンプル

https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/Samples/Assets/HelloECS/HelloCube_05_SpawnFromMonoBehaviourgithub.com

大規模な場所でConvertToEntityをする場合はSubSceneを使用するのをオススメ

tsubakit1.hateblo.jp

*1:ハイブリットECS

*2:実はもう少し前からあったが

【Unity】Scene上に構築したステージを、Entity群に変換してECSで利用可能にする「SubScene」

f:id:tsubaki_t1:20190307014827j:plain

ECS 0.0.12 preview 26から、GameObjectとComponentをEntity&ComponentDataに変換する機能が追加されました。まずは、この中で最も便利であろうSubSceneについて紹介します。

GameObjectをECS向けに変換する

preview26でGameObjectConversionUtility.ConvertGameObjectHierarchy(...)GameObjectConversionUtility.ConvertScene(...)等のAPIが追加されました。このAPIは特定のコンポーネントを対応するComponentDataに変換するといった機能を持ています。例えば下のような感じ。

  • TransformをPositionRotationに変換する
  • MeshRendererとMeshFilterをRenderMeshに変換する

f:id:tsubaki_t1:20190306232854j:plain
GameObjectをEntityへ変換

これはPrefabからEntityや、シーンに配置したGameObjectからEntity、SceneまるごとEntityといったレベルでの変換が可能です。

当然、変換なので変換先のComponentDataが無い物は変換出来ない為に完全簡単に移行という訳には行きませんし、現状、ライトマップ等のSceneに紐づく情報は剥がれます。が、Unityエディターのワークフローでシーンにオブジェクトを配置し、ゲームで利用する事が可能になるのでECSを利用する上では、使いやすくなりそうです。

f:id:tsubaki_t1:20190306233151j:plain
StealthデモのステージをEntity群に変換して表示

ということで実際にScene上のステージをEntity群に変換して利用して見るわけですが、そこで既に便利な機能「SubScene」があったので、そちらを使います。

SubScene

SubSceneの説明によると、機能は大体こんな感じみたいです。Unityがシーン展開に使用しているようなデシリアライズとは異なり、ECSのメモリ構造を概ねそのまま展開して配置出来るので楽という発想でしょう。

シーンを保存する時、UnityはSubSceneをネイティブのバイナリフォーマットに変換します。このフォーマットはメモリに対応していて、RAM内のデータを少し変更するだけでロードもしくはストリーミングが可能です。このフォーマットは大量のEntityをストリーミングするのに理想的です。

いくつかのロードが超早いと評判のゲームは、このアプローチを使っていると聞いていたと聞いているので、とても良さそうです。

実際に使ってみる

【動作環境】

  • Unity 2019.1 b5(b4以前はSubSceneの機能が正常に動作しないかもしれません)
  • Entity 0.0.12 preview
  • Hybrid Renderer
  • Windows 10

1. 選択したオブジェクトのサブシーン化

シーンにあるオブジェクトをサブシーンにします。

オブジェクトを選択して 右クリック→New SubScene from Selection をクリックすると、指定した範囲内のオブジェクトをサブシーンにします。Stageのようなオブジェクトが既に存在し、Colliderとは別に配置されている場合には、こちらのほうがやりやすいかもしれません。

よくあるGameObjectをサブフォルダのように使うが如く、シーンを階層化出来ます。SubSceneの下にSubSceneを配置するなども可能です。

https://user-images.githubusercontent.com/1644563/53929490-3a2ed100-40d1-11e9-83c5-f07ff6d06a9d.gif

シーンファイルは、親シーンと同名のフォルダ以下に配置されるみたいです。

なお、基本的に動かさないオブジェクトにはStatic Optimize Entityを追加します。これを追加してあると、描画時に軽くなります(staticフラグが付いてるなら自動で設定してくれても良いのに)

f:id:tsubaki_t1:20190307125536j:plain
動かさないならStatic Optimize Entityを追加

2. SubSceneの編集

SubSceneに持っていっても、編集は可能です。

Editボタンを押せば、Entity & ComponentDataの代わりにGameObject & Componentが表示され、編集することが出来ます。この状態になると、普通のシーンと同様の操作系になります。

https://user-images.githubusercontent.com/1644563/53894988-65cf9e00-4074-11e9-9352-e3c3744e807b.gif

3. SubSceneをEntity群に変換

Entity群に変換します。

変換するには、SubSceneのRebuild Entity Cacheボタンを押すだけです。

既存のシーンの場合は、SceneAssetに追加すれば変換が可能です。

f:id:tsubaki_t1:20190307000032g:plain
SubSceneの追加

この手順が完了すると、Assets/EntityCache/ResourcesフォルダとStreaming Assets/EntityCacheフォルダにEntityをシリアライズしたファイルが生成されます。

PrefabはSharedComponentDataをシリアライズした情報、あとはSceneHeader(シーン情報とシリアライズしたバイナリを紐付ける)といった感じでしょうか。

また変換後でもEditボタンで編集が可能です。

f:id:tsubaki_t1:20190307001209j:plain
キャッシュをビルドすると出来る成果物

これでゲームを実行すると、Sceneから変換したEntity群がロードされます。

f:id:tsubaki_t1:20190307003219p:plain
実行結果

SubSceneに変換出来ない場合

変換出来ない場合、同一のコンポーネントが複数セットされている事が考えられます。

というのも、ECSは仕組み上「一つのEntityに同じComponentを複数登録できない」という制限があります。なので、Colliderなど一つのオブジェクトに複数のコンポーネントを登録しているものを排除します。

また変換出来ないEntityの情報は尽く剥がされます。これはスクリプトで拡張が出来ますが(Renderer等もHybrid Rendererパッケージで拡張されて変換可能になっている)無いものは変換出来ません。

ArgumentException: It is not allowed to have two components of the same type on the same entity. (BoxCollider and BoxCollider)と出ます。

f:id:tsubaki_t1:20190307125435j:plain
複数のBoxColliderがある場合、変換出来ない

既存のシーンを変換する

上でもチラっと書きましたが、New SubScene from Selectionを使用してSceneを作らない場合でも、SubSceneのSceneAssetにSceneを適応すればSubSceneとして扱えます(変換も可能です)

ただし、その場合は一旦シーンを開き直さないとSubSceneをSceneと認識しませんでした。

f:id:tsubaki_t1:20190307000358j:plain
SubSceneに既存のSceneを使う

その他機能

また、キャラクターのコンポーネント情報がどうなっているのか等を確認したい場合、LivelinkでEntityの情報をGameObjectに反映させるという事ができそうな感じがあります。

f:id:tsubaki_t1:20190307005052j:plain
Livelink

MeshRendererやLODは使えないの?

MeshRendererやLOD以外でも、独自スクリプトでも差し替えは可能そうです。詳しくはこちら

tsubakit1.hateblo.jp

感想

まだ機能が限られていますが、現状でも「ステージの大雑把な情報やキャラクターはGameObject & Component」「細かく大量にあるステージ上の小物はEntityで制御」のような使い分けができそうです。

またSubSceneで一つのシーンを複数で分割し、同時編集するといった事も今までのマルチシーン構想よりスムーズにできそうです。特にGameObjectをフォルダのように利用して階層分けしていた場合、より高速でスムーズなアプローチとして期待出来るかもしれません。

ところでSubSceneは普通に現行のGameObjectベースのシーンシステムに欲しいんですが…

関連

データをロードする部分は大体コレ

tsubakit1.hateblo.jp

SubSceneのサンプル

https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/Samples/Assets/HelloECS/HelloCube_04_SubScenegithub.com