Successfully reported this slideshow.
Your SlideShare is downloading. ×

Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019]

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad

Check these out next

1 of 85 Ad

Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019]

Download to read offline

DeNAではUnityを使って多くのゲームを開発していますが、Unityはいま、かつてない規模の大きな変革の時を迎えています。この大きなうねりのなかで、何が変わろうとしているかを知り、そして変化によって生まれる価値を正しく認識して開発に取り入れることができれば、Unityでのゲーム開発の効率を大きく改善させていくことができます。本セッションでは、Unityに今どのような改善がなされているのかについて話した上で、DeNAがその領域に対して今までどのように対処してきて、Unityの改善を受けてそれをどのように生かしてゲーム開発の現場に取り入れようとしているのかについてピックアップして説明していきます。

DeNAではUnityを使って多くのゲームを開発していますが、Unityはいま、かつてない規模の大きな変革の時を迎えています。この大きなうねりのなかで、何が変わろうとしているかを知り、そして変化によって生まれる価値を正しく認識して開発に取り入れることができれば、Unityでのゲーム開発の効率を大きく改善させていくことができます。本セッションでは、Unityに今どのような改善がなされているのかについて話した上で、DeNAがその領域に対して今までどのように対処してきて、Unityの改善を受けてそれをどのように生かしてゲーム開発の現場に取り入れようとしているのかについてピックアップして説明していきます。

Advertisement
Advertisement

More Related Content

Slideshows for you (20)

Similar to Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019] (20)

Advertisement

More from DeNA (20)

Recently uploaded (20)

Advertisement

Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019]

  1. 1. #denatechcon #denatechcon Unity2018-2019を見据えた DeNAのUnity開発のこれから Haruto Otake Group Manager Gr 5, Game Development Infrastructure Dept. Game Div, Game & Entertainment Business Unit.
  2. 2. #denatechcon 自己紹介 • 大竹 悠人(Haruto Otake) • 開発基盤部 第五グループ 所属 • 来歴 • 2009/4 ドワンゴに新卒入社 • 幾つかのサービスや、家電/ゲーム機向けのプロダクトを担当 • 2013/5 DeNAに中途入社 • Webソーシャルゲームタイトルの運用ののち、新規ネイティブゲーム開発へ参加 • 基盤整備を得意としていたら、いつのまにかそちらが本業に • 現在はUnityに関連した技術サポートと様々な内製ライブラリの制作に従事
  3. 3. #denatechcon アジェンダ • Unityに今起きている事、我々がすべき事 • Unityの変化 • 我々がいますべきこと • 具体的な活用例 • パッケージ管理 • 最速のアセットビルド環境の実現 • さいごに
  4. 4. #denatechcon Unityに今起きている事、 我々がすべき事
  5. 5. #denatechcon Unity、最近とてもいい感じじゃないですか? • 明らかに機能の提供速度が上がっている • 古くからの望まれていた課題が数多く解決に向かっている • Nested Prefab, 2D Animation, Timeline, Multiplayer, Rendering, Scripting... • リリースされたものがいい感じに使える • これだよ! これ! 欲しかった物! という感覚 • なぜこう感じるのかを考えてみる
  6. 6. #denatechcon Unityの機能提供手法の変化 • Unity Package Manager (UPM) • Unity 2018から実装された、npmベースのパッケージ管理システム • manifest.jsonの記述に従って、EditorがUnityのnpmレジストリから パッケージをダウンロードして、プロジェクトに取り込む • UPMパッケージの内容はプロジェクトディレクトリと分離されて管理される • 簡単, 安全にパッケージの導入/削除/更新が可能になった • バージョン毎に動作保証する Unityバージョンが決まっている
  7. 7. #denatechcon これらの変化によって起きたこと • Unity Editorの開発 != Unityの開発 • UPMのそれぞれのモジュールはUnity Editorの更新とほぼ独立して開 発が進む • Unity Editorが更新されなくても、UPMのモジュールの中で完結する バグフィックスや機能追加はUPMでモジュールを更新することで恩恵 を受けることができるようになった • Unityの従来の機能のうちの多くがUPMのモジュールに分割された
  8. 8. #denatechcon Unityの実装の方向性の変化 • 従来 • モノリシック • 出来ることだけが出来る、ある程度の需要はそのままで実現できる • 需要が出来ることを超えると拡張しづらい • クローズド • 実装の内部がかなり閉じられていて、不具合を見つけてもヘルプデスク 経由で報告して修正されることを祈る & ワークアラウンドを積み重ねる しかない ナイーブな対応が要求される
  9. 9. #denatechcon Unityの実装の方向性の変化 • 最近 • カスタマイズ性が重視されるようになり、モジュラリティが向上 • 拡張性のあるインターフェースが提供 • 複数のパッケージで多層的に機能を提供するような公式パッケージが増加 • モダン化/高速化 • C#のモダンな機能を使って実装されるようになり、APIの使い勝手が向上 • 高速な実装や、流儀に乗るだけで高速なコードを書ける枠組み • オープン化 • 多くのパッケージがGithub上で公開され、開発にユーザーも参加可能 • Unity本体のC#ソースコードがUnityCsReferenceで公開
  10. 10. #denatechcon 車輪の再発明とミッシングリンクの補完 • 今までに提供していた機能ドメイン • 単なる”機能の強化”ではない改善が増えた • 捉え方を変えて車輪の再発明したり、オーバーホールして提供 • Nested Prefab, SBP, SRPなど… • 今までに提供してこなかった機能ドメイン • 有力なAssetStore上のアセットを買収した上でその機能ドメインを取 り入れるという流れも • ProBuilder, Anima2D
  11. 11. #denatechcon 我々がすべきこと
  12. 12. #denatechcon Unityだから…を言い訳にする時代の終わり • 複雑な要求も正攻法で解決ができることが増加 • 回りくどい解決は回りくどい新たな問題を生む • 正攻法で効率よく問題を解決できたほうが、ゲーム開発そのものに集 中できる
  13. 13. #denatechcon 我々がすべきこと • 切り開けるようになった可能性を知る • 単純に提供された機能だけでなく、何をカスタマイズできるように なったかを知る • UnityのアップデートだけでなくUPMパッケージの動向も気にする • 可能性を検証し具体的な価値に転換させる • 出来ることが増えても、活かさなければ宝の持ち腐れ
  14. 14. #denatechcon 具体的な活用例
  15. 15. #denatechcon 具体的な活用例 • パッケージ管理 • 最速のアセットビルド環境の実現
  16. 16. #denatechcon パッケージ管理
  17. 17. #denatechcon Unityの今までのパッケージ管理 • .unitypackage • ファイルとメタ情報をセットにして1つのファイルにアーカイブし、それを 別のプロジェクトに取り込める機能 • GUIDをベースにして管理される • Pros • 手軽に作成できる • Cons • そもそもパッケージ管理システムではない • アセットの更新がある場合、古いアセットが残ってしまう
  18. 18. #denatechcon DeNAの今までのパッケージ管理 • unitypackageはパッケージ管理とは呼べない • 繰り返しになるが、そもそもそういう機能ではない • パッケージ管理無しは耐えられない • 正しくパッケージを更新できない • 内製SDKの更新時に古いファイルが残ったためにエラー発生 • 依存性管理ができない • 既存の内製ライブラリ群がこれを理由に一つのリポジトリで管理 • なにか一つ更新するたびに、全体が更新されてしまう…
  19. 19. #denatechcon なので
  20. 20. #denatechcon DeNAの今までのパッケージ管理 • Upt (Unity advanced-Packaging Tool) • パッケージ管理システムを内製 • 自治的な運用が可能であること • 必要に応じてタイトルが自由に派生版やパッチを作り、導入できる • 無くてもなんとかなる • Uptを使わなくてもモジュールの導入自体は可能 • パッケージ管理システム自体の実装に時間を注がない • 複雑で豪華なものを作ってもリスクが上がる • 必要最低限のものをクイックに提供する
  21. 21. #denatechcon DeNAの今までのパッケージ管理 – 構成 • .unitypackage生成時にmanifestファイルを同時に生成 • GUIDをmanifestに含んでおく • 削除/更新時にこのGUIDを元に、プロジェクト内の旧アセットを一括削除すること で、残すことがないようにする • unitypackageをインポートすればモジュールは導入できる .unitypackage manifest.xml unitypackageの 相対パス 含まれるアセット GUID一覧 パッケージ名 バージョン 依存パッケージの リポジトリ,タグ
  22. 22. #denatechcon DeNAの今までのパッケージ管理 – 配布 • 中央サーバを置かない • Github上のブランチをインポート時に指定する形式 • メジャーバージョンごとに開発ブランチを切ってモジュールを開発 • develop/v1, develop/v2など • 同じメジャーバージョンのブランチを常にimportすると最新の安全な変 更を取得できる • manifestファイルに依存ライブラリが提供されているリポジトリを指 定する形で依存関係を設定する
  23. 23. #denatechcon DeNAの今までのパッケージ管理 • Pros • タイトル側で自由にforkして、1次対応やPull Requestを送れる • GUIDで管理するので、安全なアップデートが可能 • 依存関係管理ができる • Cons • パッケージレジストリが存在しないため、パッケージを一覧できない • DeNA独自である • 利用される機会が限定されるのもあって、若干不安定 • 既存のエコシステムに乗ることができない。 • 独自であるが故に、利用側も概念を一定学ばなければならない
  24. 24. #denatechcon UptからUPMへ • Unity Package Manager(UPM)が登場 • Uptを運用しなくてもいいのなら、その方向に寄せる • インハウスでのパッケージ管理で必要なことを洗い直して、UPMへの 移行を検討
  25. 25. #denatechcon インハウスでのパッケージ管理に必要なこと • パッケージ管理システムとしての基本機能が動く • パッケージを安全に導入/削除/更新できる • パッケージ間の依存関係を設定できて、導入時に自動的に解決される • 独自パッケージを登録可能 • インハウスで使う以上は独自パッケージを登録できないと意味がない • パッケージのvendoringが可能 • 中央サーバがシャットダウンしていても開発に影響がない • 手元にある開発中のパッケージの動作確認が高速にできる
  26. 26. #denatechcon パッケージ管理システムとしての基本機能が動く • 導入/削除/更新を安定して行えること • UPMとそのベースであるnpmが安定すればそれでよい • 現状若干UPMは不安定なところもあるが… • エコシステムは独自であればあるほど弱い • エコシステムの中の豊富なツールチェインを活用できるようになる • UPMというUnity標準 • npmというjavascript標準 • 例えば… • “npm version”でのセマンティックなバージョン更新 • “npm-check-updates”での更新済みライブラリの検知と依存の更新
  27. 27. #denatechcon UPMでの独自パッケージ運用方法 • パッケージレジストリサーバーを立てる • ローカルパッケージ運用
  28. 28. #denatechcon 独自UPMパッケージレジストリの構築 • Verdaccio • npmレジストリサーバーのnode.js実装 • UPMはnpmベースなので、UPMレジストリサーバーとして運用可能 • UPMレジストリとして使う上での注意 • UPMのEditor UIから未インストールの独自パッケージは閲覧できない • インポートしたあとはリストに表示される • Unity管理のモジュールについては、uplink機能でUnity公式のUPMレジストリに リバースプロキシ接続させる必要がある • UPMがPort80以外のポートに対応していないのでPort80でListenする
  29. 29. #denatechcon 独自UPMパッケージを作成する • npmのようなpackage.jsonをルートに配置 • いくつか独自の属性がある • https://docs.unity3d.com/Packages/com.unity.package-manager- ui@1.8/manual/index.html#advanced-package-topics • ルート以下のファイルがインポートされる • スクリプトは何らかのAssembly Definition Filesに属す必要
  30. 30. #denatechcon Assembly Definition Files(asmdef) • Scriptのコンパイル単位を分割する機能 • .asmdefファイルを置いたディレクトリ以下のスクリプトを1つの単位 にしてコンパイルする • Asmdefからは、他のasmdefを参照関係に設定可能 • 参照関係を設定した場合、他のasmdef内のコードを参照可能 • asmdefの依存関係は、UPMの依存とは別 • Asmdefの依存と別に、UPM上の依存関係も組む必要がある • 1つのUPMパッケージの中に複数のasmdefを含める事が可能
  31. 31. #denatechcon Asmdefのクラスプラットフォーム構成 • マクロ使えない問題 • asmdef内ではUNITY_IOSなどのプラッ トフォーム識別マクロが働かない • asmdefのインスペクタで代用 • 適用プラットフォームを選択可能 • 除外された場合、依存していたとして も無視される • プラットフォームごとに別名をつけた 上で全てを参照
  32. 32. #denatechcon 独自UPMパッケージレジストリの利用 dependenciesの中に、利用するパッケージのパッケージ名を キー名に、その値にバージョン名にして要素を追加する registryキーとして UPMパッケージレジストリのURLを記述する Packages/manifest.jsonを編集
  33. 33. #denatechcon パッケージのVendoring • ローカル参照によるVendoring • file:// スキーマでパッケージを展開したディレクトリへの相対パスをバー ジョン名に記述して、特定のローカルパッケージをプロジェクト導入可能 • 依存しているものも自動でVendoringされるわけではない • パッケージの取得 • UPMはnpmベースなので、npm用のツールチェインを活用できる • yarnを使用 • yarn addで依存しているパッケージも含めて取得可能 • パッケージの展開先を指定可能
  34. 34. #denatechcon DeNAでのUPMパッケージの開発時の構成 • 動作確認用Unityプロジェクト + パッケージ • Packages以下に開発するパッケージを配置 • 動作確認用のUnityプロジェクトをImportTestとして配置 • パッケージをImportTestからローカル参照する • この状態でImportTestを開くと、slnファイルが作られる • ソースを直接編集でき、変更がUnity側ですぐにコンパイルされる
  35. 35. #denatechcon これらの導入によって… • より簡単に、より安全にバージョン管理のされた内製 パッケージをタイトルが導入可能になった • 基盤開発だけでなく、タイトル側もUPMパッケージを 作ることで、モジュラリティを高めやすい構成になっ た
  36. 36. #denatechcon 具体的な活用例 • パッケージ管理 • 最速のアセットビルド環境の実現
  37. 37. #denatechcon 最速のアセットビルド環境の実現
  38. 38. #denatechcon Unityのアセットビルド • AssetBundleのビルドと利用は必須スキル • Unityの鬼門とも言える存在 • 管理機構やローダを自作しないと動かせない • 運用していくと、徐々にビルドが重くなっていく • 実装がブラックボックスになっているので内部を把握できない
  39. 39. #denatechcon DeNAの今までのアセットビルド環境 • Unity4時代のものがベース • 運用に入ると大きくは変えられない • アセットバンドルがでかい • 依存するアセットはすべて個々のアセットバンドルに巻き込む • 依存関係を設定しない • ビルドが遅い • 依存関係が無いので、大量のリビルドが必要 • ビルド対象のハッシュを計算をしてビルドをスキップするが、最低必要時間 が長い
  40. 40. #denatechcon 既存システムの運用の結果として… • 後付での高速化の為にフローが複雑に • 手元でのビルドが性能と環境依存度の問題で困難になる • ビルドサーバ(Jenkins)上で全員が手元で確認したい内容をビルド • サーバに負荷が集中し開発規模をスケールする上でのボトルネック化 動作確認のイテレーションが回しにくく
  41. 41. #denatechcon 結構ダメダメだった • 達成しなければならないこと • 手元の小さな変化を手元環境でも高速にビルドでき、確認のイテレー ションをすばやく回せること • AssetBundle同士の依存関係を(柔軟に)設定/管理できること • Unity側の大きな改修を控えていた • 中途半端なものを作っても、速攻で新たなレガシーになりうる状況
  42. 42. #denatechcon SBP & RM & AAの登場 • Scriptable Build Pipeline(SBP) • Player/AssetBundleのカスタム可能なビルドパイプライン • Resource Manager • あらゆるリソースの管理者 • Addressable Assets • SBPとRMを使って作られた、新たなAssetBundle管理システム
  43. 43. #denatechcon Scriptable Build Pipeline • Player/AssetBundleの従来のビルドパイプラインをオーバーホールして、 汎用的なビルドパイプライン構築フレームワークの上で再構成したもの • Pros • ブラックボックスが解消され、不具合を自力で追える • 様々なカスタマイズが可能になった • Cons • 速度面で若干の無駄が残る実装 • ドキュメントが足らない
  44. 44. #denatechcon Scriptable Build Pipelineの構成 • BuildTask • ビルド処理の1ステップ。SBPでのビルドは登録したBuildTaskを順番 に実行する形で処理される • ContextObject • BuildTask間での値の受け渡しに使われるDIコンテナ兼パラメータ • InjectContext属性をBuildTask内で使うと、依存性注入される • ContentPipeline • AssetBundleのビルドに必要なContextObjectをセットしたりしてくれ るWrapper
  45. 45. #denatechcon AssetBundleビルドの中身 • 5フェイズで構成 1. セットアップ 2. スクリプトコンパイル 3. 依存性調査 4. パッキング 5. 書き出し
  46. 46. #denatechcon 重要なContextObject • BundleBuildContent • ビルド対象としてパイプラインに投げ込むアセットの構成 • どのアセットをどんな単位でAssetBundle化したいか • AssetBundleBuild構造体を元に生成される • DependencyData • BundleBuildContentを元に作られる、アセット間の依存情報 • サブアセットなどのアセットごとに含まれるオブジェクト情報 • アセットから依存しているオブジェクト情報 • BundleWriteData • DependencyDataとBundleBuildContentから作られる、パッキング情報 • AssetBundleごとにどのアセットが含まれるかという情報
  47. 47. #denatechcon Resource Manager • リソースの場所と読み込む手段を定義するフレームワークと、その上で実 装されたAssetBundleを始めとした各種アセット読み込みの実装集 • Pros • 読み込む手段を自分で定義して自由に追加でき、組み合わせることで、 複雑な振る舞いも簡単に定義できる • リソースの場所の管理する層を読み込みロジックと分離できる • Cons • ドキュメントが足らない • リソースの場所を管理する層は他に定義しないと、単体では使えない
  48. 48. #denatechcon Resource Managerの構成要素 • IResourceLocation • リソースの識別子。ファイルパスと、自身を処理すべきProviderの識別子を持つ • 自身の読み込みに必要なResouceLocationを再帰的に複数保持できる • IAsyncOperation<T> • T型のリソースを読み込む非同期な操作を表す • IResourceProvider • ResourceLocationをもとにAsyncOperationを生成する、リソースの読み込み手段 • ResourceManager • ResourceLocationを元にResourceProviderを決定し、リソース確保/開放処理を移譲 する
  49. 49. #denatechcon 主なResourceProviderの実装 • AssetBundleProvider • ResourceLocationを元にAssetBundleをロードする • BundledAssetProvider • ロード済みのAssetBundleからAssetをロードする • 依存するResourceLocationの結果からAssetBundleを取得する • CachedProvider • AsyncOperationの参照カウント付きキャッシュを行う • キャッシュがなければ、指定されたProviderに処理を移譲する
  50. 50. #denatechcon 他の要素 • SceneProvider • アセットでなく、Sceneの読み込み手段を提供するもの • InstanceProvider • インスタンスの生成手段を提供するもの • 単なるInstantiateや、オブジェクトプールなど
  51. 51. #denatechcon Addressable Assets アドレスという単位でアセットにアクセスする手段を提供する。Scriptable Build PipelineとResource Managerをバックエンドとして利用し、ビルド と読み込みを行う。 • Pros • 実装をすることなく利用可能で、多くの課題を解決できる • Cons • 速度面で問題を抱えている • 設定の枠から出たことはやりづらい
  52. 52. #denatechcon 材料は出揃った!
  53. 53. #denatechcon どう料理するか? • これまで調査したことを元にした選択肢 • どれも使わない(以前のAPIを使う) • Addressableを拡張して使う • Resource Managerを使う • Scriptable Build Pipelineを使う • Scriptable Build Pipeline + Resource Managerを使う Scriptable Build Pipeline + Resrouce Managerを使うことに決定
  54. 54. #denatechcon Addressableをそのまま利用できない理由 • 速度 • 上記のような構成の場合、規模が大きいプロジェクトで試すと最小の 変化でもビルドに10分かかることもある • 手元でビルドするには困るオーダー • 小回りの効かなさ • パッキング単位を自由に調整したいが、グループ全体 or 1アセットの 単位でしかパッキングできない • グループ毎にアセットを配置する必要があり、煩雑 • 設定ベースのカスタマイズ • コンテキストが深く、設定を見てパッと何が起きるかを理解しづらい
  55. 55. #denatechcon Addressableはなぜ遅い? どうすれば速くなる? • 最低所要時間がビルド対象のアセット総数に比例する • ビルド対象のすべての依存関係を取得しないと、ビルドに必要な情報 を構築できない • 依存性の検査やアセットの実ビルドなど、コストが高い走査は結果を キャッシュしてスキップするが、キャッシュ取得が遅い ビルドに投げ込む必要のあるアセットの特定処理を、 最速であり続けるように実装する
  56. 56. #denatechcon Addressableを改善/拡張しない理由 • やりたいことに対するギャップが大きい • 速度面の改善のためにAddressableの枠組みを保たない、作り直しに 近い実装が必要 • やりたいことを実装として提供してくれているのは、SBPとRMの寄与 が大きい • インターフェースが不安定かつ開かれていない • まだ過渡期なのもあり、インターフェースの変化が多い • 改変しやすい設計ではあるが、外から拡張が容易な作りではない
  57. 57. #denatechcon 選択 • Addressableの役割をする層を、高速性にフォーカスして 再実装する • SBPを直接使ってビルドパイプラインを構築 • RMを直接使ってAssetBundleを読み込む
  58. 58. #denatechcon Abdool (Asset Bundle builD tOOL)
  59. 59. #denatechcon Abdoolの要件 • 高速である • 手元の小さな変化を高速にビルドでき、確認のイテレーションをすば やく回せること • AssetBundleの構成を自由に構築できる • 依存関係を設定し、ロード時に自動的に依存先をロードする • AssetBundle管理に必要な基本機能をきちんと揃える • 柔軟に管理できること • タイトル毎に取るべき構成は異なる
  60. 60. #denatechcon Abdoolのコアコンセプト • 常に最速であり続ける • 高速化は当然だが、モバイルゲーム運用においては速くあり続けるこ とが難しい • リリース直後は10分でビルドできても、1年運用したあとにその速度 を保つのは難しい • ビルド時間をプロジェクトのアセット総数に依存しなくして運用し続 けても速くあり続ける • 変化したアセットの総量のみに依存してビルド時間が伸びるように
  61. 61. #denatechcon AssetBundleのビルドに必要な情報 • ビルドに投げ込むのは変更された物だけではない • 同じAssetBundleに明示的に所属するアセット • 同じAssetBundleから依存する別AssetBundleに所属するアセット • 依存する別のAssetBundleも同時にビルド対象に含めないと、依存性が設定され ずアセットを二重に保持してしまう 依存関係を辿ってこれを特定する作業を 常に最速であり続けるように実現する
  62. 62. #denatechcon 前回ビルドから変更されたファイルの追跡 • Gitベースの変更ファイル追跡 • git statusとgit logを使って、最後にビルドしたリビジョン以降のファ イルの変化をすべて列挙する • ビルド時のアセット側のコミットハッシュをビルド結果に保存して、 git logの範囲を取る材料とする
  63. 63. #denatechcon 依存性調査 • アセットの依存関係を高速に調査しビルド対象を決定する • 変化したアセットから、依存関係の調査を行ってビルド対象を決定 • 常に最速であり続けるために、所属アセットバンドルの指定に制約を設ける • 依存対象のビルドのコストは、ビルド済みのものはSBPがキャッシュしてい るため、枝切りをしている前提であれば無視できる • 前回のビルド結果の利用 • アセットに変化がなければそのアセットからの依存も原則変化しないため、 以前のビルド時の依存関係をビルドに利用できる
  64. 64. #denatechcon 所属AssetBundle構成ルールの制約 • 3つの制約を守って実装する 1. 間接的にAssetBundleに含まれるアセットの存在を許さない 2. あるアセットがどのAssetBundleに属するかは、そのファイルパス情 報を材料として、他のファイルを走査することなく決定できる 3. AssetBundle名から、そのアセットバンドルに含まれるすべてのア セットを、全体を走査することなく列挙できる Assetの総数に影響されずに構成を決定できる
  65. 65. #denatechcon AssetBundle構成の柔軟性の担保 • interface経由のAssetBundle構成ルール指定 • 柔軟性を高めるため、設定でなく実装として記述する • どのアセットがどのAssetBundleに属するかを、特定のinterfaceを継 承したクラスを利用者側で実装して指定する • 論理合成を可能にする • 分岐やPrefix付与をするDecorator実装を完備 • 単純な構成であれば、標準の実装の組み合わせでカバーできるようにする • 独自実装によって前述のルールを逸脱する危険性を下げる
  66. 66. #denatechcon インクリメンタルビルドの流れ
  67. 67. #denatechcon アセット構成
  68. 68. #denatechcon 変更されたアセットを列挙
  69. 69. #denatechcon 変更されたアセットの所属するアセットバンドルを調査対象に
  70. 70. #denatechcon 変更のあったアセットの依存を辿る
  71. 71. #denatechcon 依存しているアセットの所属アセットバンドルをビルド対象に
  72. 72. #denatechcon 前回のビルド時に依存していたアセットバンドルもビル ド対象に
  73. 73. #denatechcon ビルド対象をSBPでビルド
  74. 74. #denatechcon 依存関係管理の難しさ • Nested Prefab / Prefab Variantsという特例 • Nested PrefabはEditor内とビルド時で依存性が異なる • Editorでは元Prefabを参照として持つがビルドすると元Prefabの内容 が展開される • AssetBundleとしては元Prefabへの依存を持たないが、ビルド時には 元Prefabが変化したときにビルドする必要がある • Prefabの派生関係を抜き出してキャッシュ • 派生元が変化した際に、派生先を全てビルドしなおす
  75. 75. #denatechcon Abdoolの戦略 • スクリプトキャッシュ • 運用が進むにつれて、スクリプトコンパイル時間も増大しがち • AssetBundleビルドには普通、コンパイルを必ず伴う • コンパイル時間がビルド時間に影響し、ビルドが早くなるほど支配的になる • 手元でアセットの小さな変化を確認するときのスクリプトの変更は稀 • スキップできれば、時間あたりの試行回数をかなり担保できる コミット毎にキャッシュを持つように
  76. 76. #denatechcon SBPを使ったカスタマイズ • AssetBundleビルドを伴わない、依存関係の調査のみを行 うパイプライン • 一気に結果を取れて、個別にキャッシュされる • 結果は内部構造らしさがあり、少々扱いづらい • スクリプトコンパイルのスキップ • ビルド後に得られるTypeDBのインスタンスをシリアライズしておき、次回の 同条件でのビルド時にデシリアライズ • 循環参照の検出 • AssetBundle同士の依存が循環すると、利用自体は問題ないが整理が困難に • 依存性検査の段階で低コストに検出する
  77. 77. #denatechcon 1アセット変化させた際のビルド時間 69 161 238 242 18 36 57 79 1 1 1 1 0 50 100 150 200 250 1000 2000 3000 4000 FullBuild Cached Build(Similer to Addressable) Incremental Build バンドル数 ビルド時間(sec) キャッシュビルド比 79倍 フルビルド比 242倍に!
  78. 78. #denatechcon Abdoolのロード戦略 • Resource Managerを全面的に利用 • ビルド時に生成したアセットリストや依存関係から、依存に沿った ResourceLocationを生成 • CacheProviderを使うと、これだけで参照カウント付きの実用的なAssetBundle ロード機構を実現できる • AssetBundleとアセットのそれぞれに用いるProviderを型パラメータとして指定 できるようなResourceLocationのファクトリを実装 • 幾つかのProviderを追加で実装 • 暗号化AssetBundle • iOSのOndemand Resources(ODR)
  79. 79. #denatechcon Abdoolを使ったロードのサンプル • 構成 path/to/bundles Catalog.bytes (カタログ) bundle1.unity3d (アセットバンドル) address/of/asset.png (スプライト)
  80. 80. #denatechcon Streamを使った暗号化チャンクロード • LoadFromStreamを使うと、StreamからAssetBundleを 読み込める • ざっくりとした条件としては、シーク可能なStreamを与える必要がある • ランダムアクセス可能な暗号をStreamとして実装すること で、暗号化されたAssetBundleをメモリに全展開すること なく読める • 大半の暗号化用Streamの実装はランダムアクセス不能なので注意 • AssetBundleのサイズが大きいと重くなるので、できるだけ小さくする • アセットのサイズだけでなくAssetBundleサイズにも比例してしまう
  81. 81. #denatechcon 最速のアセットビルド環境の実現 • “常に最速であり続ける”ビルド環境が構築できた • Scriptable Build Pipelineを通してAssetBundleを深く理解すること で、安全なインクリメンタルビルドを実現する構想を得られた • Unity標準の実装を活かしたアセットロード基盤を構築 • かなりのロバストネスを得ることができ、ランタイムで出来ることや 効率も以前より向上させることが出来た
  82. 82. #denatechcon 他にもDeNAのUnity開発で取り組んでいること • .NET 4.x • Google公式のProtocolBuffersの採用 • gRPCの導入と開発ツールへの応用によるインタラクティブなデバッグ • async/awaitの導入とTask<T>の安全なハンドリングのノウハウ構築 • .NET Core SDKの導入 • .NET Core SDKを利用したUnityに依存しないUnity基盤開発体制 • Scriptable Render Pipelineの導入 • SRPによるモバイル向けのカスタムレンダーパイプラインの導入
  83. 83. #denatechcon これからの展望 • Abdoolの改善 • 実機からの要求時にビルドするオンデマンドビルドサーバー • アセットの所属AssetBundleを意識しない、高レベルのロードAPI • UI Elements • UI ElementsによるモダンなEditor UI開発の可能性を調査 • Prefab Improvements • Nested / Variantを活かした効率的なUI実装とワークフローの検討 • ECS / C# Job System • ECSとC# Job Systemの使い分けの模索とノウハウの積み上げ
  84. 84. #denatechcon 最後に • Unityの変化をアンテナをより広げて、キャッチアップし ていく覚悟を持たなければならない • 開かれた可能性を咀嚼して、それを現実のプロダクトに実 利として反映しなければならない • DeNAは覚悟を持ったUnityエンジニアを待っています
  85. 85. #denatechcon #denatechcon

Editor's Notes

  • 本日はお集まり頂きありがとうございます。
    このセッションでは、Unity2018から2019を見据えたDeNAのUnity開発
    というタイトルで、最近のUnityの変化と、それに関連したDeNAのゲーム開発での取り組みについて話させていただきます。
  • まず自己紹介から。
    僕は大竹悠人といいます。
    ゲーム事業部の基盤開発部というところで、主にUnity向けのクライアントライブラリやSDKの開発をしています。
  • 今日のアジェンダです。
    まず、Unityに今起きている変化と、それに対して我々がすべきことについて。
    その後、すべきことの中でDeNAで行っていることを、2つのトピックに絞って話していきます。
  • Unityに今起きていることと我々すべきことについて
  • このセッションを見に来ている方々の殆どはUnityを日頃使ってらっしゃるんじゃないかと思いますが、最近のUnityをみて”何か変わってきたな”って思っているかた、どのぐらいいますか?
    ありがとうございます。N割ぐらいいますね。
    実際、ここ2017-2019にかけて古くから残されてきた課題が、どんどん解決に向かってきています。たとえば、ここに出しているようなフューチャーです。
    また、リリースされたものに対しても”こうじゃないんだよなぁ…”という感覚に陥る事がかなり減ったように思います。
    これはざっくりした主観の話になっちゃうんですが、恐らく多くの人に伝わるんじゃないかと思います。

    なぜこのような感じるのかを深掘りして、Unityに何が起きているのかを分析してみます。
  • まず機能提供の方法が大きく変化しました。
    そのうち大きなものが、Unity Package Managerの誕生です。以下、UPMと呼びます
    UPMは、Unity 2018から正式に使えるようになったnpmベースのパッケージ管理システムです。
    Editor上の操作やManifestファイルの記述に従って、Unityの公式レジストリからパッケージをダウンロードしてプロジェクトに取り込んでくれます。
    これによって、Unityの機能をモジュール単位で選択して取り入れることができるようになりました。
  • これらの変化によって起こったことがあります。
    それは、Unity本体の開発とUnityの開発がイコールではなくなった、ということです。
    今までのUnityはUnity Editorの更新によって多くの機能がアップデートされたり、追加されてきました。
    しかし、UPMの導入により、多くの機能がUPMのパッケージとして提供されるようになり、Editorの更新と機能の更新の関連性は以前のような絶対的なものではなくなりました。
    Unity本体側の機能への依存が無いわけではないので、特定のUnityバージョン以降でしか動かないという制約もパッケージごとにありますが、
    パッケージの更新だけで多くのバグフィックスや機能追加を行うことができるようになりました。
  • 提供される実装の方向性に関してはどうでしょう。
    従来のUnityの実装は、正直にいってモノリシックかつクローズドなものでした。
    基本的に外からの拡張というのは意識されておらず、かといってプロダクションでの需要を満たそうとすると拡張を要求される、だが実装も開発もかなりクローズドで、ナイーブな対応を余儀なくされてきました。
  • このあたり、最近はかなり変わりました。
    まずカスタマイズが最初から意識されるようになり、全体的にモジュラリティが向上しました。
    その結果として、1パッケージで全てをまかなわずに、複数のパッケージのレイヤによって機能を提供するようなケースも増えてきています。
    ソフトウェアの設計も以前のいわゆる”ゲームらしい”感じからかなりモダンなものになってきていますし、パフォーマンスが重要な場面は流儀に乗るだけで高速性を得られる枠組みを作ってくれています。
    また、かなり開発のオープン化が進みました。
    多くのパッケージがGithub上で公開され、Preview版などの早期の段階からユーザーが開発に参加しやすいようになりました。
    Unity本体のC#ソースコードも公式から提供され、安心して内部実装を追うことができるようになったんじゃないかと思います(笑)
  • これらの実装の変化は、様々な領域に対して適用されています。
    公式に提供してきた機能ドメインに対しては、単に後方互換を保って機能を強化していくだけでなく、カスタマイズ可能にオーバーホールしたり、捉え方から変えて提供し直すような流れが来ています。
    また、使い方、管理の仕方を提示してくれるようなモジュールも、上位レイヤとして用意されるようになってきています。

    公式に提供されてこなかった新たな機能ドメインに対しては、AssetStore上の有力アセットを買収して公式に取り入れていくという流れもいくつかでてきています。
  • これらをふまえて、我々はどうしていくべきでしょうか
  • Unityだからこれが出来ない、または回りくどい非効率な解決しか出来ないといった事は皆さん今まで出会った事があると思います。
    ですが、これまで説明してきた変化の流れの中で、この状況は大分改善してきています。
  • 従って、我々はまず何が出来るようになったか、切り開かれた可能性を知るべきです。
    単純に提供された機能だけでなく、何をカスタマイズできるようなり、どんな可能性が生まれたのかを知り、
    そしてその知った可能性を検証して、実現して、具体的な価値に転換させていかなければなりません。
  • ここから、DeNAでの具体的な事例についてお話させていただきます。
  • 他にも様々な取り組みをしていますが、時間の都合もありますので今日はパッケージ管理とアセットビルド環境についてに絞っていきます。
  • まず、パッケージ管理についてです。
  • Unityの今までのパッケージ管理について振り返ってみましょう。
    今までのUnityのパッケージと言えるものといえば、.unitypackageがありました。
    これはファイルとメタ情報をセットにしてアーカイブできる、いわばアセットエクスポート機能です。
    コンテキストメニューから手軽に作成できますが、そもそもパッケージ管理システムではないので、
    モジュール管理目的に使おうとすると、更新時に古いアセットが残ってしまったり、安全なパッケージ削除が難しかったりといった難点があります。
  • パッケージ管理なしで内製ライブラリやSDKを提供していた結果として、古いファイルが残っている事による問題がタイトル開発時に頻発していました。
    また、ライブラリ間の依存関係を管理できない故に巨大な1リポジトリで多くのライブラリを管理している状況になっており、内製ライブラリの横展開を行う上ではまともなパッケージ管理システムがなければ耐えられないという状況になっていました。
  • なので
  • DeNAでは泣く泣く、Uptというパッケージ管理システムを内製していました。
    基盤開発が逆にボトルネックになってしまうことが無いように、自由に派生やパッチを作り、自治的な管理が可能であること、
    また外部開発会社への提供などを考えてUptが無くても最低限導入はできること、
    最低限開発が回るサイクルが作れれば良いのでこの実装は手がかからないことを目指しました。
  • 構成としては、
    unitypackageと一緒にメタ情報を載せたmanifestファイルを生成するような形になっていました。
    UnitypackageにパッケージングしたアセットのGUIDをmanifestに含んでおき、更新時にこのGUIDを元に古い導入済みのアセットを削除してから新しくunitypackageをインポートする形になっています。
    バージョン管理については実際のインポート前にManifestファイルに記載した依存情報とバージョン情報をみて適宜SKIPすることで実現しています。
  • 配布方法としては、自治的な運用と運用工数の削減を主眼に置いて中央サーバを置かない構成にしました。
    Githubのリポジトリとブランチを指定してインポートする形にして、
    対応しているファイル構成のリポジトリを作ればUpt対応パッケージになり、
    forkしてからインポートし直せば派生も可能という形にしていました。
  • これらによって当初の目的は達成できましたが、
    そもそもパッケージ管理システムが社内独自であるというのは、既存のエコシステムに乗ることが出来なかったり、利用にあたっての学習コストが高かったりという問題がありました。
  • そんな最中、冒頭でも上げたUPMが登場ました。
    独自で運用せずに目的を達成できるのであれば、それにこしたことはありません。
    一旦、インハウスでのパッケージ管理で必要な事を洗い出し、UPM移行を検討しました。
  • 必要なこととしては、以下のような点が上げられるかと思います。
    まず、パッケージ管理システムとしての基本機能が問題なく動くこと。
    次に、独自パッケージを登録できること。
    最後に、パッケージのベンダーリングができることです。

    パッケージ管理システムの基本機能として求められるのは、ざっくり言うと
    パッケージのバージョンと依存性を管理できること、そしてパッケージの導入・削除・更新を安定して行えることにあると思います。

    独自パッケージを登録できることは、内製ライブラリの管理に用いる以上、当然できないと話にはなりません。

    パッケージのベンダーリングができることは、パッケージレジストリのアベイラビリティにタイトル開発が不必要に引っ張られることを避け、
    また開発中のパッケージの確認がまともなイテレーション速度で回せるようにするためには必須です。

    UPMを利用する場合に、これらがどのように解決できるかを説明していきます。
  • UPMはベースとしてはnpmを下敷きにしていますし、幾つか制約はあるもののUPM自体の挙動が安定さえすればパッケージ管理システムとしての基本機能は十分なものを持っています。
    また、こういったものは、広く広まった物であれば、エコシステムの中に存在する豊富なツールチェインを活かすことができるという利点もあります。
    UPMの場合、Unity標準であることでUnity上で実装したツールも活かしやすくなる上、npmというjavascript界での標準をベースとしているため、npm向けの様々なツールを活用できます。
  • UPMで独自パッケージを運用するためには、パッケージレジストリを社内にサーバーとして立てる方法と、ローカルパッケージ運用の二通りがあります。
  • パッケージレジストリの構築は、npm用のレジストリサーバーであるヴェルダッシオを用いることで実現できます。
    運用する上では2点工夫が必要です。
    まず、Unity管理のパッケージも取得できる必要があるため、Unity管理のモジュールについてはヴェルダッシオのuplinkというリバースプロキシ機能を使って公式のUPMレジストリに中継する必要があります。
    また、ポート番号は80でListenする必要があります。これは、UPMがこれ以外のポートでの接続に対応していないためです。
  • 独自パッケージの作成するには、
    まずはnpmモジュールの作法に従ってpackage.jsonをルート配置し、パッケージ情報を記述します。
    属性はいくつか独自のものがありますので、UPMのドキュメントを見ながら適宜設定していきます。
    UPMでは、ルート以下のファイルがプロジェクト内に存在するかのような形でインポートされますが、C#スクリプトは必ず何らかのAssembly Definition Filesに属さなければならないという制約があります。
  • Assembly Definition Filesとは、Scriptのコンパイル単位を分割する機能です。
    .asmdefファイルを置いたディレクトリ以下のスクリプトを、独立した一つの単位としてコンパイルしてくれるようになります。
    Asmdefからは、他のasmdefを参照関係として設定でき、
    設定した場合は対象のasmdefに対して依存が設定され、
    内のコードを参照することができるようになります。

    asmdefとしての依存関係は、UPMのパッケージとしての依存とは別物です。
    Asmdefとしての依存を設定したとしても、UPMとしての依存が設定されるわけでもないので、それぞれに対してきちんと依存関係を自分で設定してやる必要があります。
    1つのUPMパッケージ内に複数のディレクトリを置き、その下にasmdefをそれぞれ配置することで、複数のasmdefをもたせることが出来ますが、逆に複数のパッケージに分割して1つのasmdefをもたせることは出来ません。

  • Asmdefを使ったクラスプラットフォーム構成についてです。
    Asmdef内では、プラットフォーム識別マクロが動かなくなるため、特にネイティブプラグインなどで問題がおきます。
    これは、asmdefのプラットフォーム設定で代用可能です。
    同名クラスの定義を持たせたプラットフォーム毎のasmdefを用意した上で、それら全てに依存するファサードとなるasmdefを上段に用意することで、プラットフォームに合わせて適切に利用するasmdefを切り替えてくれるようになります。
  • 独自パッケージレジストリの利用方法です。
    これは簡単で、Packages以下のmanifest.jsonファイルを編集し、registryキーにレジストリのURLを記述します。
    あとは、dependenciesに利用したいパッケージ名とバージョンを記述すれば完了です。
    Unityがフォアグラウンドになったタイミングで、自動的にこの記述に従ってパッケージがインポートされます。

    また、この画像にあるように、fileスキームで任意のパッケージのディレクトリを指定することで、
    ローカルにあるパッケージをパッケージレジストリを介さずにインポートすることができます。
  • パッケージのベンダリングは、正にこのローカル参照を用いることで実現できます。
    注意点としては、ベンダリングしたパッケージから依存するパッケージは、同様にローカルパッケージ運用をしないベンダリングされません。

    ベンダリングする際にパッケージをどうやって取ってくるんだという話があると思いますが、これにもnpmのツールチェインを使えます。
    yarnを利用することで、依存パッケージも含めてまとめて取得することができるので、非常に便利です。
  • ベンダリングが出来るとわかったので、パッケージ開発時にもこれを活かすことができます。
    動作確認用のUnityプロジェクトと開発するパッケージをgitで管理し、動作確認用プロジェクトから、ローカル参照します。
    この状態で動作確認用プロジェクトをUnityで開くと、パッケージ側のコードを直接編集可能なソリューションファイルが生成されるので、これをIDEで開けばそのまま独自パッケージ開発を進めることが出来ます。
    パッケージに加えた変更は、プロジェクト内のコードへの変更と同様に、Unityが検知して自動的にコンパイルしてくれます。
    テストコードなどは、動作確認用のプロジェクトの内部に配置しています。
  • これらによって、より簡単に、より安全に内製パッケージをタイトルが取り入れられるようになり、
    気軽にタイトル側もUPMを使った、モジュラリティを高めやすい構成でのパッケージ開発をとることができるようなりました。
  • 次に、最速のアセットビルド環境の実現について話します。
  • Unityのアセットビルドは長らく鬼門のような存在でした。
    プロダクションではほぼ必須スキルと言えるのに、自分たちで管理機構からローダまで自作しないとまともに使えなかったり、
    運用していくとビルドがどんどん重くなったり、ブラックボックスすぎて何をしているかもわからないなど、散々な有様でした。
  • DeNAでは、今までUnity4時代に作ったライブラリを都度改修しつつ使っていました。
    アセットバンドル同士の依存関係が設定できないという割り切った仕様で、複数のアセットバンドルから使われているアセットは重複して含まれるため、アセットサイズの増大の一員となっていました。
    また、インクリメンタルビルドもサポートはしていますが、全ビルド対象のハッシュを計算した上で変化したアセットをビルドするという仕様のため、最低でも必要なビルド時間がそれなりに長くなってしまっていました。
  • インクリメンタルビルドや並列ビルドなどのビルド高速化施策は後付で行っており、これによってフローが複雑になったり、ビルドマシンのスペックにも頼りがちになりました。
    こうなると、手元でのアセットバンドルビルドのハードルが上がっていき、
    開発者は小さな変化でもビルドに必ずビルドサーバーを通すようになっていきました。
    要するに、Jenkinsにビルドさせるためにコミットを行っていた、ということです。
    これによって、実機での動作確認のイテレーションが回しにくくなり、開発速度や確認精度を下げる要因になってしまっていました。
  • 要するにかなり大本からダメダメで、作り直してなんとかする必要がもともとありました。
    新しく作り直すに達成しなければならないこととして、ビルドフローの肥大化を防ぐため、動作確認のイテレーションを速く回せ、AssetBundle同士の依存関係を柔軟に管理できることを目指していました。
    ですが、このあたりにUnityが大きく手を入れていく話がUniteなどで2年ほど前から流れてきていたので、機を伺っていました
  • そして具体的に登場したプロダクトが、

    Scriptable Build Pipeline
    Resource Manager
    Addressable Assets

    の三点です。
  • Scriptable Build Pipelineについて説明していきます。
    これは、プレイヤーやアセットバンドルの従来のビルドパイプラインをオーバーホールして、汎用的なビルドパイプライン構築フレームワークの上で再構成したものです。

    これにより、ブラックボックスが解消され、今までできなかったカスタマイズが出来るようになりました。

    難点としては、速度面でのムダがそれなりにあるのと、とにかくドキュメントが少なく扱いづらいことです。
    最近、Unity 山村さんの発表でその輪郭が解説されたので、一度目を通してみることをおすすめします
  • Scriptalbe Build Pipelineのフレームワークは、
    ビルド処理の一部を表すBuildTask
    BuildTask間のデータ受け渡しに使われるコンテナであるContextObject
    そしてAssetBundleビルドを実行するContextObjectを生成してくれるContentPipelineが主なものとしてあります。
  • DefaultBuildTaskというクラスを見ると、AssetBundleビルドの輪郭が把握できます。
    これを見ると、大まかに
    セットアップ
    スクリプトコンパイル
    依存性調査
    パッキング
    書き出し
    の5フェイズで構成されていることがわかります。
  • これらのフェイズの中で読み書きされるContextObjectの中でも、次の3つはカスタマイズを施す上で重要な位置を占めています。
    BundleBuildContextは、ビルド対象の定義となる入力です。
    DependencyDataには、アセット同士の依存情報が吐き出されます。
    BundleWriteDataには、アセットをどういう単位でAssetBundleにするのかという、パッキング情報が吐き出されます。
  • 次に、ResourceManagerについてです。
    これは、リソースの場所と読み込む手段を定義するフレームワークと、その上で実装された各種リソース読み込みのデファクト実装集です。

    拡張が非常にしやすく、リソースの場所を管理する層と具体的な読み込むロジックを分離することができます。

    難点としては、これもドキュメントが足らず、リソース管理自体は簡単な管理で問題ないですが、他に定義する必要があります。
  • Resource Managerは主に以下のような要素で構成されます
    ResourceLocationはリソースの識別子、場所を表し、再帰的に依存するリソースを表現できます。
    AsyncOperationはT型のリソースを読み込む非同期な操作を表します。
    RsrouceProviderは、ResourceLocationを元にAsyncOperationを生成します。
    ResourceManagerは、ResourceLocationを受け取って、登録されたResourceProviderの中からリソース確保/開放処理の移譲先を決定します。
  • ResouceProviderは様々なものが標準で実装されていますが、主だったものとしては以下のものがあります。
    AssetBundleProviderは、AssetBundleをロードします。
    BundledAssetProviderは、AssetBundleからアセットをロードします。
    CachedProviderは、AsyncOperationの参照カウント付きキャッシュを行い、キャッシュがなければ指定したProviderを移譲します。
  • 他にも、SceneProviderやInstanceProviderなど、アセットロード以外の目的のProviderのインターフェースや実装も揃っています。
  • Addressable Assetsについてです。
    これは、アセットにアドレスをという文字列を振り、アドレスを用いてランタイムでアセットに対してアクセスする手段を提供するものです。
    どういう単位でパッキングするのか、どういう読み込み方をするのかは、設定でカスタマイズする形になります。
    バックエンドとしてScriptableBuildPipelineとResourceManagerを利用しており、概ねこれらの管理層として機能しています。

    手っ取り早く使えますが、速度面に問題があるのと、設定の枠から出たことはやりづらく、プロダクションで使うには足りないものもあります。
  • これで、新たなアセットビルド基盤を作る材料は出揃いました。
  • これらの材料の調査の結果、いくつか選択肢が出てきましたが、我々はAddressableの利用をやめ、Scriptable Build PipelineとResourceManagerを使うという選択をしました
  • 先程も欠点として若干触れましたが、Addressableを利用しないのは、速度面と、小回りの効かなさが理由です。
    Addressableを試してみたところ、規模の大きいプロダクトのアセットを持ち出してテストしたところ、1アセットのみを変化させてもビルドに10分ほどかかることがありました。
    これでは、手元でビルドを気軽に回せるとは言えないオーダーです。
    また、アセットバンドルを作るに当たって、キャラや技単位など、適度な単位でパッキングして配信したい需要がありますが、Addressableはかなり荒い制御しかかけることができません。出来なくはないですが、かなりムリな運用になることが想定されました。
  • Addressable Assetsのビルドの最低所用時間が長いのは、ビルド対象のアセット総数に比例してビルドの最低所用時間が伸びることにあります。
    ビルド対象のすべての依存関係を取得しないと、ビルドに必要な情報を構築することができません。
    コストが高い処理はすべて結果をキャッシュするのですが、そのキャッシュ取得だけでそれなりの時間がかかります。

    そのため、ビルド対象自体を低コストで枝刈りするような形のパイプラインを組む必要があることがわかりました。
  • これを踏まえて速度面や挙動面の改善を図ろうとしても、そもそもAddressableの現状の実装とのギャップは大きく、改修する方向での実現には努力が多く必要になりそうでした。
    加えて、過渡期なのもあってインターフェースも安定しておらず、改変しやすい保守性のある設計にはなっているものの、外からの拡張を容易にしようと作っている訳ではないようであったので、苦労しても相応のリターンを得られるとは言い難い状況でした。
  • これらの理由から、我々はAddressableを捨てて、SBPを直接使ってビルドパイプラインを構築し、直接RMを使って読み込みをさせる形でAddressableの代替となる層を独自に実装することにしました。
  • そうやって生まれたのが、Abdoolです
  • Abdoolの要件としては、依存のAssetBundle管理システムの課題がそのまま当てはまりました。
    AssetBundle間の依存関係の構築を、設定の柔軟性を保ったままサポートしつつ、
    高速に確認のイテレーションを回せる速度を担保することです。
  • これを踏まえ、Abdoolは常に最速であり続けることを目指して設計しました。
    イテレーションの加速のためにはビルドの高速化は当然重要ですが、モバイルゲーム開発においてはその高速性を常に維持することが難しいです。
    リリース直後は10分でビルドできたとしても、1年後にリソース総数が飛躍的に膨らんだ結果、ビルドに数時間かかってしまう、ということがありえます。
    そのために、ビルド時間をプロジェクトのアセット総数に依存しなくし、変化したアセットの総量のみに依存してビルド時間が伸びるようにすることを目指しました。
  • そもそも、Unityで正しくAssetBundleをビルドするためには、ビルド対象のAssetBundleに直接含むアセットすべてと、そこから依存しているAssetBundleを全て同時にビルドパイプラインに投げ込む必要があります。
    これを事前に枝刈りして行うためには、依存関係を辿ってビルドの影響範囲を調査する必要があるため、Abdoolではこの処理を常に最速であり続ける形で実現することを目指しました。
  • このためにまずはGitベースの変更済みのファイル追跡を採用しました。
    ビルド後にビルド元アセットのコミットハッシュを記録することで、最後のビルド以に変化したファイルを列挙出来るようにしています。
  • これを元に、依存関係を辿って実際のビルド対象となるAssetBundleの構成を決定しなければなりません。
    常に最速であり続けるために、所属するアセットバンドルの指定方法に幾つか制約をもたせています。
    ビルド対象には依存アセットバンドルも含まれますが、変化が無いビルド済みのものはSBPがキャッシュを参照してくれるため、事前に大半を足切りしている前提であればこのコストはほぼ無視できます。

    また、変化が無いアセットから依存するアセットが変化することはないので、これを利用して前回のビルド時のアセットバンドル間の依存関係をビルドに利用していきます。
  • 所属アセットバンドルの構成に関して課しているルールは、
    第一に、間接的にアセットバンドルに含まれるアセットの存在を許さないこと
    第二に、どのアセットバンドルに属するのかは、ファイルパス情報のみを材料として決定することが出来ること
    第三に、アセットバンドル名からそのアセットバンドルに含まれる全てのアセットを全体を走査することなく列挙できることです
    要するに、アセットの総数に依存して負荷が高まるような挙動を許さないようなルールにしています。
  • AssetBundleの構成の記述の自由度を担保するため、AssetBundleの構成ルールは設定としてではなく、特定のinterfaceを実装する形で指定するようにしています。
    標準の実装でありそうなパターンの実装と、分岐やPrefix付与を行うデコレータ実装を用意することで、実装負荷を下げて前述する制約を逸脱したルールを実装してしまう危険性を下げられるようにもしています。
  • Abdoolによるビルドの流れを説明していきます。
  • Abdoolによるビルドの流れは以下のとおりです。
    文字だと理解しづらいので、図解して説明していきます。
  • このようなアセット構成のビルドを考えてみます。Bundle1~6を表す箱ににそれぞれアセットが含まれており、依存関係を線で表しています。
  • まず、Bundle1のasset1.prefabが変更されたケースを考えてみます。Abdoolはまず、変更のあったアセットをGitを使って列挙します。
  • 次に、変更されたアセットが所属するアセットバンドルをビルド対象とした上で、それに含まれる全てのアセットを調査対象にします。
  • つぎに、調査対象のアセットからの依存を辿ります
  • 調査対象から依存しているアセットが所属しているアセットバンドルを、ビルド対象にします。
  • このままでは、依存として検知するべきBundle6を検知できないため、
    ビルド対象となったアセットバンドルから、前回のビルド時に依存していたアセットバンドルもビルド対象として、調査対象から拾えない依存関係をすべて補完します。
    調査対象になっているAssetBundleも、変更のあったAssetBundleなので当然ビルド対象とします。
  • これをビルドにかけると、変化のないものはビルドがスキップされ、変化のあったBundle1のみが実際にビルドされます。
    ビルドに関係のないBundle2やBundle5がどれだけ量があっても、変わらないコストでビルドすることができます
    このあと、ビルドによって得られた今回のビルド内容の依存関係グラフと前回ビルド時の依存関係グラフを合成して、新たな依存関係グラフを作り出し、カタログファイルとして保存しています。
  • また、依存性管理に関してはUnity 2018.3のNested Prefab対応として特例対応をしています。
    NestedPrefabはEditor内では内包するPrefabを参照として保持しますが、ビルド時には参照が解かれて全ての内容が参照元に展開されるという特性があります。
    このため、AssetBundle化したあとの依存性と、ビルド時に変更検知として考慮すべき依存性が異なっています。
    Abdoolでは、Prefabの派生関係を全て抜き出した上でビルド時に保存しておき、派生元が変化した際に全ての派生先をビルド対象とすることで対処しています。
  • また、スクリプトのコンパイル時間も運用が進むにつれて増大しがちですが、AssetBundleビルド時にはスクリプトコンパイルを伴います。
    これをSKIPできれば、小さな変化を確認するイテレーションには効果があると判断しました。
  • SBPを使って実際に行ったカスタマイズがこちらです。
    そもそもAbdoolのAssetBundleビルドでは実際のビルドを行わない、依存関係の調査のみを行うパイプラインが必要なので、そちらをSBPを用いて構築しています。
    スクリプトコンパイルのSKIPは、ビルド後に得られるTypeDBのインスタンスをキャッシュしておくことで実現できました。
    循環参照検出は、AssetBundle同士の循環した依存を検出して複雑な依存性が生まれるのを事前に察知する試みです。
  • このような工夫によって、目的としていた高速であり続けるビルドパイプラインを構築することが出来ました。
    効果を確かめるために、Adressable同様のSBPのキャッシュを利用したビルドと、Abdoolの枝刈りを行うインクリメンタルビルド時のビルド時間を比較してみます。
    条件として、平均250KBの、最大で3hopの依存関係を持つアセット郡を用意し、ごく小さな1アセットのみが変化した場合を計測しました。

    このように、フルビルド時やキャッシュを使ったビルドではアセットバンドル数の増加につれて比例してビルド時間が伸びていきますが、
    インクリメンタルビルドでは常に一定の時間でビルドができている上、絶対値としても全て1秒で処理できています。
    アセットバンドル数4000という数になるとキャッシュビルド比で79倍、フルビルド比で242倍もの性能を達成できました。
  • ロードをどのようにしていくかについても話します。
    ロードにはResourceManagerを全面的に活用しています。
    ビルド時に生成したカタログファイルを元にロードしたいAssetの所属するAssetBundleやその依存AssetBundleを抽出し、依存に沿った形のResouceLocationを生成しています。
    ResourceManagerを採用することで、ResourceLocationを吐き出すだけで参照カウント付きな実用的なAssetBundleロード機構が実質100行前後のコードのみで実現できました。
    また、幾つかの拡張Providerを追加で実装し、暗号化やiOSのOndemand Resources経由でのAssetBundleロードも気軽にできるようにしています。
  • Abdoolを使ったロード処理のサンプルです。
    AssetBundleのロードはProvideResource時に透過的に行われ、また読み込み方法もLocatorに型パラメータとして渡すProviderの組み合わせによってカスタマイズすることができます。
  • カスタムProviderの一つとして、Streamを使った暗号化チャンクロードも実現しています。
    通常、暗号化を施すと復号化したあとにLoadFromMemoryで読み出す必要があるためマネージドメモリを消費しますが、LoadFromStreamを使うとこれを回避することができます。
    ランダムアクセスができる暗号アルゴリズムをStreamとして実装することで、暗号化されたAssetBundleをメモリに全て展開することなくチャンクロードで読み込ませることができるようになります。
    1アセットバンドルのサイズに比例してアセット読み込み時のリード量が増えるという特性がUnity側にあるので、このパターンを用いるときは1AssetBundleをできるだけ小さく保つ必要があります。
  • まとめます。
    SBPを活用することで、常に最速であり続けるビルド環境を構築することができました。
    また、ResourceManagerを活用することで、Unity標準の実装を最大限に活かす形でアセットロード基盤を構築できました。
  • 他にもDeNAのUnity開発では、基盤・タイトル共に様々なことに取り組んでいます。
    たとえば、.NET 4.xや.NET Coreの活用やScriptable Render Pipelineなどが上げられます。
  • これからの展望です。
    Abdoolに対しては、実機からの要求に応じてEditor上でオンデマンドビルドして最新のアセットを供給するオンデマンドビルドサーバーの構築や、AddressableのAssetReferenceのようなハイレベルAPIの拡充を行っていきます。
    ほかにも、Prefabの進化を活かしたワークフロー検討はもちろん、ECSやUI Elementsなどのこれからの技術に対してもしっかりと検討していこうと考えています。
  • 最後になりますが、
    Unityの最近の変化はアンテナをより広げて覚悟を持っていかないと拾いきれないほど大きなものになってきています。
    アンテナだけなく、開かれた可能性を咀嚼して、プロダクトにしっかりと反映していかなければなりません。
    DeNAはそんな覚悟を持ったUnityエンジニアを待っています。

×