Unity UGUI最適化に関して、あう可能性のある問題ーーUGUI編

##キーワード
インターフェース制作
メッシュの再構築
インターフェース切り替え
ローディング相関
フォント

---
##一、インターフェース制作

**Q1: UIインターフェースで、CanvasとRectTransformのどちらを使ってルートノードを作ることはよりいいでしょうか？どちらの方法がより効率的ですか？**
>Canvas分割は大きなトピックです。簡単に言うと、一つのCanvasにあるすべてのUI要素が1つのメッシュに結合されているため、大きすぎるメッシュを更新するコストが非常に高くなります。ですから、一般的に各複雑なUIインターフェイスにそれぞれのCanvas（サブCanvasもできる）を作成することをお勧めします。UIインターフェイスが非常に複雑な場合、さらに多くのサブCanvasを分割する必要があります。同時に、動的要素はCanvasのMesh更新を引き起こすため、動的要素と静的要素の分離に注意してください。最後に、Draw Callの増加につながるため、Canvasを細分化しすぎることはできません。

---
**Q2: UWAパフォーマンステストレポートにあるShared UI Meshはどういう意味ですか？**
![2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/90ae27fb-7d6d-1c4f-dd1f-3c58061a090c.png)
>Shared UI Meshとは、Unity 5.2バージョン以降のUGUIシステムによって維持されるUIメッシュです。以前のバージョンでは、UGUIはCanvasごとに一つのMesh（名前はBatchedMeshで、マテリアルに応じて異なるSubMeshに分割されます）を維持します。Unity 5.2以降、UGUIは最下層にマルチスレッドルールを導入し、Meshのメンテナンスにも変更が起きました。現在、Shared UI Meshは静的なグローバル変数として、最下層によって直接維持されています。このサイズは、現在のシーンにあるすべてのアクティブなUI要素が生成したMesh数に関します。
>
一般的に、インターフェースにUI要素が多い場合やテキストが多い場合にこの値が高くなります。UI/Effect/shadowおよびUI/Effect/Outlineはテキストが導くMesh数を大幅に増加しますから、これらの二つのEffectを使用する時にこの値を特に注意する必要があります。

---
**Q3: Unity 5.xバージョンでは、UGUIを使用する過程にアトラスをパッケージに入れましたが、これで自動的に更新できなくなりました。どうすれば自動更新できますか？**
>Unity 5.xでUGUIが使用するAtlasは確かに見えないため、独立に直接パッケージすることはできません。ただし、Atlas冗長性を回避するために、Packing Tagの同じソーステクスチャファイルを１つのAssetBundleに（同じAssetBundle Nameを設定する）パッケージすることをお勧めします。同時に、このようにパッケージすると、依存するCanvasのパッケージ化をより自由になさせます。つまり、それに依存するすべてのCanvasをAssetBundleに入れる必要がなく、更新する時にAtlasがあるAssetBundleを直接更新すれば大丈夫です。

---
**Q4: Canvas.SendwillRenderCanvasesは、ScrollRectがスクロールしているときに生成されますが、これを削除する方法はありますか？**
>ScrollRectがスクロール時に、OnTransformChangedのコストを生成します。これはUI要素が移動したときにトリガーされますが、通常、Canvas.SendWillRenderCanvasesをトリガーしません。
>
Canvas.SendWillRenderCanvasesに時間がかかると観察した場合は、ScrollRectが配置されているCanvasのPixel Perfectオプションがオンになっているかどうかを確認できます。このオプションを開くと、UI要素が移動する時、長さと幅は（ピクセルを揃えるために）微調整されます。一般的に、ScrollRectにより多くのUI要素が存在しているため、高いCanvas.SendWillRenderCanvasesコストが生成しやすいです。したがって、Pixel Perfectを閉じて効果が許容できるかどうかを確認でき、またはスクロールプロセス中にPixel Perfectを一時的に閉じて、コストをなくすことができます。

---
##二、Meshの再構築
**Q1：UGUIにImageのColor属性を変更しましたが、Canvasは再構築されますか？Animationの変化量としてColorを借りたいだけです。**
>ImageコンポーネントのColor属性を変更する場合、原理は頂点カラーを変更することであるため、MeshのRebuild（つまりCanvas.BuildBatch操作、同時にCanvas.SendWillRenderCanvasesコストもある）を引き起こします。頂点カラーを介してIU要素カラーを変更する利点は、マテリアルが変更されないようにすることが保証できため、追加のDraw Callを生成しないことです。

---
**Q2：Unity自有のUI Shaderで色を処理する時、_Color属性を変更すれば頂点の再構築はトリガーされませんか？**
>UIのデフォルトShaderに一つのTint Color変数が存在し、通常、この値は定数(1,1,1)であり、変更されません。スクリプトを使用してImageのMaterialにアクセスし、そのTint Color属性を変更すると、UI要素が生成したMesh情報に影響ありませんので、MeshのRebuildを導きません。しかし、こうすればマテリアルが変更されているため、Draw Callは１つ増えます。

---
**Q3: UGUI Batchに対して何かアドバイスありませんか？何かBatchのルールは存在しますか？**
>UGUIで、BatchはCanvasを単位にします。つまり、同じCanvasにあるUI要素が最後に同じMeshにBatchされます。Batchする前に、UGUIはUI要素のマテリアル（通常はAtlas）とレンダリング順序に従ってUI要素を再配置します。レンダリング結果を変更せずに、同じマテリアルのUI要素を可能な限り同じSubMeshにマージして、DrawCallを最低まで低下させます。Batch操作は、UI要素が変更された場合にのみ実行され、合成されたMeshが大きいほど、操作にかかる時間が長くなります。
>
ですから、複雑なCanvasから頻繁に変化しているUI要素（位置、色、長さや幅など）を分離してから、複雑なCanvasの頻繁再構築を避けます。

---
**Q4: Unity 5.3.4バージョンのUGUI Canvasを使用しています。毎回Rebuild Batchが影響する頂点数は確認するにはどうすればよいですか？Memory Profilerは１つの方法ですが、見つけるのは簡単ではありません。**
>Unityエンジンは5.2以降にShared UI Meshを使用してUI Meshを保存し始めたため、RebuildごとにUI頂点数を確認することは非常に困難です。しかし、開発チームはFrame Debuggerツールを介してUIインターフェイスをさらに確認ことを試みることができます。

---
**Q5: 動的および静的分離または複数のCanvasによってもたらされるパフォーマンス向上の理論的根拠は何ですか？ 静的部分が変更されない場合、Canvas全体が更新されませんか？**
>UGUIでは、Mesh更新または再構築（できる限りにUI部分のDrawCallを合併する）はCanvasを単位にしており、中にあるUI要素が変更する（位置、色など）時のみ行われます。したがって、動的UI要素が静的UI要素から分離された後、動的UI要素の変化が導くMesh更新または再構築に関する範囲を減らすことができ、それによってある程度のコストを減らすことができる。静的UI要素が配置されているCanvasには、グリッドの更新と再構築のコストがありません。

---
**Q6: UWAは、「静的UI要素と頻繁に変更している動的UI要素を可能な限り分離し、異なるPanelに保存します。同時に、頻度の違う動的要素を異なるPanelに保存します。」とお勧めしますが、特殊効果をPanelに置いたら、動的要素に配置する必要はありませんか？**
>一般的に、特殊効果はパーティクルシステムでありますが、パーティクルシステムのレンダリングとUIは独立しております。Render Orderを介してのみ両者のレンダリング順序を変更できます。パーティクルシステムの変化は、UI部分の再構築を引き起こしませんので、特殊効果の配置には特別の要求はありません。

---
** Q7: 複数の人が同じ画面にいる場合、キャラクターの動きによって頭の上の名前Meshが再編成され、より深刻なラグが発生します。最適化の方法はありますか？**
>UGUIを使用して開発した場合、頭の上の文字量が多いと、パフォーマンスの問題が発生しやすくなります。最適化には、次の点を考慮することができます。
>
1、UI / Effect、特にOutlineの使用はできるだけ避けてください。これにより、テキストのメッシュが4倍になり、UI再構築のコストが大幅に増加します。
2、Canvasを分割し、画面上のすべての頭上のテキストをグループ化し、異なるCanvasに配置すると、一方で、更新の頻度を減らすことができます（グループ内にテキストの移動がない場合、グループは再構築されません）、一方、再構築時に関するMeshのサイズを縮小できます（再構築はCanvasを単位として実行されます）。
3、移動中のテキストの更新頻度を減らすために、テキストの変位がしきい値を超えると本当に移動を始めると考えできます、これでCanvasの更新頻度を確率から減らすことができます。

---
##三、インターフェースの切り替え
**Q1: ゲーム内でUIが重複していますが、どうすればよいですか？たとえば、現在、フルスクリーンUIインターフェイスがあります。ボタンの1つをクリックすると、もう１つのフルスクリーンインターフェイスが生成され、最初のUIインターフェイスをカバーします。現在のアプローチは、カバーされたインターフェイスにSetActive（False）を行いますが、後でSetActive(True)を行う時にGC.Alloc が生成されます。この状況で、BatchesとGC.Allocを両方削減したい場合、何かお勧めする解決策ありませんか？**
>OutUIなどのLayerを追加して、CameraのCulling MaskにこのLayerを選択しない（つまり、このLayerをレンダリングしない）ことを試すことができます。これで、UIインターフェースが切り替えると、 Canvas の Layer を直接変更して「非表示」を実現できます。ただし、イベントの禁止や動的UI要素の無効化などに注意する必要があります。
>
このアプローチの利点は、切り替え時のコストが基本的になく、冗長なDraw Callがないことです。欠点は、「非表示」の場合でも一定量の継続的なコストがあり（通常は大きくない）、対応するMeshもメモリにずっと存在する（これも大きくない）ことです。
>
上記の方法は参考用であり、パフォーマンスへの影響は特定の状況によって異なります。

---
**Q2: 図に示すように、UIを開いたり、どこかに移動したりすると、CPUにインパルスが発生することがよくあります。さらに観察すると、Instantiateが大量のGCを生成することがわかります。InstantiateはGCを生成すべきかどうかを確認したいです。アセット制作の調整を通じてそのようなGCを回避できますか？下の図に示すように、数MBのGCを一回で生成すると、非常に直感的であります。**
![3.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/afb0e81b-0a8b-0562-7c48-dd665aa6ae47.jpeg)
>正確に言えば、これらのGC.Allocは、Instantiateが直接引き起こされるのではなく、インスタンス化されたコンポーネントがOnEnable操作を実行し、GCがOnEnable操作で生成されるためです。
>
上図にある関数を例としたら、Text.OnEnableは、１つのUIインターフェイスをインスタンス化する時に、UIにあるテキスト（テキストコンポーネント）がOnEnable操作を実行します。主に初期化テキストメッシュの情報（各テキストが配置されているメッシュ頂点、UV、頂点色などの属性）であり、これらの情報は配列（Monoメモリ）に格納されるため、テキストが多いほど、Monoメモリのコストが大きくなります。しかし、これは避けられないことであり、発生回数を最小限に抑えることしかできません。
>
ですから、Instantiate/Destroyを介して頻繁に切り替えられるUIインターフェイスを処理することはお勧めしません。代わりに、SetActive（true / false）を使用すること、またはMonoメモリのコストを繰り返されないために、UIを直接移動することをお勧めします。

---
##四、ローディング相関
**Q1: UIプレハブをロードするときに、特殊効果をプレハブに配置すると、ローディングに時間がかかります。 このローディング時間を最適化する方法は？**
>UIと特殊効果（パーティクルシステム）のローディングコストは多くのプロジェクトにより高いCPU時間を占めています。UIインターフェースのインスタンス化とローディング時間は、主に次の側面で構成されています。
>
1、テクスチャアセットのローディング時間コスト
>
UIインターフェイスの主な時間コストであります。前の記事[「Unityモジュールのローディング詳細解析（テクスチャ編）」](https://blog.jp.uwa4d.com/2020/02/26/unity%e3%83%a2%e3%82%b8%e3%83%a5%e3%83%bc%e3%83%ab%e3%81%ae%e3%83%ad%e3%83%bc%e3%83%87%e3%82%a3%e3%83%b3%e3%82%b0%e8%a9%b3%e7%b4%b0%e8%a7%a3%e6%9e%90%ef%bc%88%e3%83%86%e3%82%af%e3%82%b9%e3%83%81)で詳しく説明したように、アセットがロードする際に、高解像度のAtlasテクスチャが常に伴っています。これに対して、UIインターフェイスのローディング効率を上げるために、アート品質が許す限りにUIテクスチャをできるだけ単純化することを開発チームにお勧めします。
![5−1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/620c4bf9-e44a-b0c1-5381-ca90849d8355.png)
>2、UIメッシュの再構築時間コスト
>
UIインターフェイスがインスタンス化またはActiveすると、常にCanvas（UGUI）またはPanel（NGUI）のUIDrawCallの変化を引き起こし、更にメッシュの再構築操作をトリガーします。CanvasやPanelのメッシュ数が多いと、再構築コストも高くなります。
>
3、UI関連するコンストラクタと初期化操作の時間コスト
>
この部分は、UIの最下層がインスタンス化されたときのctorコスト、またはOnEnableやOnDisable自身のコストです。
>
上記の2と3は、主にエンジンまたはプラグインの自身のロジックコストであるため、この二つの操作の発生頻度を出来る限りに回避及び低下する必要があります。UWAからの提案は下記のように。
>
1、メモリが許せば、UIインターフェースをキャッシュします。UIインターフェイス関連アセットの繰り返しのロードと関連クラスの繰り返しの初期化を最小限に抑えます
>
2、UIインターフェースの使用頻度に応じて、より適切な切り替え方法を使用します。たとえば、移動またはCulling Layerを使用してUIインターフェースの切り替え効果を実現します。そうすると、UIインターフェースのローディング時間コストを低下でき、切り替えのスムーズさを向上させられます。
>
3、特殊効果（特にパーティクルエフェクト）の場合、UIインターフェイスと特殊効果を組み合わせると、読み込み時間が2つを別々にローディングのに費やした時間の合計よりも長くなることはわかりませんでした。 したがって、パーティクルシステムのローディング効率を最適化するという観点からのみこの質問に答えます。
>
パーティクルシステムのローディングコスト、現時点では、主にこれ自身のコンポーネントの逆シリアル化時間コストとローディング数に関します。逆シリアル化時間コストに対して、これはUnityエンジンのパーティクルシステム自体のローディングコストであり、開発者がコントロールできる部分はあまり多くないです。逆に、ローディング数は私たちが注意すべき点であります。今まで私たちが参加したプロジェクトの中に、多くのプロジェクトで多数のパーティクルシステムがロードされており、下の図に示すように、1,000を超えるプロジェクトもあります。したがって、自分のプロジェクトのパーティクルシステムの使用状況を注意することを開発チームにお勧めします。一般的に、パーティクルシステムの最大数を400未満に制御することをお勧めします。

---
**Q2: アトラスを使用するUIプリセットがあり、パッケージ化するときに、アトラスとUIを一緒にAssetBundleとしてパッケージしました。ロードしてGameObjectを生成したらすぐにAssetBundleオブジェクトをアンロードしましたが、後でGameObjectを破棄すると、アトラスがまだ存在していることがわかります。これはどの状況ですか？**
>これはよく見られる状況です。unload(false)を通してAssetBundleをアンロードしても、ロードされたアセットは破棄されません。Resources.UnloadAssetまたはResources.UnloadUnusedAssetsをコールする必要があります。
>
AssetBundleローディングの詳細な説明に関しては、前の記事[「知っておくべきAssetBundle管理の原理」](https://blog.jp.uwa4d.com/2020/11/13/%e7%9f%a5%e3%81%a3%e3%81%a6%e3%81%8a%e3%81%8f%e3%81%b9%e3%81%8dassetbundle%e7%ae%a1%e7%90%86%e3%83%a1%e3%82%ab%e3%83%8b%e3%82%ba%e3%83%a0)を参照してください。

---
##五、フォント相関
**Q1: Profiler実機でiPhone Appを確認すると、一部のUIを開く際に、Font.CacheFontForTextが２秒以上の時間をかかります。これは主に何かが影響していますか？問題ありませんか？このコストは生成したRenderTextureのサイズに関係ありますか？**
>Font.CacheFontForTextは主に、動的フォントFont Textureを生成するコストであります。一度に開くUIインターフェイスにあるテキストが多いほど、コストが大きくなります。２秒以上の時間がかかると、確かに少し大きいです。このコストは、生成したFont Textureにも関係あります。簡単に言うと、主に現在のFont Textureに次のテキストを収容できる場所があるかどうかを確認し、収まらない場合、さらにFont Textureを拡大して、パフォーマンスコストを引き起こします。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com