テラシュールブログ

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

【Unity】Unityでプレゼンテーション資料を作る

f:id:tsubaki_t1:20180503001307j:plain

今回はUnityでKeynotePowerpointにあるようなプレゼン資料を作るプロジェクトの紹介です。

 

 

もはやPowerPointに切り替える必要はない…

説明会や勉強会等で資料を作成すると、よく「KeynotePowerPoint)とデモ用の何か(自分の場合はUnityエディター)の切り替え」を行います。
これが中々の曲者で、一旦Keynoteをプレゼンモードから戻すと現在進行中の時間がリセットされ、のこり時間を見失う事があります。発表中で上がっている時や、時間がズレてる時は特に。

 

他にもPowerPointとUnityエディターを行ったり来たりすることで、誰かが何かの問題を起こすかもしれません。これはとても難しい問題です。

そこでvalyard氏は考えました。

 

Q:UnityエディターとPowerPointの切り替えで起こるトラブルを回避するには?

A:UnityがPowerPointになればいい

 

そうだ、Unityでプレゼン資料を作ろう

このブッ飛んだ考えで作られているのが、Presentation Project (Unity)です。
キャッチコピーはNo need to switch to Power Point anymore.

f:id:tsubaki_t1:20180502233205g:plain

github.com

考え方は単純で、UGUIのレイアウト機能を使えば資料ぐらい作れるよね!という物です。1シーンを1スライドと捉えて、それを切り替えることでプレゼン資料を実現しています。
このプロジェクトで提供されている物は、この「1スライド1シーン」を順番に切り替える&ロードする仕組みです。

f:id:tsubaki_t1:20180503000046g:plain

一応ビルドすることも出来るので、WebGLとかでも動くんじゃないですかね(多分

 

使い方を見てみる

とりあえず試しに使ってみました。

f:id:tsubaki_t1:20180503000140g:plain

 

Window > Presentationでメニューを開きます。

f:id:tsubaki_t1:20180502235358j:plain

あとは+でプレゼン資料のページを増やして、シーンを登録していきます。シーンは自分で普通のエディター操作で作ります。
何か最初から色々とセットされていたら、左上のNewボタンとか押しておけばOKです。

f:id:tsubaki_t1:20180502235436j:plain

あとは右上の>Bボタンを押せばプレビューが出来ますし、Stopボタンでプレビューを停止できます。
「再生」が入っていると、その資料を開くときにエディターがシーンを再生してしまうので、文字だけのページの場合は外しておくと良いかもしれません。

f:id:tsubaki_t1:20180502233928j:plain


発表者ノートは?プレゼンの残り時間の確認は?

ノーコメント。

 

その他

・・・確か自分が知ってる範囲で2件ほど同じ様な事をやってるプロジェクトがあった気がするけど思い出せない…

動かしながらプレゼン出来るのは面白いかも

【Unity】独自に作成したスレッドの処理時間をCPU Profiler(Timeline)で確認する

まんまタイトルの通りで、.NETで作成したスレッド上の処理時間をProfilerに乗せる方法についてです。

 

 

Profilerで負荷のかかっている場所を見つける

処理負荷や処理時間を知るのに、Profilerは非常に有用なツールです。
パレートの法則で言われるように、大抵の負荷は2割がボトルネックになっている事が多く、Profilerはその2割を見つけるのに有効です。

f:id:tsubaki_t1:20180430232723j:plain

さて、Profilerに処理時間を乗せたい訳ですが、特に何も設定しないならばMonoBehaviour.UpdateやECSのSystemといった単位でCPU Profilerに計上されます。
これでは「◯◯コンポーネントが遅い」事は解っても、何が問題とはハッキリ出来ません。

そこでProfiler.CustomSamplerで特定の処理時間をピックアップします。
サンプルとしてVector3を最後にApplyするだけのモノと、transformを取得してユニットを動かす場合、transformをキャッシュして行う場合の処理時間を見てみます。

gist.github.com

結果はProfilerのTimelineで確認できます。確認すると、Vector3だけ取り出しておいて最後に反映させるモノが、Transformに毎回数値を入れてるモノよりとても速い事が確認出来ます。
transformのposition等を上書きするだけでもソレナリのコストが発生していることです。あとgameObject.transformが内部的にキャッシュされるというのは迷信だと予想出来ます。

f:id:tsubaki_t1:20180501132033j:plain

ちなみにBeginSampleではなくCustomSamplerを使用しているのは、こちらのほうがオーバーヘッドが少なく、正確な値が取れるからです。

 

非同期処理とProfiler

負荷を計上するProfilerですが、実はスレッドが関わってくると途端に面倒になってきます。
C# Job SystemであればC# Job System側である程度の処理時間がProfilerのTimelineに出力されるので、そこまで致命的な問題にならないのですが、.NETの機能で作成したThreadはUnityのProfilerに計上されない為、どの程度の時間がかかっているのかが把握しにくくなっています。
またBeginSampleはメインスレッド上でしか動作しません。

 

この問題を解決するのが、Profiler.BeginThreadProfilingです。このAPIで囲った範囲のsampler.Begin~sampler.Endの範囲はProfilerのTimelineに乗るようになります。
例えば下のようなコードを記述すると、Profilerから処理時間が確認できます。

gist.github.com

f:id:tsubaki_t1:20180501135619j:plain

またsampler.Beginの引数にgameObjectをセットすれば、Profiler Timelineに計上される処理時間とHierarchy上のGameObjectを紐付ける事が出来ます。

 

複数フレームを跨げる

この処理時間は複数のフレームを跨いでもOKで、特にコルーチンのような遅延処理に対してThreadを使用する場合には有用そうです。
ただバージョンによっては載らない事もあるかもしれません。以前はsampler.Endまで到達した処理が無ければ載らなかった気がするので(Unity 2018.1f1で確認)

f:id:tsubaki_t1:20180501220611j:plain

逆にCPU ProfilerのHierarchyに処理時間が載らない点ので、その勘違いは気をつける必要があります。

 

関連

async/awaitのコードはココのを真似させてもらいました。

satoshi-maemoto.hatenablog.com

samplerについての説明。BeginSamplerは止めよう。
どちらかと言えばProfilerで見るような物をゲーム画面に乗せられる方が重要

tsubakit1.hateblo.jp

Profiler便利便利。

tsubakit1.hateblo.jp

【Unity】ECSを使用した大規模なデモプロジェクト "Unite Austin Technical Presentation"

f:id:tsubaki_t1:20180427001949j:plain

簡単なサンプルではなく、ECSを大規模に使用した技術デモ「Unite Austin Technical Presentation」の紹介です。

 

 

Unite Austin Technical Presentation

Unite Austin Technical Presentationは、Nordeus社が作成したゲームの一部プレビューです。内容は大量のミリオン同士の戦い…という感じです。

実際、数万体*1のミリオンがワールドの中央付近で戦闘を繰り広げています。

f:id:tsubaki_t1:20180427010109g:plain

また1キーや2キーでワールドの中身に介入することも出来ます。
例えばマウスの位置に隕石?を落下する攻撃を打ち込むと、大量のミリオンが一斉に吹き飛ぶのが確認出来ます。

プロジェクトは以下のGithubから入手出来ます。サイズがちょっと大きくて、3.3GBくらいあるので、モバイルWifiから落とす場合には注意が必要です。帯域制限を喰らいます(体験談

github.com

 

ユニットの動作や攻撃にはECSが使用されている

このゲームですが、キャラクターの移動や攻撃、キャラクター表現等、多くの部分にECSが使用されています。

キャラクター毎にEntityが割り当てられ、キャラクターの一体一体が各々で動いています。ミリオンだけでなく、弓の攻撃も一本一本がEntityです。

f:id:tsubaki_t1:20180427012516j:plain

ステージとの連携は、ふっ飛ばされた時などの物理挙動にはRaycastCommandで地面との相対距離等が求められていますが、移動はもっぱらNavMeshQueryでパスを取得してパスの通りに移動するというものみたいです。

 

逆に、判定やレンダリングなど、ユニットの動作に関する部分は多くの部分が自作コードになっていました。これは単純に用意されてないというのもありそうですが、幾つかは特別な最適化を施したいというのも理由の一つかもしれません。
例えば以下のような感じ

  • 当たり判定(判定の精度を下げたい)
  • レンダリング(DrawMeshIndirectで一括表示したい)
  • キャラクターのAI

f:id:tsubaki_t1:20180427010640j:plain

これらの処理は大まかな部分をSystemはJobSystemに移譲しており、ワーカースレッドはソレナリの範囲を埋めています。

特に長いのはMoveJobで、中でNavMeshQueryでパス計算とかしてます。

f:id:tsubaki_t1:20180427011010j:plain

 

キャラクターアニメーションは頂点シェーダー

さて、このプロジェクトは数万体という大量のユニットを同時に表現するためにDrawMeshIndirectでキャラクターを描画しています。
とは言え、DrawMeshIndirectで描画するものは基本的にアニメーションを使用できません。その辺り、このプロジェクトでは、頂点シェーダーでポリゴンを動かすことで実現しています。

f:id:tsubaki_t1:20180427015251g:plain

基本的には Animation Texture Baker と同じ系統の技術のようですが、テクスチャをベイクするというよりはバッファを云々してるように見えます。(ちゃんとコードを読んでいない)。このバッファを塗るのに、UnsafeUtilityもガンガン使うという中々に面白くないコードになっています。

 

 なお、何故かTextureAnimationSystemはComponentフォルダ以下にあります。多分ミスです。Systemフォルダに無かったので焦りました。

f:id:tsubaki_t1:20180427020546j:plain

 

状態の切り替えはComponentDataの追加で

ユニットには複数の状態が設定されています。例えば「移動中」とか「攻撃中」とか、あとは「死亡中」とか。

この状態の切替は、ComponentDataの抜き差しで行っていました。

f:id:tsubaki_t1:20180427025041p:plain

ComponentDataの抜き差し(AddComponent/RemoveComponent)は、ComponentDataが所属するチャンクを移動する事になるので余り効率的では無いように思ってたんですが、各動作毎に要求するデータが大きく違うので、その辺りを嫌がったのかもしれません。

まあチャンク間の移動といっても、数箇所の要素を塗り直すだけですし(新しくチャンクが作られなければ)案外問題は無いのかもしれません。

f:id:tsubaki_t1:20180427030732j:plain

 

ユニット情報はPrefabから取得

ECSを使用しているので殆どがコードで書かれるように思っていましたが、ユニット情報等はECSハイブリットの構造を使用して取得していました。
Prefab(GameObject)をInstantiateするのではなく、PrefabからEntityを作ってEntityをInstantiateする感じです。

これでコードを弄る事なく、複数パターンのEnittyを作成しています。

f:id:tsubaki_t1:20180427022246j:plain

このコードを見てEntityManagerのInstantiateにGameObject割り当てられる事に気づきました。

確かにこの使い方は理にかなってるように感じます。まぁ最終的にはScriptableObjectかEntityをシリアライズしたものに纏めてほしい気もします。

 

ユニットの生成位置はMonobehaviourから取得

ハイブリットと言えば、ユニットの生成位置等の情報はMonoBehaviourを継承したコンポーネントから取得していました。
出現位置などの情報はGameObjectでシーン上に配置(Gizmoでプレビュー)し、実際にUnitを生成するのはComponentSystemで行っている感じです。

f:id:tsubaki_t1:20180427032356j:plain

 

関連

このプロジェクトの紹介です

www.youtube.com

ECSとGameObjectのハイブリット

tsubakit1.hateblo.jpECSについて

tsubakit1.hateblo.jp

このプロジェクトのフォーラム

https://forum.unity.com/threads/unite-austin-technical-presentation.522628/

*1:ゲーム開始時は1万くらい、放置すると2万3万と増えていく

【Unity】C# Job Systemを使う時によく見るエラー

今回はC# Job Systemを使ってるとよく見るエラーについてです。

 

 

A Native Collection has not been disposed, resulting in a memory leak.

Googleで日本語にすると以下のような訳になります。

ネイティブコレクションが破棄されていないため、メモリリークが発生します。

 このエラーは内容の通りで、NativeArray等を生成した後に後片付けしてない場合に発生します。
Awake/OnDestroyやOnEnable/OnDisableのように対応した環境でちゃんと破棄しないとコレが出るので注意が必要です。

破棄はDestroyではなくDisposeです。

gist.github.com

JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak

Googleで日本語にすると、以下のような訳になります。

JobTempAllocは4フレーム以上古い割り当てを持っています - これは許可されておらず、漏れそうです

これはAllocatorがTempやTempJobに設定したNativeArrayが、複数のフレームを跨いだ時に発生します。

返却期限を超えないようにNativeArrayを破棄しておけばOKです。

gist.github.com

nvalidOperationException: The previously scheduled job AnyComponent:AnyJobName writes to the NativeArray AnyJobName.anyfield.

Googleで日本語にすると、以下のような訳になります。

nvalidOperationException:以前にスケジュールされたジョブAnyComponent:AnyJobNameがNativeArray AnyJobName.anyフィールドに書き込みます。

 これは少しややこしくて、複数のJobが同一のNativeArrayにアクセスしていた場合に発生します。
普通はNativeArrayに同時に書き込んだり、順不同で書き込んだ時になにかデータがおかしくなる的なバグなのですが、C# Job Systemでは複数のジョブから同一のコンテナにへ書き込む可能性がある実装に対してエラーを出すようになっています。要素単位ではない点に注意。*1

f:id:tsubaki_t1:20180425234951j:plain

この「書き込むか、書き込まないのか」については、[ReadOnly][WriteOnly]属性で判断しています。また、どちらの属性も無い場合には「書き込むし読み込む」という状態になります。

 

つまり、この問題を解決するならば、明確に同時にアクセスしないように定義する、もしくはジョブが同時に実行されないようにするが回答となります。

 

例えばReadOnlyを設定して明示的に同時アクセスしないようにする、

f:id:tsubaki_t1:20180426000533j:plain

https://gist.github.com/tsubaki/862adcd1d5922ac2dfddd25bed8b0f97#file-anycomponent-cs-L46

 

またはスケジュールを発行する際に依存関係を設定して、同時に実行しないようにする…といったモノが考えられます。

f:id:tsubaki_t1:20180426000747j:plain

https://gist.github.com/tsubaki/776470e2067ff6e542d884853c4e55f3#file-anycomponent-cs-L32

 

なお、Jobを使い回す(参照先のNativeArrayだけ書き換えてScheduleを発行)のような場合にもこのエラーが出ます。

 

InvalidOperationException: The native container has been declared as [ReadOnly] in the job, but you are writing to it.

[ReadOnly]を設定したコンテナにアクセスしたオブジェクトに書込が発生した場合にエラーとして出力されます。

注意点として「書込が発生したら」エラーになるらしく、ビルド時には分かりません。

 

InvalidOperationException: The native container has been declared as [WriteOnly] in the job, but you are reading from it.

[ReadOnly]を設定したコンテナにアクセスしたオブジェクトに書込が発生した場合にエラーとして出力されます。

こちらも「読込が発生したらエラー」になります。

 

例えば writeOnlyData += n; みたいなコードを書くと発生します。

 

InvalidOperationException: AnyJob.anyField is not a value type. Job structs may not contain any reference types.

JobSystem内で参照型のオブジェクトにアクセスした場合に発生します。例えば JobのフィールドにComponentを登録して、アクセスしようとしても出来ません。

 

対策は「参照型クラスを使うな」です。C# Jobsystemでは基本的にNativeArrayに結果を格納して、それを料理してもらう形になります。

 

FindObjectFromInstanceID can only be called from the main thread.

Unityのコンポーネントインスタンスにメインスレッド以外からアクセスしようとすると発生します。

 

IndexOutOfRangeException: Index 0 is out of restricted IJobParallelFor range

IJobParallelForを使用した場合に、indexで指定された要素以外の要素にアクセスが発生した発生します。実際に書き込まれるのか等は関係なくエラーになります。

対策としては、明示的にReadOnlyを付けて書込が発生しないようにすればOKです。

f:id:tsubaki_t1:20180426004000j:plain

https://gist.github.com/tsubaki/af86f8891780dd4c692e04b7b1352165

 ArgumentException: System.Boolean used in NativeArray<System.Boolean> must be blittable

NativeArrayに使用できるのはblittable型のみです。boolは非blittable型なので使用できません。
なので、int型にして 0 or 1 みたいな懐かしい感じのことをする必要があります。

Blittable 型と非 Blittable 型 | Microsoft Docs

関連

github.com

tsubakit1.hateblo.jp

www.slideshare.net

*1:要素単位の場合はJobParallelForを使う