UnityのAddressablesのRemoteコンテンツ運用 - ContentCatalog
こんにちは、エンジニアリングマネージャーの渡辺(@mochi_neko_7)です。
今回は前回の記事の続きとして、UnityのAddressablesのCatalog(Content Catalog)の詳しい紹介をします。
前回の記事はこちら。
RemoteからAssetBundleをダウンロードして使用するだけなら前回記事の内容でも十分なのですが、アプリケーションのリリース後にRemoteでコンテンツの追加をするためにはそれだけでは不十分で、Catalogをきちんと管理してあげる必要があります。
Localのみでアセットを管理している場合はCatalogの存在はほとんど意識する必要はないのですが、Remoteでアセットを管理する場合には非常に重要なものになります。
本記事ではその理由も含めて、Catalogがどういったものなのか、どうやって使用するのか、どのように運用できるのかを体系的に整理して紹介します。
少し深めの内容になるため初心者向けではなくなってしまいますが、Catalogに関して公式のドキュメントも情報が少ないですし、体系的に説明してくれている情報も見当たらないため、しっかり目に紹介したいと思います。
今回も前回に引き続き使用しているバージョンは以下になります。
- Unity 2021.3.0f1
- Addressables 1.19.19
- 公式ドキュメントは 1.20 の方が情報が多いのでリンクは1.20を使っています
Catalogの役割
前回記事で紹介したAddressablesにおけるアセットのロードフローをおさらいしましょう。
IResourceLocator
にAddressを渡して、IResourceLocation
を取得し、それから 指定されている IResourceProvider
を使用してAssetBundleやアセットをロードする形でした。
この IResourceLocator
の役割をし、Addressablesで管理している全てのAssetBundle、アセットの IResourceLocation
を内包しているデータがCatalog(ContentCatalog)です。
つまり、AddressからロードするAssetBundleやアセットを特定し、それらをロードするために必要な情報を提供するのがCatalogの役割になります。
当然ですがCatalogに載っていないAssetBundleやアセットはロードすることができませんので、使用しているCatalogにどのAssetBundleやアセットの情報が載っているのかというのを把握することは重要です。
Catalogの中身
Catalogの大まかな役割を把握したところで、実際にCatalogの中身を覗いてみましょう。
Json
catalog.json
の拡張子からも分かるように、Catalogの出力されるデータはJsonで記述されているので、中身をテキストで見ることができます。
Mac版のUnityで新規プロジェクトを作成して、Addressablesのパッケージを追加し、設定の初期化をしてからURPのLit ShaderだけAddressableに設定した状態で生成されるCatalogはこのような中身になっています。
{ "m_LocatorId": "AddressablesMainContentCatalog", "m_InstanceProviderData": { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" }, "m_Data": "" }, "m_SceneProviderData": { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.SceneProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.SceneProvider" }, "m_Data": "" }, "m_ResourceProviderData": [ { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" }, "m_Data": "" }, { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" }, "m_Data": "" }, { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" }, "m_Data": "" }, { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" }, "m_Data": "" }, { "m_Id": "UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider", "m_ObjectType": { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" }, "m_Data": "" } ], "m_ProviderIds": [ "UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider", "UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider", "UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider", "" ], "m_InternalIds": [ "{UnityEngine.AddressableAssets.Addressables.RuntimePath}/StandaloneOSX/defaultlocalgroup_assets_all_cf7c3574ff06b3d38474c7759827fb82.bundle", "DebugUICanvas", "DebugUIPersistentCanvas", "Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader", "Scenes/SampleScene" ], "m_KeyDataString": "CgAAAABEAAAAZGVmYXVsdGxvY2FsZ3JvdXBfYXNzZXRzX2FsbF9jZjdjMzU3NGZmMDZiM2QzODQ3NGM3NzU5ODI3ZmI4Mi5idW5kbGUADQAAAERlYnVnVUlDYW52YXMAIAAAAGNmNmNiZGQ2NzIwODlhODQ3OTZlNTVhMjFmZWQxY2JlABcAAABEZWJ1Z1VJUGVyc2lzdGVudENhbnZhcwAgAAAAZjZiMWEwZmU3NWQ1MDA5NDQ5Y2Y1NWFlNzYyMjBlMmIAQAAAAFBhY2thZ2VzL2NvbS51bml0eS5yZW5kZXItcGlwZWxpbmVzLnVuaXZlcnNhbC9TaGFkZXJzL0xpdC5zaGFkZXIAIAAAADkzMzUzMmE0ZmNjOWJhZjRmYTA0OTFkZTE0ZDA4ZWQ3AAsAAABTYW1wbGVTY2VuZQAgAAAAOTljOTcyMGFiMzU2YTA2NDJhNzcxYmVhMTM5NjlhMDUEAAAAAA==", "m_BucketDataString": "CgAAAAQAAAABAAAAAAAAAE0AAAACAAAAAQAAAAIAAABfAAAAAgAAAAEAAAACAAAAhAAAAAQAAAADAAAABAAAAAUAAAAGAAAAoAAAAAQAAAADAAAABAAAAAUAAAAGAAAAxQAAAAEAAAAHAAAACgEAAAEAAAAHAAAALwEAAAEAAAAIAAAAPwEAAAEAAAAIAAAAZAEAAAEAAAAIAAAA", "m_EntryDataString": "CQAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAP////8AAAAA/////wEAAAABAAAAAQAAAAEAAAD/////AAAAAP////8BAAAAAgAAAAIAAAABAAAA/////wAAAAD/////AwAAAAEAAAACAAAAAQAAAP////8AAAAA/////wMAAAADAAAAAgAAAAEAAAD/////AAAAAP////8DAAAABAAAAAIAAAABAAAA/////wAAAAD/////AwAAAAUAAAADAAAAAgAAAAAAAACF+Vmw/////wUAAAAGAAAABAAAAAMAAAD/////AAAAAP////8HAAAABwAAAA==", "m_ExtraDataString": "B0xVbml0eS5SZXNvdXJjZU1hbmFnZXIsIFZlcnNpb249MC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsSlVuaXR5RW5naW5lLlJlc291cmNlTWFuYWdlbWVudC5SZXNvdXJjZVByb3ZpZGVycy5Bc3NldEJ1bmRsZVJlcXVlc3RPcHRpb25zrAIAAHsAIgBtAF8ASABhAHMAaAAiADoAIgBjAGYANwBjADMANQA3ADQAZgBmADAANgBiADMAZAAzADgANAA3ADQAYwA3ADcANQA5ADgAMgA3AGYAYgA4ADIAIgAsACIAbQBfAEMAcgBjACIAOgAyADIAMQAxADUAOQAzADAAMAAxACwAIgBtAF8AVABpAG0AZQBvAHUAdAAiADoAMAAsACIAbQBfAEMAaAB1AG4AawBlAGQAVAByAGEAbgBzAGYAZQByACIAOgBmAGEAbABzAGUALAAiAG0AXwBSAGUAZABpAHIAZQBjAHQATABpAG0AaQB0ACIAOgAtADEALAAiAG0AXwBSAGUAdAByAHkAQwBvAHUAbgB0ACIAOgAwACwAIgBtAF8AQgB1AG4AZABsAGUATgBhAG0AZQAiADoAIgA1AGQANgA0ADEANgBlADgAYwAwADkANgA0ADEAZQBkADMANgA1ADkANABmADAAZAA0AGUAZgBlADIAMQBkADEAIgAsACIAbQBfAEEAcwBzAGUAdABMAG8AYQBkAE0AbwBkAGUAIgA6ADAALAAiAG0AXwBCAHUAbgBkAGwAZQBTAGkAegBlACIAOgA2ADMAMgAwADEALAAiAG0AXwBVAHMAZQBDAHIAYwBGAG8AcgBDAGEAYwBoAGUAZABCAHUAbgBkAGwAZQBzACIAOgB0AHIAdQBlACwAIgBtAF8AVQBzAGUAVQBXAFIARgBvAHIATABvAGMAYQBsAEIAdQBuAGQAbABlAHMAIgA6AGYAYQBsAHMAZQAsACIAbQBfAEMAbABlAGEAcgBPAHQAaABlAHIAQwBhAGMAaABlAGQAVgBlAHIAcwBpAG8AbgBzAFcAaABlAG4ATABvAGEAZABlAGQAIgA6AGYAYQBsAHMAZQB9AA==", "m_resourceTypes": [ { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.IAssetBundleResource" }, { "m_AssemblyName": "UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.GameObject" }, { "m_AssemblyName": "Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.Rendering.UI.DebugUIPrefabBundle" }, { "m_AssemblyName": "UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.RectOffset" }, { "m_AssemblyName": "UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.UI.MaskableGraphic+CullStateChangedEvent" }, { "m_AssemblyName": "UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.Events.PersistentCallGroup" }, { "m_AssemblyName": "UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.Shader" }, { "m_AssemblyName": "Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "m_ClassName": "UnityEngine.ResourceManagement.ResourceProviders.SceneInstance" } ], "m_InternalIdPrefixes": [] }
実際のプロジェクトではAddressablesで管理されているアセットの情報がさらに追加されます。
ここでJsonを見て分かる注意点が3つあります。
- InternalIdは直接見ることができる
m_InternalIds
- 設定でInternalIdに何を使うのか制御できるので、どんなアセットを使用しているのか知られたくない場合にはFullPathではなくGUIDを使うのがおすすめです
- ただしSceneはFullPathにしないとSceneのロードができなくなることに注意が必要です
- Base64エンコードされていて直接見ることができないデータもある
m_KeyDataString
、m_BucketDataString
、m_EntryDataString
、m_ExtraDataString
- ちゃんとBase64デコードすれば見ることもできます
- InternalIdに
StandaloneOSX
などのプラットフォーム固有の情報を含んでいる- LocalのAssetBundleのInternalId(Path)
- このためCatalogはAssetBundleと同様にビルドするプラットフォーム別にファイルが必要になることが分かります
インターフェース
Base64エンコードされていることにより具体的な内部の情報が分かりづらいので、IResourceLocator
の定義を確認してみましょう。
namespace UnityEngine.AddressableAssets.ResourceLocators { /// <summary> /// Interface used by the Addressables system to find the locations of a given key. /// </summary> public interface IResourceLocator { /// <summary> /// The id for this locator. /// </summary> string LocatorId { get; } /// <summary> /// The keys defined by this locator. /// </summary> IEnumerable<object> Keys { get; } /// <summary> /// Retrieve the locations from a specified key. /// </summary> /// <param name="key">The key to use.</param> /// <param name="type">The resource type.</param> /// <param name="locations">The resulting set of locations for the key.</param> /// <returns>True if any locations were found with the specified key.</returns> bool Locate(object key, Type type, out IList<IResourceLocation> locations); } }
Keys
はそのCatalogに記載されているAddressなどの一覧です。
そのKeyを Locate(...)
で指定することで、指定したKeyが含まれている場合は IResourceLocatioin
(のリスト)を返してくれます。
次に IResourceLocation
の定義を見てみましょう。
namespace UnityEngine.ResourceManagement.ResourceLocations { /// <summary> /// Contains enough information to load an asset (what/where/how/dependencies) /// </summary> public interface IResourceLocation { /// <summary> /// Internal name used by the provider to load this location /// </summary> /// <value>The identifier.</value> string InternalId { get; } /// <summary> /// Matches the provider used to provide/load this location /// </summary> /// <value>The provider id.</value> string ProviderId { get; } /// <summary> /// Gets the dependencies to other IResourceLocations /// </summary> /// <value>The dependencies.</value> IList<IResourceLocation> Dependencies { get; } /// <summary> /// The hash of this location combined with the specified type. /// </summary> /// <param name="resultType">The type of the result.</param> /// <returns>The combined hash of the location and the type.</returns> int Hash(Type resultType); /// <summary> /// The precomputed hash code of the dependencies. /// </summary> int DependencyHashCode { get; } /// <summary> /// Gets the dependencies to other IResourceLocations /// </summary> /// <value>The dependencies.</value> bool HasDependencies { get; } /// <summary> /// Gets any data object associated with this locations /// </summary> /// <value>The object.</value> object Data { get; } /// <summary> /// Primary address for this location. /// </summary> string PrimaryKey { get; } /// <summary> /// The type of the resource for th location. /// </summary> Type ResourceType { get; } } }
InternalId
はJsonでも見える形で含まれていましたね。
Dependencies
があることから、アセットやAssetBundleの依存関係の情報も持っていることが分かります。
Data
は前回記事でも説明したようにCRCなどの情報を含む AssetBundleRequestOptions
という型にキャストして利用できます。
PrimaryKey
はAddressに相当するものになります。*1
重要な情報のみ解説しましたが、これらの情報がBase64エンコードされてCatalogに含まれていることになっています。
以上がCatalogの中身になります。
CRCに関する補足
CRC(Cyclic Redundancy Check)に関して少し補足説明します。
CRC(Cyclic Redundancy Check)はAssetBundleの内容の差異を検知するためのHash値のような役割をする情報です。
AssetBundleをロードする際にオプションで指定することができ、期待されるCRCの値と実際にロードしたAssetBundleから計算したCRCの値を比較して、そのAssetBundleが期待している内容であるかを判別できます。
これはダウンロードしたデータの破損や古くなったキャッシュの検出などに利用できます。*2
特にRemoteのコンテンツをストレージにキャッシュしたり、その中身を更新したりする際にはCRCを利用することになるため、期待されるCRCの情報がCatalogに載っていることも知っておくべき事項になります。
Catalogのロード方法
Catalogには大きく分けて3つのロード方法があります。
- Local
- Remote
- API(URL)
1. Local
アプリケーション本体をビルドすると、StreamingAssets/aa
のフォルダ内に
settings.json
catalog.json
(圧縮設定している場合はcatalog.bundle
)
の2つのファイルが作成されます。
アプリケーション起動中のAddressablesの初期化時(明示的に初期化した時 or 初めてAddressablesにアクセスする時)に、まず settings.json
をロードします。
中にはLocalのCatalogのPath、次で説明するRemoteのCatalogのPath or URLが書かれていて、それを元に catalog.json
をロードします。
このLocalのCatalogにはGroupの設定で Include in Build = true
になっているものがアプリケーションのビルド時の状態で全て含まれます。
Addressablesの内部的には AddressablesMainContentCatalog
というLocatorIdの IResourceLocator
としてロードされます。
当然ですがこのLocalのCatalogの中身を更新するためにはアプリケーション本体をビルドし直す必要があります。
2. Remote
AddressablesAssetSettings
の Content Update
の項目で Build Remote Catalog
をtrueに設定すると、Addressablesのビルド時にCatalogを出力するようになります。
そのすぐ下の Build & Load Paths
の設定で、出力先のPathとロード先のPathを指定できます。
Load Path
はLocalの settings.json
にも記載されて、Addressablesの初期化時に特別な操作をしなくても自動でロードをしてくれます。*3
ただしPathを固定するのが難しい場合には、次で説明するようにAPIを叩いて手動でロードしてあげる必要があります。
3. API(URL)
AddressablesにはRuntimeで手動でCatalogをロード・アンロードするAPIが用意されています。
PathにはURLを指定しても問題ありません。
現在ロードしているCatalogの一覧も取得できます。
自分でロードしたCatalogをきちんとアンロードするためには IResourceLocator
のインスタンスを保持しておく必要があります。
それは LoadContentCatalogAsync
の戻り値で取得できるのですが、メソッドのパラメータの autoReleaseHandle = true
に指定していると null
になってしまうため、アンロードも細かく管理する場合には autoReleaseHandle = false
を指定するようにしましょう。
Catalogの運用
RemoteからCatalogを指定して使用したい場合には、先ほど紹介した Build Remote Catalog
の設定が必要になります。
それを含めた全般的な話は公式のドキュメントにも大まかな説明はありますので、一読しておくことをお勧めします。
それ以外にCatalogを運用する上で知っておくと良い話も紹介します。
Catalogの更新
アプリケーション外からコンテンツを追加して使用したい場合には、AssetBundleをサーバーなどに配置するだけではなく、Addressablesのビルド時に同時に生成されるCatalogもサーバーなどに配置してあげる必要があります。
そのためAssetBundleを追加したり更新する際にはCatalogも更新しなければなりません。
AssetBundleの中身を更新したい場合にも、CRCのチェックをする場合にはCatalogにはCRCの情報が記載されているため、Catalogも更新してあげる必要があることには注意が必要です。
Catalogの分割
CatalogのAPIの説明からも分かるかもしれませんが、CatalogはLocalのものだけではなく任意に追加して複数のCatalogをロードして使用することができます。
前回の記事でも少し触れましたが、Addressablesでアセットをロードするときには IResourceLocator
の列挙を foreach で回して、初めに取得できた IResourceLocation
を使用してロードするということがソースコードから分かりました。
このため、複数のCatalogを使用してもどれかのCatalogから正常にアセットをロードできる状態であれさえすれば問題ないことが分かります。
この性質を考慮すると、少し凝った形のアセット管理の方法にもAddressablesを適用できます。
そもそものテーマであるRemoteでのアセット管理をする上では、アプリケーションのリリース後にAssetBundleを更新したり追加して使用したいという話でした。
そのようなケースではアプリケーション本体に含まれているLocalのCatalogには更新後のAssetBundleや追加したいAssetBundleの情報は当然持っていません。
そのため、それら更新したいAssetBundle、追加したいAssetBundleの情報を持っているCatalogをRuntimeで外からロードする必要があります。
従って最低でも2種類のCatalogを使用することになります。
- LocalのCatalog
- アプリケーション本体に含まれている、アプリケーションビルド時に生成されるCatalog
- RemoteのCatalog
- アプリケーションの外からロードして使用する、アプリケーションのビルド後に追加して使用したいAssetBundleの情報を含んでいるCatalog
もちろん数の制限もないので、もっとたくさんのRemoteのCatalogを使い分けることもできます。
例として、Remoteから3つのSceneのAssetBundleを追加したい場合には、以下のようなCatalogを作成して運用することも可能です。
- LocalのCatalog
- 追加したいScene以外の情報を持っている
- SceneAのCatalog
- LocalのCatalogの情報に加えて、SceneAのAssetBundleの情報を持っている
- SceneBのCatalog
- LocalのCatalogの情報に加えて、SceneBのAssetBundleの情報を持っている
- SceneCのCatalog
- LocalのCatalogの情報に加えて、SceneCのAssetBundleの情報を持っている
ただしこのようなことをするためにはアセットの運用面で下記などの注意が必要です。
- LocalのCatalogの情報の部分はアプリケーションのリリース時の内容とは変わっている可能性があるため、LocalのAssetBundleのCRCのチェックをすると失敗する場合がある
- SceneA~Cの間に依存関係を持たせることができないので、多重参照しているアセットは複製して各AssetBundleに含める必要がある
- Addressablesのビルド時にGroupの設定で
Include in Build = true
にしているものがCatalogに含まれるため、1つのUnityProjectでSceneを作成している場合にはビルド時に設定を切り替えてあげる必要がある - Catalogのアンロードのタイミングでは使用しているアセットもちゃんとアンロードしてあげる
もちろんScene単位でなくても任意の単位でcatalogを分割して運用することも可能です。
公式のドキュメントを読む感じでは、大規模なプロジェクトで大量のアセットを管理する場合を想定してこのような運用ができるようにしているようです。(どのページに書いてあったか忘れてしまったのですが...)
UnityProjectの分割
やや強引な言い方をするなら、AddressablesはCatalogから正しくアセットが読み込める状態を作ってあげればどうにかなるものです。
Catalogが分割できるので、UnityProjectを分割する運用、例えばアプリケーション本体を開発するUnityProjectと、Remoteから追加したいアセットを作成するUnityProjectを分けて運用することも可能です。
もちろん注意事項はあります。
- C#のScriptをComponentとして使用したい場合には、アセット作成側のUnityProjectにも同じScriptを含んでおく必要がある
- そもそもScriptが絡むと運用が大変なのでおすすめはしないのですが...
- 厳密に言うとAssembly名、NameSpaceを含むClass名、SerilaizedObjectの構造が一致していればソースコードに細かい差分があっても問題ないです
- UPMのPackageとして共有できるのが理想です
- UnityProjectを跨いで共有したいアセットがある場合には、それぞれのProjectで全く同じ設定のGroupを用意する必要がある
- 設定内容にもよるかもしれませんが、GroupのGUIDも強制的に揃えておく必要がある場合もあります
- 特に
Universal Render Pipeline/Lit
など標準的なShaderなどは使いまわすケースが多いので気を付ける必要があります
- 共有するGroupの設定の
Internal Bundle Id Mode
やAddressablesAssetSettingsのShader Bundle Naming Prefix
などでProjectIdを使用することはできません- ProjectIdはおそらくUnityProject固有のものなので、これを使用してしまうと差分が発生してしまいます
一応公式のドキュメントにも少し説明があります。
終わりに
以上、Catalogに関する詳しい紹介をしました。
- Catalogはどういった役割をするものなのか
- Catalogが実際に持っている情報は何なのか
- Catalogはどこからロードして使用するのか
- Catalogがどのように運用できるか
こちらの情報を参考にしつつ、実際にご自身で色々触ってみるとより理解が深まると思います。
公式のドキュメントにもあまり体系的な説明がないため、Catalogの実態が掴みづらいかと思いますが、分かってしまえばそれほど複雑なものでもありませんし、意外と緩い部分もあって融通が効く部分もあります。
実際のプロジェクトでは実現したい運用に合わせて、開発体制を整えたり、細かい機能を調整したり、CIを組んだり、デリバリーの体制を整えたりと細かい調整をしていくことになるかと思います。
Remoteでのアセット管理はニーズがある一方で導入ハードルがやや高いと思いますが、前回と今回の記事の内容で基本的な内容は抑えられているかと思いますので、チャレンジしたい方に参考になれば幸いです。
記述内容には気を配ってはいますが、もし本記事の内容に間違っている記述や不適切な記述がある場合にはコメントやTwitterDMなどでお知らせいただけると幸いです。
今後は社内の開発のために作成したデバッグに便利なCatalogBrowser的なEditor拡張や、細かい運用面で苦労した話のTips集なども公開できればと思いますが、余力があればということで。