テラシュールブログ

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

【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

【Unity】Unity Hubで表示されていないバージョンのUnityをダウンロードする

今回はUnityHubで表示されていないバージョンのダウンロードについてです。

UnityHub経由でダウンロード出来ない

Unity Hubは複数のUnityのバージョンをインストールするのに便利です。 複数のUnityのバージョンをひと手間無くインストール出来ますし、Unity Hub経由でプロジェクトを開けば、一致したバージョンで開いてくれます。

さて、今回困った内容ですが、まずは下の画像を見てください。

f:id:tsubaki_t1:20181124224022p:plain

私がほしいのはUnity 2018.2.15f1です。しかし実際にインストールできるのはUnity 2018.2.17f1です。

Unity インストーラー経由でダウンロードすれば良いじゃんという意見もあるかもしれませんが、日本語化が可能なのはUnity Hubからのみです。どうしてもUnity Hubからインストールしたい。

解決編

ダウンロードアーカイブUnity Hubボタン経由でインストールすれば、Unity Hubでインストールしてくれます。

f:id:tsubaki_t1:20181124224522p:plain

何気に5.6までインストールが可能でした。(現在の話)

【Unity】AR Foundation(ARKitとARCoreのマルチプラットフォームAR環境)の超入門

今回はARCoreとAR Kitの両方に対応したマルチプラットフォームAR環境である「AR Foundation」、その最初の一歩として「とりあえず動くもの」の作り方を紹介します。

AR Foundationとは?

マルチプラットフォームAR環境です。現状はAR CoreとAR Kitの両方ですが、今後は他のものが増えるかもしれません。

blogs.unity3d.com

かなり勘違いしやすいのは、ARCore SDK for UnityAR Kit Pluginをまとめたものではない という点です。

AR Foundationは、ARKit XR Plugin(com.unity.xr.arkit)ARCore XR Plugin (com.unity.xr.arcore)をまとめたものです。
どちらも最終的にはARKit SDKとARCore SDKは使用しているのですが、C#側で使用するAPIが若干異なるので、そのあたりに注意です。また、AR Kit PluginやARCore SDK for Unity等の、それぞれ特化したプラットフォームと比較して機能が若干貧弱です。

平面検知は普通に使えるので、それが欲しいなら割と良いです。

なお、現状「ビルドしないと動作しない」です。他のAR Kit PluginsやARCore SDK for Unityが持っているような、スマホを接続してプレビューする的な機能は使えません。ロードマップによるとUnity 2019.1+です。 そういった点で言えば、Vuforia は楽で良いですね。対応するスマホも不要ですし。

f:id:tsubaki_t1:20181121204720j:plain

使い方

Package ManagerでAR FoundationARCore XR PluginARKit XR Pluginをインポートします。

あとPlayerSettingsiOSRequest ARKitSupportAndroidARCore Supportedを入れる必要があるかもしれません。

f:id:tsubaki_t1:20181121202521j:plain

ARのセットアップ

セットアップは非常に簡単です。

  1. Main Cameraを削除して、代わりにAR Cameraを導入します。
  2. メニューアイテムのGameObject > XR > AR Aession Originと、GameObject > XR > AR Sessionを選択します。

f:id:tsubaki_t1:20181121203130j:plain

現状こんな感じ。

f:id:tsubaki_t1:20181121203335j:plain

平面の検出をグラフィカルにする

下のような感じで、判定した平面の検出をグラフィカルに表示します。

f:id:tsubaki_t1:20181121203542j:plain

  1. メニューアイテムの GameObject > XR > AR Default Planeを選択して、AR Default Planeを作成、それをPrefab化します。
  2. AR Session OriginオブジェクトにAr Plane Managerコンポーネントを追加します。
  3. AR Plane ManagerコンポーネントPlane Prefabに、1で作成したAR Default Planeをセットします。

これでカメラを地面に写した時、黒い枠が表示されるようになります。

f:id:tsubaki_t1:20181121203857g:plain

f:id:tsubaki_t1:20181121213515j:plain

オブジェクトをAR空間に配置する

AR空間に配置するのには、少しだけコードを使用します。

タップした位置を把握するのは origin.Raycast(Input.GetTouch(0).position, hitResultsです。あとは取得した位置にオブジェクトを作るだけです。
下のコードはARSessionOriginコンポーネントと同じオブジェクトに登録します。

gist.github.com

あとはタップで生成したいPrefabをSpawnObjectprefabに登録すれば準備完了です。

f:id:tsubaki_t1:20181121205432j:plain

なおPrefabが大きすぎると最初からめり込んだ状態で生成され、生成されていないように見えるので、注意です。

完成

ここまで調べる時間を除けば、ほとんど数分で達成できるのは中々良いです。リモートが無いので作る手軽さやワークショップ等ではVuforiaの方が正直楽なんですが、まぁかなり良いんじゃないでしょうか。

f:id:tsubaki_t1:20181121210315g:plain

あとは周辺の環境と馴染ませたいなら、https://github.com/Unity-Technologies/arfoundation-samples/blob/master/Assets/Scripts/LightEstimation.csとか使うと、結構良い感じにマッチします。

f:id:tsubaki_t1:20181121210545g:plain

試した環境:2018.3b9

関連

サンプル一覧

AR Foundationのサンプル集です。

github.com


AR Foundationのマニュアルです。撮影したカメラの画像とか、一部で需要があるんじゃないかしら
撮影結果をキューブマップに流し込んだり

About AR Foundation | Package Manager UI website


www.youtube.com