テラシュールブログ

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

「Unityテストを完全に理解した」の動画とスライドが公開

f:id:tsubaki_t1:20180608093128j:plain

先日行われた「Unityテストを完全に理解した」の動画とスライドが公開されてました。

Unity テスト完全に理解した - connpass

 

動画

動画はこちら。動画では少しスライドが見にくいので、スライドは別に見るのをお薦め

www.youtube.com

各セッションのスライド

Unityでどのようにテストを導入していくか…という話です。

speakerdeck.com

speakerdeck.com

SOLIDの設計について

speakerdeck.com

関連

monry.hatenablog.com

【Unity】ECSのメモリレイアウトとその周辺

今回はECS(Entity Component System)とはどういった機能かといった話について書いていこうと思います。
この情報はハッキリ言えば知らなくても問題ない情報なのですが、知っておくとECSを採用するべきケースや作り方に迷ったときに助けになるかもしれません。

 

なお、ECSの概念や使い方については、Unity 2018のEntity Component System(通称ECS)について(1) Unity 2018のEntity Component System(通称ECS)について(2)で紹介しています。
実際の使い方イメージはECSで簡単なゲームを作ってみたので、その解説 です。

 

 

メモリ転送速度がCPUのボトルネックになる世界

ここ数年でCPUの性能は飛躍的に向上し、またマルチコア等の複数並列同時計算能力も手に入れました。それに対してメモリの速度はそこまで高速化しておらず、メモリの転送速度がCPU性能を引き出す上でのボトルネックになっている事が多々あります。

 

例えばメインメモリからデータをコアに渡す場合、その読み込みを行う間にコアでは200サイクル以上もの処理を行えます。もし処理が完了したならば、情報が届くまでの間は待ち時間が発生します。
この辺りのギャップを無くすため、大抵のCPUにはL1キャッシュやL2キャッシュといった高速な(しかし非常に小さい)キャッシュが存在します。このキャッシュに乗っている情報の取得は3~4サイクルと非常に短い時間で取得出来るので、毎回メインメモリから取得するのに比べて圧倒的に効率的です。

f:id:tsubaki_t1:20180605230619j:plain

 

また普通は必要なデータを1バイトずつ転送するのではなく64バイト程度のメモリを一気に転送します。1バイトずつ転送したり必要な分を抽出する手間よりも一気に転送してしまったほうが高速という形です。

このため、もし必要なデータが例え1バイトしかなくとも、63バイトの無関係なデータと共にロードされます。

f:id:tsubaki_t1:20180605231249j:plain

 

オブジェクト指向はCPUに優しくない

オブジェクト指向コンポーネント指向は、オブジェクトに処理やデータを格納し、内部でデーややステートを制御するアプローチです。このオブジェクトは各々が独立しており、基本的に単体で完結出来るのが理想です。
またオブジェクト毎に独立した形でメモリに展開されており、参照という形で各々のオブジェクトにアクセス出来るようになっています。

この設計はメモリの空きを作らないという点や設計しやすい点で非常に優れているのですが、キャッシュという観点から見ると少し良くないです。
各オブジェクトはメモリ上に散らばる形で配置されるため、メインメモリからデータを取り出す時に何度もメインメモリから転送する事になります。

f:id:tsubaki_t1:20180605232441j:plain

なお正しくはオブジェクト指向が悪いというよりは、オブジェクト指向だと大量のデータを操作するときに効率が悪くなりやすいという話です。
なので、少ないオブジェクトだったらソコまで気にしなくても良いと自分は思います(そう言っても大抵は最終的にオブジェクトの量が増えることが多いのですが)

 

その名はデータ指向設計

キャッシュに優れた場合のメモリレイアウトはどういったものか?
簡単に言えば、データが固まっているものです。

1回64バイトで可能な限りデータをロードしてキャッシュに載せられれば、以降のデータ読み込みはL1キャッシュから行われるようになります。
これでコアの待ち時間が殆どなくCPUは効率的に処理を実行出来るようになります。

f:id:tsubaki_t1:20180605235806j:plain

これでアクセスが高速化出来る訳ですが、この高速化を十分に活用する為には一つの処理中に様々なデータにアクセスするのではなく、アクセス対象を決めて一気に処理してしまうのが効率的です。

これは並列処理した場合でも同様で、むしろデータ構造的に並列処理しやすいといったメリットもあります。

f:id:tsubaki_t1:20180606125316j:plain

このようなメモリレイアウトの設計やデータのアクセス方法に着目した設計をデータ指向設計と呼ばれます。

 

UnityのECSはデータ指向設計

UnityのECS(Entity Component System)はC#上で実装されたデータ指向設計(Data Oriented Design)です。

 

利用者側から見た場合、Entityとはコンテナです。EntityにComponentDataを追加したり削除する事で、Entityの振る舞いを作ることが出来るようになっています。
その点で見ると、Entity=GameObjectでComponent=Monobehaviourといえるかもしれません。この見た目は人類に優しいです。

f:id:tsubaki_t1:20180605234712j:plain

実際には下のように、ComponentData毎の連続したデータでありEntityはタダのIDでしかありません。Entity2と要求すればA[2]とB[2]、C[2]に簡単にアクセス出来るという感じです。
この辺りはEntityManagerが管理してくれています。

f:id:tsubaki_t1:20180605234922j:plain

SystemはComponentDataの配列にアクセスします。
実際には後述するもう少し面倒な話があるのですが、何にしてもメモリはある程度まとまった所にあるので一気にキャッシュに乗せて一気に処理するといった事が可能になっています。

f:id:tsubaki_t1:20180605235141j:plain

 

アーキタイプとチャンク

ECSのメモリ管理の説明をする上で外せないのが、チャンクとアーキタイプの2つの要素です。基本的にEntityは任意のアーキタイプを持ち、特定のチャンクに格納されます。この辺りは混乱しますよね。えーと、

 

まずアーキタイプとはその名の通りEntityの雛形です。Entityはアーキタイプを元に作成します。またEntityのメモリレイアウト等もココに格納されています。

チャンクは実際にComponentDataを格納するメモリ空間を管理するものです。チャンクは必ずドレかのアーキタイプに所属していて、同じアーキタイプのEntityしか格納しないようになっています。
同じアーキタイプしか格納しないので、もしEntityにComponentDataを足したり減らしたりすると、Entityはアーキタイプが一致するチャンクへ移動します。

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

このチャンクですが1チャンク毎に64KByteの連続したメモリを確保します。ここで確保し利用するのはメモリはネイティブなメモリです。
メモリ確保後は確保したメモリを塗るだけでオブジェクトの増減が表現出来るのでかなり低コストに生成・削除が実現出来る訳です。
また64KByteを使い切ると、新しくチャンクを増やす形で対応します。

ComponentDataArrayは複数のチャンクを一つの配列に見せる

チャンクは複数のアーキタイプ・そして64KByte毎に生成されています。要するに、「特定の種類のComponentData」を扱いたい場合には飛び飛びになってしまっています。

この辺りを使いやすくしてくれているのがComponentDataArrayです。ComponentDataArrayは一見連続した配列のようなインターフェースを持っているのですが、実際にはチャンク毎の「ComponentDataのポインタ」と「要素の数」を持ったリストのようなものです。

要素を取得する場合、下のような処理が行われているっぽいです。

  1. チャンクの先頭のComponentDataを取得
  2. 要素の分だけ横にズラしつつデータを取得
    (連続してるのでキャッシュも効く
  3. 要素を読み終わったら次のチャンクへ(1へ戻る

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

似たような形でShardComponentDataArrayというものがありますが、こちらは少し異なります。

そもそもShardComponentDataは複数のEntityで一つのShardComponentDataを共有する機能です。そのため、チャンクの中に含まれるのはShardComponentDataの実体ではなくアクセス用の何か(たぶんハッシュ値みたいです。
なおShardComponentDataを含めると値の種類毎にチャンクが分割されます。そのためユニークなShardComponentDataが大量に有る場合、効率が激減する可能性があるので注意が必要です。

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

何にせよ、ComponentDataArray系をunsafeで触ると面倒なことになるかもしれません。

 

ComponentGroupによるフィルタリング

ComponentGroupは特定のComponentDataArraをフィルタリングして特定のComponentDataを持つ物のみのリストを作ってくれる便利機能です。

一致する要素毎に繋いでリストにする感じと予想してますが果たして。

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

なおComponentGroupのタイミングはフレームの最初か最後らへんと聞きましたが果たして。普通にオブジェクトを足したタイミングでチェックしてる印象があるんですが、うーん。

ちなみにフィルタリングコストは結構安いらしいです。これならラベル用のComponentData(0バイトの構造体)とかも普通に使って良さそう。

 

チャンク単位の操作

ComponentDataArrayは内部でチャンクの切替えを行っています。これは簡単にアクセス出来る反面、チャンクを跨ぐような処理を行ったり(componentDataArray[i-1]等)何度もチャンクを舐める処理を行うと効率が悪化していきます。
(例えばfor文で同じComponentDataArrayを何度も最初から最後まで舐める等)

こういったケースではチャンク単位の操作を行うことで、比較的効率的な処理が実現出来ます。

tsubakit1.hateblo.jp

 

メモリのレイアウト

メモリは実際には下のようになっているらしいです。一つのチャンク(64k)に同数のComponentDataが入るように、レイアウトが調整されています。
https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20180604/20180604184817.jpg

このComponentData一つ一つはStructなので構造体の配列(AOS)です。現状ECSはこの形で動作しているみたいです。
ただSIMD等に全部お任せする場合の最大効率は配列の構造体(SOA)と呼ばれる形で、その形が使えるようになってほしい所もあります。

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

この辺りはC# Job SystemとNativeArrayの形で見ると、ComponentDataを分解して配列の構造体として再レイアウトするSOAやAOSOA利用オプションが付くっぽいです。
現状C# Job SystemではNativeArraySOAを使用するという形でSOAを使えそうですが、ECSで利用するオプションは不明(未定)です。

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

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

 

Systemの実行順はGroup様を見てる

ECSのSystemの実行順番ですが、Systemが要求するComponentGroupを見て、決定しているみたいです。内部的に依存関係グラフが作られてるとか何とか。
例えばデータの読み込み(ReadOnly)は書き込み(WriteOnly)が終わった後に呼ばれるといった具合です。これで「データを入力し、入力されたデータを使って何かをする」の流れを特に意識せずとも実現出来ています。

f:id:tsubaki_t1:20180606014523j:plain

これはJobComponentSystemも同じっぽいです。UpdateAfter等の明示的に順番を制御する機構も、多分同じ仕組みを使ってます。

 

ECSのソースコードはUnity 2018.2から確認できる

 Unity 2018.2からPackagesから取得したパッケージがProjectビューに映るようになり、ECSの内部コードも確認できるようになりました。

内部でどんな処理が行われているか等が確認出来ます。なおunsafe使いまくりなので面倒さがマッハです

f:id:tsubaki_t1:20180604183751j:plain

感想

ということでECSは何をやってるのか?というお話でした。
確かにECSは面倒ですが、逆を言えばこのレベルの最適化を開発者は殆ど意識せず組めるというのが、なかなかに素晴らしい印象です。

とりあえずコレで組んどけば Performance By Default というのは、なる程という感じ。

 

これでUnity開発でよくある、プロジェクト後半・オブジェクトを配置していく段階で想定外の負荷がどんどん増える…というのは避けられるかもしれません
C#実装部分に関しては)

 

参考リンクメモ

DODについて
Data-Oriented Design (Or Why You Might Be Shooting Yourself in The Foot With OOP) – Games from Within

ECSメモリレイアウトについての議論
https://forum.unity.com/threads/ecs-memory-layout.532028/

【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~Entity Component System 編~
https://www.slideshare.net/UnityTechnologiesJapan/unite-tokyo-2018-training-daycjobsystem-ecscpu-entity-component-system-1

データ指向設計について
http://tech.cygames.co.jp/archives/2843/

ECSとDODの組み合わせ

Nomad Game Engine: Part 2 — ECS – Nikolai Savas – Medium

コンポーネントベースのEntity Systemについて
CppCon2015/Implementation of a component-based entity system in modern C++ - Vittorio Romeo - CppCon 2015.pdf at master · CppCon/CppCon2015 · GitHub

Unityでのデータ指向のアプローチについて

Unity at GDC - A Data Oriented Approach to Using Component Systems - YouTube

Unity at GDC - C# to Machine Code - YouTube

ECSとJobSystemを使うと上がる問題と対処法
[Unity ECS] All of the Unity’s ECS + Job system gotchas (so far)

以下略(メモ残すの忘れてた

【Unity】Unite tokyo 2018の講演動画とスライドが公開

f:id:tsubaki_t1:20180531221217j:plain

Unite tokyo 2018のセッション動画が公開されました。

 

 

セッション動画が公開

タイトル通りで、イベントの講演動画が公開になったみたいです。
セッションのタイムテーブルからYoutubeSlideshareのページへジャンプ出来ます

events.unity3d.jp

f:id:tsubaki_t1:20180531221449j:plain

 

残念ながら、事前に非公開となっていた動画は公開されません。
またセッションによってはスライドがUnity製だったり全編動画だったりで資料が無い物もあります。それらは動画の中で確認するのが良さそうです。

f:id:tsubaki_t1:20180531221727j:plain

 

すごく個人的な感想を言わせてもらえば、英語圏のスライドはライブデモに重きをおいている印象があるので、スライドで満足せず動画を見たほうが良いです。

 

個人的お薦め

Addressableを始めとした新しいワークフローについてです。

www.youtube.comShaderGraphの使い方入門です。

www.youtube.com説明不要

www.youtube.com

関連

Unite Europeの動画一覧。どちらかと言えばキャラクターを推す日本と比較してVR/ARやテクノロジー、ワークフロー周りの話が多いです

tsubakit1.hateblo.jp

【Unity】UnityでOSにインストールされてるフォントを使いたい

f:id:tsubaki_t1:20180530215432j:plain

今回は「OSにインストールされたフォント」をUnityで使用する方法についてです。

 

 

フォントはゲームの第二の顔

フォントは実は結構重要な要素の一つで、AppStoreやGooglePlayで売上ランキングに乗るようなアプリは結構ゲームにマッチしたフォントを使用していることが多かったりします。

また視認性なども大きく異なり、文章が読みやすいフォントや、視認しやすいフォント、ゲームの味として採用されるフォントなど、フォントは結構重要な要素の一つです。

 

f:id:tsubaki_t1:20180530220054j:plain

で、Unityエディターを長年見続けてフォントに見慣れてくると、見慣れたフォントが出てくるだけでウンザリする事があります。Unityは基本的にArialでArialに無いフォントは特定のフォントにフォールバックするので、フォントを変更していないとだいたい同じフォントが使われてしまう訳です。

 

f:id:tsubaki_t1:20180530220841j:plain

f:id:tsubaki_t1:20180530221823j:plain

【Unity道場 2017】伝える!伝わる!フォント表現入門より

 

なるほど!さっそくフォントを変更しよう!!となる訳ですが、ここには一つ注意すべき点があります。ほとんどのフォントにはライセンスが付いてくるという事です。
自分の知る範囲では商業用は兎も角、無料で配布してるOR仕様が可能なフォントは利用する事は出来ますがアプリに同梱する事を禁止してる事が多いです。そういったフォントは基本的に使用できません。

また漢字の収録数も問題になります。幾つかの常用漢字は含まれますが「全て漢字」のとなると含まれていない物が多いです。
フォントに含まれていない漢字は自動的にフォールバックしてシステムフォントが使われてしまうので、途中まで独特なフォントが唐突にゴシックになるなんてことも起こりえます。

f:id:tsubaki_t1:20180530223504j:plain

 

そうだ、全部システムフォントを使おう

良いフォントを使用したいがフォントを同梱するのはアレ…という場合の力技対策で、システムフォントを使用するというアイディアがあります。
つまり、フォントを含められないならOSにプリインストールされてるフォントを使用して文字を表現するというものです。OSのフォントは漢字も揃っている事が多いですし、同梱してないので再配布の問題もありません。
まぁOSに含まれていない事もあるんですが…

 

で、通常はUnity指定のフォントにフォールバックされてしまうのですが、フォールバック先を強引にこちらで指定する方法についてです。

 

手順

まず適当なフォントを用意します。
StandardAssetsのUtilityに何気に幾つかフォントがあるので、ソレでも良いです。

f:id:tsubaki_t1:20180530230622j:plain

次にフォントの中身に何も含めないようにします。

Incl.Font Dataのチェックを外すと「フォントはゲームをプレイする端末にインストールされているものを使う」判断になり、フォントファイルの中身が空になります。
これで元々のフォントが何であれ使用することが出来ます。

f:id:tsubaki_t1:20180530230830j:plain

あとはFont Namesに使用したいフォントを登録します。
複数のフォントを登録する(例えば基本的にRobotoを使用するが一部でSunsのフォントを使用する)といった場合には、カンマで区切ります。なおフォント名は英語で入力します。

下の図ではInpactフォントを使用していますが、Inpactは日本語に対応していないので日本語をMSP明朝にフォールバックしています。

f:id:tsubaki_t1:20180530231800p:plain

フォント名が分からない場合は、下のが参考になるかもしれません。また、Font.GetOSInstalledFontNamesでインストールされてるフォント一覧を確認出来ます。

dekasu.net

フォールバックのやりすぎにご注意

FontNamesに任意のフォントを登録することで、フォールバック(代替で表示)する事ができるようになる訳ですが、一つ注意すべき点があります。

FontNamesに登録したフォントは全てのフォントがメモリに自動的にロードされてしまうので、ココに大きなフォントを沢山指定するとメモリを大きく消費するかもしれません。

もし中国語や日本語、韓国語の全てのフォントを一つのフォントで賄おうとする場合には注意が必要そうです。

 

CreateDynamicFontFromOSFontは使わないの?

今回は割とトリッキーなアプローチを紹介しましたが、似たような機能でCreateDynamicFontFromOSFontという「OSにインストール済のフォントを元にFontを作成する」という正にソレなAPIがあります。

実際コレを使用すればMS PMinchoのフォントをロードして使用するという事も可能です。ただコレはフロートしては面倒な所があって、

  1. フォントを管理する制御システムを用意して、そこにフォントを集約しないとメモリ消費と描画負荷がどんどん上がる
  2. UIにフォントを登録する仕組みが別途必要になる
  3. ゲームを再生しないと動作が確認出来ない

という点があります。

 

フォント管理システムですが、これはフォントが「1フォントにつき1枚のテクスチャ」になる事が問題となります。
例えば2つのフォントがあったとして、片方が「ABC」もう片方が「BCD」と表示した時、2つは「BC」という共通した文字列を持っているのにも関わらずデータを共有せず異なる文字列として扱ってしまっています。
これはフォントのリビルドを回避するのに使えるアプローチですが、特に気にせずガンガンFontを生成すると、色々とメモリがマッハになります。

f:id:tsubaki_t1:20180530233105j:plain

なお、個人的には、この手のアセットが関わるリソースマネージャー系はScriptableObjectに格納するのが良いと思います。参照した地点でデータがロードできて、アンロードもソコまで複雑化しない。

 

WebGLは?

この記事を書いたキッカケですが、Unity WebGLではシステムフォント系の機能が全く使えないので、こういったフォールバックの仕組みは使えません。
Webフォント系も使えないっぽいので、基本的にコンテンツに同梱して使用する必要があります。

関連

文字を表示しようと思ったらTextMeshProも良いかもしれません。BMPフォントなどが代表されるように、印刷したら大丈夫になるフォントは結構あります。

tsubakit1.hateblo.jp文字は大事だと説明してくれる資料

tsubakit1.hateblo.jp文字表示機能についてです

tsubakit1.hateblo.jp