テラシュールブログ

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

【Unity】小さく、軽く、早い、Tiny Mode(Project Tiny)についてのアレコレ

f:id:tsubaki_t1:20181207211531j:plain

コンパクトなUnityゲームを出力するTiny Modeのプレビュー版が公開されました。
ということで、早速触ってみます。

この記事はTiny Mode 0.13.2 previewで作成されています。

モバイルブラウザ向けUnity

Tiny Modeは、モバイルブラウザやインスタントゲーム、アプリ内広告(ゲーム)向けゲームを出力する機能みたいです。

小さく、軽く、早いUnityです。

WebGLのように「モバイル”でも”動く」ではなく、完全にモバイルブラウザのようなコンパクトな環境向けにチューンされる物で、現行のUnityと比較して使用出来る機能が大きく異なります。

どんな物かは、実際にプレイしてもらうと理解が早いです。
かなりサクっと起動します。

f:id:tsubaki_t1:20181207211341p:plain

https://tiny-match3.storage.googleapis.com/index.html

現状使える機能群

Tiny Modeが使用する機能は、完全に1から新しく作成したものを利用しています。だからUnityとは比べ物にならないくらい軽量・軽快に動作するんですが、現状利用できる機能がかなり限定されます。

  • UI
    • UILayout(RectTransform)
    • UI( Image, Button, Toggle )
    • Text & Text HTML
  • Assets
    • Audio
    • 2D( Sprite, Tilemap, Particles )
    • Video(HTML5のフルスクリーン)
  • Physics
    • Box2D, Rigidbody2D
  • Animation
    • Tween
    • Animation
  • Ad

Compatibility Cheat Sheet | Package Manager UI website

また、多くの部分で使い勝手が若干異なります。
例えばTransformからRotationScaleを取り外したり出来ます。

f:id:tsubaki_t1:20181207230141j:plain

必要ない機能は削る事を想定しています。通常のUnityと異なりモジュールがキッチリしているので、不要な機能は削られやすく、また削れば削るほどコアランタイムが小さくなります。
これはプロジェクト単位で設定するみたいです。

f:id:tsubaki_t1:20181207225356p:plain

Previewの現状は完全にHTML5の2D向けですが、将来的にインスタント3DやARにも対応するみたいです。

スクリプトはECSベース

Tiny ModeではGameObject/ComponentではなくECSベースです。 ECSの考え方は、雑に言えば「オブジェクトを集めて一気に処理する」です。

  • 処理を担当する機能(System)がオブジェクト(Entity)を集めて一気に処理を行う
  • 処理を行う対象のオブジェクト(Entity)は、Entityが持っているデータ(ComonentData)の組み合わせで決まる

言ってしまえばコレだけです。
(その他にも色々と面倒な事があるのですが、根底はコレです)

f:id:tsubaki_t1:20181207223953g:plain

Tiny Modeは完全にPure ECSです。

ただし、多くの部分がGUIで制御出来るようになっています。例えばPrefabの利用やComponentDataGUIで作るなど。

またComponentBehaviourというMonoBehaviourに近いモデルのクラスが追加されています。OnEntityEnabledやOnDisableEntityなどECSでやるのが面倒だったイベント処理を担当してくれます。
ただOnUpdateは要らないんじゃないかな。ECSの挙動的に明らかに効率悪いし、マニュアルにも反復処理はComponentSystemを使うべきってなってるし。

f:id:tsubaki_t1:20181207214814j:plain

なお現状処理はTypeScriptで記述しますが、プレビューが外れるまでにTypeScriptからC#になることが確定しています。

TypeScriptは文法的にC#に似ていますが、別物なのでC#来るまで触らないというのは選択肢としてアリかなと思います。
またC#でないとBurstコンパイラが使えないので、最大性能は出ません。なので今の段階でベンチマークすることはそれ程意味はなさそうです。

レンダリングWebGLCanvasの両方に対応

レンダリングの設定はWebGLCanvasの両方に切り替えが可能です。
この辺りはコンテンツによって最適解が違う感じがします。

f:id:tsubaki_t1:20181207230632j:plain

試し方

Tiny Modeを試す方法です。

導入

Package Managerから導入出来ます。

f:id:tsubaki_t1:20181207211758j:plain

とりあえず試したい場合は、メニューアイテム > Tiny > Import Samplesからサンプルパッケージが導入出来ます。 ("registry": "https://staging-packages.unity.com"は不要です)

どんなゲームが含まれているのかは、以下の記事が分かりやすいです。

qiita.com

機能 内容
Project ゲームのプロジェクト(***.utproject)。
UnityProject内に複数存在する
(Ad等は単体のゲームだけじゃないからかな)
Group 概念的にはScenePrefabが近い(****.utdata)
Entity GameObjectのようなもの
Component Entityに登録するデータ
System フィルタで指定したComponentに対して処理を行う(***.ts)
Behaviour フィルタで指定したComponentが生成されたり破棄されたりすると呼ばれる(***.ts)

ゲームの再生

再生は普通に「再生ボタン」ですが、GameViewではなくブラウザで実行します。 何もしなければPCのブラウザですが、QRコードを読み込めば同じネットワーク上のモバイルでも動作します。QRコードで起動したゲームは、タブを閉じなければ次起動したときに勝手にコンテンツを更新して再起動してくれます。

f:id:tsubaki_t1:20181207232629j:plain

この時、Unityエディターでポーズを選択すると、現在動いているシーン内の状況をComponentDataの中身も含めて再現します。これはモバイル上でゲームを動作させている場合も同様です。(Live Link)

https://user-images.githubusercontent.com/1644563/49653477-6908bd00-fa78-11e8-9817-c135d63157f3.gif

同様にプロファイラーが使えるっぽいです。

なお、シーンの再現やプロファイラはデバッグ、開発モードでビルドした時のみです。リリースビルドだと出来ません

結局名前はTiny Unity? Tiny Mode?

Project Tinyです。

名前は現状、安定していないみたいです。
自分が聞いた限りだと、以下の名前がありました。ど忘れした、もっと別の名前も言ってた気がしますが、まぁ出てこないって事は二度と聞かないでしょう。

  • Tiny Mode
  • Tiny Unity
  • Unity for Small things
  • Project Tiny

名前が安定したら変更します。

感想

多くの機能が完全に新規なのでムムってなりますが、PrefabやProfiler(Frame Debugger)の機能が使えたり、いつものHierarchy & Inspectorによるデバッグが出来るので、思ってるよりすんなりと使えます。

関連

使い方を紹介しています。 qiita.com

マニュアルです

Tiny Mode | Package Manager UI website

blogs.unity3d.com

【Unity】はじめてのScriptable Build Piepline

f:id:tsubaki_t1:20181203181626j:plain

今回は、先日行われたGotanda.unity #9で話した「はじめてのScriptable Build Pipeline」の解説記事です。

スライド

www.slideshare.net

Scriptable Build Pipelineとは?

Scriptable Build PipelineはUnityでゲームやAssetBundleをビルドする為のコードを露出したパッケージです。

f:id:tsubaki_t1:20181203183345j:plain

本来のBuildPipelineは殆どがネイティブコードで動作しており、簡単なオプションの設定等しかできませんでした。
Scriptable Build Pipelineでは殆どのコードが露出しており、どのような設定で構築されるのか確認したり、AssetBundleの構築ルールを独自のルールにカスタマイズするといった事が可能になります。

f:id:tsubaki_t1:20181203183724j:plain

Scriptable Build Pipelineの使い所

カスタマイズ出来るからといって、全てを最初から定義する必要はないです。

Scriptable Build PipelineAddressable Asset Systemが利用しています。基本的にはAddressable Asset Systemで利用するアセットを使用しますが、必要になればScriptable Build Pipelineを利用して生成するAssetBundleのカスタマイズを行う形になります。

f:id:tsubaki_t1:20181203182525j:plain

またLegacy Build Pipeline.csUnity 5系のAssetBundleを構築出来るので、現状のAssetBundle Nameベースからの移行も、まぁ何とか出来るんじゃないかなと期待しています。
もしくは、完全に自分の考えでアセットを構築するのもアリでしょう。最終的にアセットの依存関係さえ作れれば良いので、自分で作ってJSONに格納して、それをタスクに渡して…というのも可能かもしれません。それはLegacy Build Pipelineをカスタマイズすれば割と何とかなりそうです。

なおLegacy Build Pipeline.csは既存の全てのビルド設定をサポートしていない点に注意です。特にDisableLoadAssetByFileName`DisableLoadAssetByFileNameWithExtensionは、パフォーマンスの向上が期待出来るとはいえ、コードの変更を強制する形になるかもしれません。

  • DisableLoadAssetByFileName :強制ON
  • DisableLoadAssetByFileNameWithExtension :強制ON
  • IgnoreTypeTreeChanges:現状未サポート
  • AppendHashToAssetBundleName :現状未サポート、
    (簡単に足せるよね!という暴論)
  • StrictMode :サポートせず
  • DryRunBuild :サポートせず(不要)

導入

Scriptable Build Pipelineはソレ単体でも導入できますが、大抵の場合はAddressable Asset Systemと一緒に導入して、必要に応じてカスタマイズという形になると(個人的には)思います。
なので、その2つを導入していきます。

最初にAddressable Asset Systemを導入します。 これは下の記事が分かりやすいです。

kan-kikuchi.hatenablog.com

導入したら、Scriptable Build Pipelineのコードを編集可能にします。

プロジェクトフォルダ/Library/PackageCacheScriptable Build Pipelineのパッケージ(com.unity.scriptablebuildpipeline)が格納されているので、これをプロジェクトフォルダ/Packagesフォルダに移動します。

f:id:tsubaki_t1:20181203200040g:plain

これでデバッグログを出したり、スクリプトデバッガで挙動を追跡したり、色々と可能になります。
バージョンアップしたいと思ったりパッケージの改造が不要と思えばプロジェクトフォルダ/Packagesからプロジェクトフォルダ/Library/PackageCacheに戻せば良いので、かなり手軽です。

なおソースファイルは全て読み取り専用になっています。

f:id:tsubaki_t1:20181203191349j:plain

Scriptable Build Pipelineを理解するためのサンプルコード

f:id:tsubaki_t1:20181203192728p:plain

Scriptable Build Pipelineは、ほとんどの箇所でBuildTaskRunnerIBuildTaskそしてIContextObjectを利用したコードで実装されています。なのでSRPのコードを色々と見る前に、APIがどういったものか把握しとくと、コードを読むのが大分楽になります

  • IContextObjectデータを格納するインターフェース。
  • IBuildTaskビルドの処理を担当するインターフェース。BuildTaskRunnerのTask一覧に登録した順にRun()が呼ばれる。使用するデータは[InjectContext]でフィールドに自動的に注入される。
  • BuildTaskRunner:ビルドを実行するクラス

下のコードは、文字を登録して、登録した文字を取得し、表示するだけの簡単なコードです。

f:id:tsubaki_t1:20181203195423g:plain

gist.github.com

あとはDefaultBuildTasks.csを見たりLegacyBuildPipeline.csを追っていくと、結構何をやっているのかがわかります。

f:id:tsubaki_t1:20181203201308j:plain

関連

マニュアル(時々URLが変わる)

docs.google.com

Gotanda.Unity #9

meetup.unity3d.jp

【Unity,ECS】他のEntityが持つComponentDataを追跡する - ComponentDataFromEntity

今回もECSの話です。

ゲームでは特定のオブジェクトを追跡するというのはよくある話です。例えばカーソルやユニット、アイテムやパスなど。

ECSでも同様にそういった機構が必要になる事は多々あります。今回はEntityから特定のキャラクターを追跡させる方法についてです。

キャラクターを追跡する

キャラクターを追跡する…となると、MonoBehaviourを使用するならば非常に簡単に実現が可能です。下のようなコードを書けば良いです。

gist.github.com

ただしECSでは、こんな簡単に実現出来ません。ECSが使用するオブジェクトEntityは基本的に参照型ではなく、またポインター等でアドレスを取得してもComponentData等の増減やSharedComponentDataの値変更等の理由により、アドレスが変化する可能性があります。

なのでComponentDataFromEntityを使用して、Entityから対象の座標やパラメーターを追跡します。

ComponentDataFromEntityでEntityの持つComponentDataを参照する

ComponentDataFromEntityは、全てのComponentDataの内から、特定のEntityが持つComponentDataを取得するAPIです。

特定のEntityの方を向くシステムを作ってみます。

f:id:tsubaki_t1:20181201224146g:plain

最初に特定のEntityからPositionComponentDataを取得します。

[ReadOnly] public ComponentDataFromEntity<Position> positionFromEntity;

値を取得するには、下のように記述します。 なお対象のEntityがPositionを持っていない可能性もあるので、一応でEntityが無ければ処理をスキップします。

if (!positionFromEntity.Exists(target.Value)) return; 
var targetPos = positionFromEntity[target.Value];

gist.github.com

ComponentDataFromEntity[ReadOnly]なら並列でもアクセス出来るので、楽で良いです。

なお、ComponentDataFromEntityやポインタをキャッシュして云々は、基本的にランダムアクセスになります。ECSは連続したメモリアクセスによる高速化(事前にソートしたデータへ連続してアクセスし、同じ処理を繰り返し実行することによる高速化)なので、ランダムアクセスは出来れば避けたい所です。

ComponentDataFromEntityと同じ種類のComponentDataにアクセスする場合

厄介なのが、ジョブ上でComponentDataFromEntityと同種のComponentDataにアクセスする場合です。
例えば、ComponentDataFromEntityが取得するPositionの情報をを元に、Positionを更新する…といった場合です。

ということで、キャラクターがシリンダーを追跡するコードを作ってみます。

f:id:tsubaki_t1:20181202000338g:plain

まず、下のコードはエラーになります。 InvalidOperationException: The writable NativeArray LookTargetJob.Iterator is the same NativeArray as LookTargetJob.Data.positionFromEntity, two NativeArrays may not be the same (aliasing).

gist.github.com

一旦EntityFromPositionの情報をキャッシュしてから使用するように変更してみました。
ポインタを使用して直接アクセスするのも悪くなさそうですが、このアプローチならリニアなメモリアクセスが維持できるので良いんじゃないかなとコッソリ思っています。
(キャッシュミスしまくるComponentDataFromEntityを事前に叩いておき、参照するデータを連続したメモリに格納、あとは参照先の値を使用した処理はシーケンシャルに処理する)

gist.github.com

コピーのコストがあるので最適解かは微妙なところですが、まぁ一応こんな感じで回避出来るという事で。

また、一応EntityCommandBuffer.Concurrentで一旦バッファに格納することでも回避が可能といえば可能です。

tsubakit1.hateblo.jp

追記

一つしか存在しないようなデータ(例えばプレイヤー)の場合、Systemにフィールド持たせてキャッシュさせるのはアリです。
Systemは世界で一つであることが保証されているので、シングルトン的な感じでデータをキャッシュしたり出来ます。 ただ、ComonentGroupがなければSystem停止はしてくれないので、動作を安全にするためにSystemが勝手に動かないようにOnUpdateを即リターンするなり、Systemの生成を運用するなり工夫する必要はあります。

【Unity】最近ECSで色々と遊ぶ際によくやってる、4つのTips

今回は自分がECSで色々やるときに、よくやっているTipsを3つ紹介します。

パッケージをPackagesに移す

PackageManagerで導入したパッケージはC#コードとして公開されています。
ただしパッケージ内のコードを追う為にはProjectビューのPackagesの中から探す必要があります。ここで参照するコードはプロジェクトが含まれておらず、被参照のコードを探すのもメソッドの中身を追うのも一苦労です。

なので、最近はパッケージの中身をPackagesフォルダの下に移動させています。
これでコードの"宣言への移動"やスクリプトでバッグ等、IDEの便利機能が使用可能になります。

f:id:tsubaki_t1:20181130220741g:plain

やり方は簡単、 プロジェクト/Library/PackageCacheの中にあるEntityComponentSystemのパッケージをプロジェクト/Packagesフォルダへ移動するだけです。

f:id:tsubaki_t1:20181130220453p:plain

難点はPackagesフォルダへ移動すると、PackageManagerでパッケージの更新が出来なくなる点です。 その場合は、一旦PacakgesフォルダからPackageCacheフォルダへ動かせば、更新が可能になります。

tsubakit1.hateblo.jp

inputDepsのチェーン

最近、C# Job Systemのジョブを複数持つ時にはJobHandleを数珠繋ぎ的な感じで使い回す事にしています。特にJobComponentSystemを使用するときには、下のようにJobHandleを受け取って、受け取ったJobHandleを更新する形にすると、ジョブの並び順が分かりやすくなります。
下の場合は、基本的にスケジュール実行順で呼ばれます。

個人的にはJobHandleの数が1個で済むのがポイントです。

f:id:tsubaki_t1:20181130221651p:plain

Debug.Logを出す

C# Job Systemですが、Burstが絡まなければログを出力できます。
正確にはスレッドセーフなスタティック関数は呼べるといった感じです。これはマニュアルで将来的に出来なくする的な事が仄めかされていますが、まぁ今は使えます。問題はありません。

当然、恐ろしく高い負荷が計上されます。ただ、呼ばれるのは別スレッドなので、コアを使い切ってなければアリかなというのが個人的な印象。

特にECSは結果を確認するのが非常に面倒くさいシステムでもあるので、(スクリプトでバッグも使いにくい)割と助かってます。

なおDebug.Line等のメインスレッドでしか呼べない機能は呼べません。

f:id:tsubaki_t1:20181130223459p:plain

gist.github.com

NativeArrayを自動的に開放するジョブ

NativeArrayを使用すると面倒くさいのが、NativeArrayの開放です。
これが面倒くさいのでNativeArray(n, Allocator.Persistent)で一旦確保した後に使いまわしたい所ですが、これはメモリを再確保しないことがある意味前提となっていて、最大長を変えるとかを連続して行うとメモリの断片化の要因になりそうな感じがあります。

なのでAllocator.TempJobで確保して、[DeallocateOnJobCompletion]でジョブの完了後に自動的に破棄するようにします。
これでMonoBehaviour等でジョブを発行し、LateUpdateでCompleteしてメモリを開放…みたいな事をしなくとも、ジョブが完了したら速やかに開放してくれるようになります。

f:id:tsubaki_t1:20181130225824p:plain

NativeArrayの確保は複数のジョブで共有する計算結果をキャッシュしたり、複数のジョブからアクセスしたいReadOnlyのオブジェクトを確認するとかに便利。
下の図では、ログを出すのが重いので一旦パラメーターをキャッシュして、複数のジョブで一斉に処理するようにしています。

f:id:tsubaki_t1:20181130225852p:plain
f:id:tsubaki_t1:20181130225908p:plain

とは言え、ジョブは単体に全部入りするより機能を絞って連続させたほうが良いのはご存知の通り。つまり最後にババを引くジョブが作ってる内にちょくちょく変わります。

で、面倒になってきたので最近はジョブ解放専用のジョブを1回発行するようにしています。 まぁinputDepsのチェーンで順番が分かりやすくなったので、割とコレしなくても何とかなるといえばなんとかなるのですが。

tsubakit1.hateblo.jp

関連

tsubakit1.hateblo.jp