@natsu1211

ゲーム開発、プログラミング言語の話

uGUIの内部実装を完全理解する(1) - 全体構成

なぜ今さらuGUI

uGUIはUnity 4.6で導入されて以来、UnityでランタイムUIを作成するための標準ツールとなりました。現在、Unityは新しいUI作成ツールUI Toolkitをリリースしていて、UI Toolkitの習得もますます重要になってきています。しかし、UI Toolkitの公式の位置付けは依然としてエディターUIの作成が主であり、しばらくの間、uGUIはゲーム内UI制作の主要なツールとして、UI Toolkitと共存することが予想されます。

まだまだ絶賛開発中のUI Toolkitとは違い、uGUIはすでに成熟した技術で、コードベースが大きな変更されることはもうないでしょう。ソースコードリーディングするにはむしろ好都合です。UI Toolkitの思想はWebフロントやWPFに近いですが、uGUIはより従来のUIコントロールベースの考えに近いUIフレームワークです。ここでuGUIとUI Toolkitを比較したいわけではないですが、数年間重度にWPF使っていた私からして、(エディターUIと比べて)比較的にシンプルなゲーム内UIを作る場合、ちゃんとUXML/USSを作りデータバインディングをやるより、uGUIで充分というかむしろ柔軟だと感じています(逆に複雑になりがちなエディターUIを作る場合、IMGUIより断然UI Toolkitがおすすめ)。2つかなり思想が異なるUIフレームワークですが、入力とEventに対する応答処理など、共通する部分もあります。実際のゲーム開発でより複雑なUIの作成やパフォーマンスの最適化に役立つことはもちろん、UI Toolkitの勉強の手助けにもなると考えています。

本当は数年ぶりに仕事でがっつりuGUI触るから、uGUIを振り返ってみようなんて言えません・・・

uGUIの構成

uGUIはピュアC#で書かれており、Unity UIというパッケージを通じて配布されています。このシリーズでは、Unity UI 1.0.0バージョンのソースコードを徹底的に分析します(changelogによると、1.0.0から2.0.0への変更はTextMeshProが同じパッケージに統合されただけで、他の部分に変更はありません)。

Unity UIパッケージ(Runtime部分)の中身は以下のようになる、

UIフレームワークとして、少なくとも以下の問題を解決する必要があります。

  • UIコントロールをどう提供し、レンダリングするか
  • ユーザーの入力にどう応答するか
  • 複数のUI要素をどうレイアウトするか

uGUIも例外ではなく、上記の問題に対する解決策を提供しています。 そのため、このシリーズではuGUIのソースコードを以下の3つの部分に分けて解説します。

ビジュアルコンポーネントレンダリング

Unityではシーン内のすべてのオブジェクトはGameObjectであり、異なるコンポネントをつけることで異なる機能を実現します。ここで言うビジュアルコンポーネントとは、ImageやTextなどのScriptのことで、これらのScriptコンポネントをGameObjectにつけることで、ビジュアル内容を表示する機能を実現しています。Image、Textなどのコンポネントは、抽象クラスGraphicを継承しています。Graphicクラスには、meshやmaterial情報など、UIをレンダリングするために必要なさまざまな情報が含まれています。ここで一つ重要なのは、UIは2次元に見えますが、Unityでは全部3次元のmeshによって構成されています。

実際のレンダリングは、主にCanvas、CanvasRenderがUnityエンジンの描画部分と協力して行われます(エンジンのランタイムがさらに低レベルなグラフィックAPIを使い、GPUレンダリングしてもらいます)。CanvasとCanvasRenderはC++で実装されたコンポーネントで、uGUIに属しているわけではなく、Unityエンジンのランタイムの一部です(そのため、namespaceはUnityEngine.UIではなくUnityEngineです)。uGUIでは、CanvasのユティリティクラスであるCanvasUpdateRegistryを提供しており、Canvas内の各Graphicクラスのプロパティが変更された際に、正しくRebuild(レイアウトの再計算とUIの再描画)が行われるようにしています。

入力の応答

ImageやTextなどのビジュアルコンポーネントの他に、ButtonやToggleなど、ユーザーの入力に応答できるコンポーネントもあります。ユーザーの入力は、Unityエンジンのランタイムによって一連のEventに変換され、これらのEventはEventSystemとInputModuleを通じてDispatchされます。実際イベントを処理する対象は、ユーザーがクリック/タッチした点からRaycastの処理によって見つかり、登録されたイベントハンドラが呼び出されます。

UI要素のレイアウト

uGUIでは、基本的なレイアウト機能を実現するためにRectTransformコンポネントを提供しています。UIのmeshをどの位置にどれぐらいの大きさで描画すべきかRectTransformによって決まるため、GraphicクラスもRectTransformに依存しています。しかし、RectTransformもCanvasと同様にC++で実装されたコンポーネントであり、uGUIの一部ではなく、オープンソースでもありません。より複雑なレイアウトをするために、uGUIはLayoutGroupなどのコンポーネントを通じて自動レイアウト機能を提供しています。

また、uGUIは各種コンポーネントに専用のEditorインターフェースも提供しており、エディター内でコンポーネントの各種プロパティを編集できるようにしています。Runtime部分のソース読みが完了した後、Editorの実装についてもある程度解読していく予定です。

以下はuGUIの全体クラス図です。すべてのクラスとインターフェースが含まれているわけではありませんが、ソースコードの構成と照らし合せれば、各クラスの役割のイメージが掴めやすくなると思います。

今後のシリーズ記事では、ここに示された各クラスの実装について分類して説明していきます。