テラシュールブログ

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

【Unity】Cinemachineで複数のカメラを使用する

f:id:tsubaki_t1:20180923231727g:plain

今回はCinemachineで複数のカメラを使用する方法についてです。

 

 

複数のカメラを使う

ゲームを作成している時、複数のカメラを使用したいというケースが稀に存在します。例えば画面分割2Pプレイダンスシーンの背景カメラなど。

f:id:tsubaki_t1:20180923222302j:plain

普通はCameraを複数持てば良い話です。ただしCinemachineを使用する場合に少し考える必要があります。
というのもCinemachineはCinemachine Brainは最も優先度の高いVirtual Cameraを追跡する仕組みの為、複数のCinemachineBrainを用意すると全て同じVirtual Cameraを追跡してしまいます。

 

今回はその解決法についてです。

 

Timelineを使用している場合、複数のTrackを用意する

それがTimelineを使用している場合、複数のVirtual Cameraを使用するのは非常に単純、TimelineのTrackを複数用意すれば良いです。

f:id:tsubaki_t1:20180923223852j:plain

 

Timelineを使用しない場合

Timelineを使用しない場合、非常に面倒な手間が必要です。

  1. 同時に使用するCameraの数だけレイヤーを用意
  2. VirtualCameraのレイヤーを設定
    使用するレイヤーはカメラ毎にユニークな物を使用する
  3. CameraのCullingMaskでVirtualCameraが持っているレイヤーを指定
  4. 2と3をカメラの数だけ繰り返す

f:id:tsubaki_t1:20180923224614j:plain

f:id:tsubaki_t1:20180923224848j:plain

正直な所、この制御のためだけに31個しか使えないレイヤーを使うのはナンセンスな印象があります。多分開発者的には「映す対象とVirtualCameraが一致する」的な考えなんじゃないかなーと予想してますが…うーん?

 

なおCan I use 2 camera with Cinemachine Brain?にてCinemachineBrain.SetCameraOverride() を使用して独自にCinemachineBrainのアクセス先をオーバーライドする方法が紹介されていますが、APIがInternalなので使えません。
使えたとしても追加でカメラを制御するコードを記述する必要があります。正直これならCinemachineBrainのCullingMask使っている所を書き換えた方が楽かなという印象です。

【Unity】ECSで複数のWorldを運用する方法

「デモとしてのECS」が欲しいなら使いませんが「ゲームとしてのECS」が必要なら知っておくと良い、複数のWorld運用についてです。

 

 

ECSを”ゲーム”にするためには

ECSをヘビーに使用する時に気になってくるのが「特定の状態で不要になるSystem」の存在です。

デモであればSystemを止める事は不要です。始まりも終わりもなく単純に続けるシステムです、なんにも問題はありません。何故なら全て必要なSystemのみが動作しているからです
ですが「ゲーム」ならば、Systemが動きっぱなしというのは少し面倒な問題です。大抵の場合、不要なSystemが登場します
例えばシューティングゲームに必要なPositionの更新やHitcheckといったSystemはゲーム以外の状態…例えば結果画面やタイトル、ポーズ、会話といったシーンでは不要でしょう。

ここでのSystemを止めるアイディアは3つあります。

  • 複数のWorldを運用し、Worldを切り替えて運用する
  • ComponentSystemを取得して積極的にOFFにする
  • Systemが要求する全てのEntityを削除する

ある意味どれでも良いと思いますが、今回は複数のWorldで運用する方法について考えてみます。

f:id:tsubaki_t1:20180923022709p:plain

 

複数のWorldを管理する

複数のWorldで運用するのは、個人的には悪くないアイディアのように考えています。

  • 有効・無効を大きな単位で行える
  • 生成・破棄を裏側で行える
  • オブジェクトを維持したまま処理だけを止められる

まずWorldという単位で処理の有効化・無効化が行えるので、そのWorldでどのような処理が行われているのかを把握せず大きなWorld単位でON/OFFが実行できます
これは「Game World」や「Menu World」のような単位で区切っておけば、メニュー中にゲームを簡単に止められる事を意味します。

オブジェクトの生成・破棄をバックスレッドで実行できる点も魅力的です。同じスレッドだと同期ポイントが必要になるのですが、Worldを分割しておくことで現在有効ではないWorldならスレッドから操作するといったアイディアが出てきます。

またSceneのように読込-破棄を選択するのではなく、Entityを維持したまま処理のみを停止出来るのも魅力的です。例えばゲーム中にメニュー画面を開いても、ゲームの内容は維持されていますが、ゲームの進行は停止するといった事ができそうです。

f:id:tsubaki_t1:20180923024530p:plain

tsubakit1.hateblo.jp

 

複数のWorldは動かせなくなりました。

なおWorldは複数同時に動かす事も可能です。
下のGifアニメでは、赤いコインを作成したWorldと白いコインを作成したWorldを作成して、各Worldを有効・無効を切り替えています。

 

Worldを作成する

まずは独自のWorldを作成します。

Worldはnew World("World Name");で作成できます。
なお独自に作成したWorldには一切のSystemが登録されていないので、world.CreateManager<ComponentSystem>();で必要なシステムは独自に登録する必要があります。

gist.github.com 

動かすWorldを切り替える

次に複数作成したWorldを切り替えます。
正確にはWorldを有効化するという方が動作に沿っています。

ScriptBehaviourUpdateOrder.UpdatePlayerLoopを使用すると、指定したWorldのみが有効化します。逆を言えば、指定していないWorldの持つ全てのSystemは動作を停止します。

gist.github.com

Tips

World.Active

WorldにはActiveというAPIがあります。
これは特別な意味はない単なるプロパティです。そのため、ココにWorldをセットする事に意味はそれ程ありません。

強いて言うならSceneのActiveと同じような扱いになりそうです。つまりGameObjectを作成したらActiveなシーンに生成されるように、アクティブなシーンに対して色々と行う…という感じ。

Worldの運用によっては、メインのゲームとなるWorldは常に一つということも考えられるので(Worldを跨いだEntityのやりとりが出来ないので、メインとなるWorldへ追加したEntityをマージしていく)、こういった設計も有りかなという感じです。

 

Default Worldを生成しない方法

コレを使っていると気になるのが、最初から用意されているWorldです。
最初から用意されているWorldは基本的に全てのSystemが最初から登録されています。処理の一貫性を保つ為には、コレは余計なお世話です。

Default Worldを作成しないようにするには、Scripting Define Symbolsに UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAPを定義しておきます。

f:id:tsubaki_t1:20180923024034j:plain

初期化を防止するコードはAutomaticWorldBoostrap(PackageManagerから参照)にて確認出来ます。
Worldの破棄はWorld.DisposeAllWorlds();でも可能ですが、シンボルで事前に作成を抑制したほうが正しい印象です。

f:id:tsubaki_t1:20180923023831j:plain

Default Worldは「全てのSystemを登録」してしまいます。ちゃんとInjectで注入する形で設計し、注入対象のEntityが無ければそこまでは問題にはならないと思います。
ですがInjectを行わないタイプ(GetEntity等)のSystemや、Systemを別のアプローチで動かす(RXやAsync等)場合には注意が必要かもしれません。

 

EntityはWorldを跨がず、SystemはWorld毎に作成

Worldを複数作成した時に気になるポイントは、別のWorldからEntityへアクセスすることが出来ないという点です。つまり、異世界転生(World間の移動)は可能ですが、異世界通信は不可能という話。

f:id:tsubaki_t1:20180923031357p:plain

ただしSystem間の場合は普通にアクセスできそうです。流石にInjectは使えませんが、CreateManagerしてるなら普通にイベント登録するなり色々と。
ECSのSystemは普通にオブジェクト指向です。

 

Systemの停止処理をはさみたい場合は注意

なお、今回の方法で止まるのはOnUpdate等の処理です。
OnStopRunningOnStartRunning等のコールバックが呼ばれる訳ではない点に注意です。

またOnUpdateで駆動してない場合もあります。
例えばMeshInstanceRendererSystemとかはRenderPipeline.beginCameraRenderingCamera.onPreCull側にイベント登録して呼び出しを行っているので、Worldを止めても無効化されません。

 明確に停止したい場合は、World.GetExistingManagerでSystemを取得して停止するのが良さそうです。

 

関連

ECSで移動するビルボード風スプライトを同時に114514体表示する

【Unity】Entity Component System入門(その1)【2018.2】

【Unity】ECS + JobSystemでライフゲームを実装してみた

【Unity】Unity 2018.3でTimelineの座標系が少し分かりやすくなった

UnityのTimelineは座標系に謎の挙動がありましたが、Unity2018.3でもう少し納得できる感じに修正されました。

 

 

 

Unity 2018.3以前のTimelineの座標系

Timelineの座標ですが、Unity2018.3より前はAnimatoinControllerの有無で挙動が変わるという、非常に面倒くさい仕様でした。
AnimationTrackで操作する場合、AnimationControllerが無い場合は親オブジェクトからの相対座標ですが、有る場合は現在座標を開始地点とした相対距離を移動します。

https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20180514/20180514230903.gif

これがAnimationControllerの有無で変わる為に無駄な混乱が有りました。
例えばゲーム演出としてのカットシーンを使用したい場合、AnimationControllerを外さないと現在地からの相対移動でスタートすることになり座標が狂います。かといって座標をTimeline開始点に合わせて調整するのは余りにも無駄な操作です。

tsubakit1.hateblo.jpそれだけでも意味不明ですが、この挙動はゲーム再生時のみのためゲーム再生とTimeline再生で動作が異なります。

そんな感じで、Timelineは結構面倒な機能になっていました。

 

変化したTimelineの座標系

 Unity 2018.3からAnimationTrackに3つのオプションが追加されました。

  • ApplyTransformOffset
  • ApplySceneOffset
  • Auto

この設定により座標の設定が若干異なることになります。

f:id:tsubaki_t1:20180914233322p:plain

ApplyTransformOffsetはカットシーン向け

ApplyTransformOffsetはキャラクターの現在の座標を無視して、親GameObjectからの相対距離で移動します。
親オブジェクトの座標に従う為、座標は常に期待通りの位置で終わります。逆に、その場でアニメーションさせたい場合には親オブジェクトを用意して実行という無駄な手間が発生します。
f:id:tsubaki_t1:20180914225131j:plain

f:id:tsubaki_t1:20180914232327g:plain


開始点を少しずらしたい場合はオフセットで位置や回転を調整して、アニメーションをうまい感じに調整していきます。

ApplySceneOffset

ApplySceneOffsetの場合、Timeline開始時の座標を基準としてアニメーションを開始します。

f:id:tsubaki_t1:20180914232733j:plain

f:id:tsubaki_t1:20180914232924g:plain

なお、この状態でアニメーションを編集すると挙動がかなりおかしくなります。基本的にアニメーションを編集する際にはTransformOffsetにするのが良さそうです。

 

Auto

AnimationControllerの有無で動作が変わります。
Uniy 2018.3以前の動作との互換性用です。

 

私が本当に欲しかったもの

 PlayableDirectorとの相対距離で作ってほしかった私です。
まぁ全オブジェクトをPlayableDirectorの子にして、TransformOffsetで再生すれば良い話ではあるんですが、親子関係は色々な動作に強烈に関わるからできるだけ動かしたくないっていう

 

tsubakit1.hateblo.jp

何にしても、余計なお世話が消えて大分使いやすくなりました。

【Unity】自作のパッケージを追加したりパッケージを一覧から検索したり…PackageManagerがUnity 2018.3で変化したポイント

今回はUnity 2018.3でPackageManagerが変化した幾つかのポイントです。

 

 

Package Managerに自作パッケージの追加が可能に

Unity 2018.3からPackageManagerに自作のパッケージを追加出来るようになりました。今までもpackage.jsonを拡張すれば可能でしたが、それが機能としてちゃんと追加された形です。

 

synamon.hatenablog.com

実際の手順

手順は大きく分けて3つあります。

  1. パッケージ化するフォルダの作成
  2. 「package.json」の記述
  3. PackageManagerからパッケージの追加

一つずつ見ていきます。

 

パッケージ化するフォルダの作成

パッケージ化するフォルダを作成します。基本的なルールはAssets以下のプロジェクトと同じような感じです。つまりEditor以下はビルドされませんし、AssemblyDifinitionFileは使えます。

例えば今回はエディター拡張と画像ファイルを適当に突っ込んだフォルダを用意しました。

f:id:tsubaki_t1:20180913204126p:plain

 

package.jsonの記述

次にpackage.jsonを記述します。ここにパッケージのバージョンやパッケージの名前、依存関係等を記述していくわけです。

gist.github.com

f:id:tsubaki_t1:20180913204625j:plain

記述のルールに関してはPackageManagerのマニュアルに詳しく記述されています。
多くの場合、パッケージに「他のパッケージへ依存関係を持たせる」方法が有益かもしれません。

 

パッケージの登録・登録解除

最後にパッケージを登録します。
PackageManagerの下に+ボタンが追加されており、ここからpackage.jsonを指定すると、指定のパッケージが登録出来ます。

f:id:tsubaki_t1:20180913205745g:plain

登録するとPackages一覧に自作のパッケージが追加されました。

ただエディター拡張が使えたり使えなかったりします。動作を確認中

f:id:tsubaki_t1:20180913205616j:plain

 

パッケージの登録解除はPackageManagerから行います。
Removeボタンを押せば登録が解除され、一覧から参照出来なくなります。

f:id:tsubaki_t1:20180913211358j:plain

 

PackageManagerの一覧表示が少し変わった

PackageManagerの一覧表示のオプションとして「Previewを表示するかどうか」が追加されました。

PackageManagerが提供するアセットは「Verified」と「Preview」の二種類があり、Verifiedは安定版Previewはβ版といえます。
今回は明確にPreviewを隔離した形です。なおPreview外すと新しい機能が殆ど無くなるっていうね。

f:id:tsubaki_t1:20180913211544j:plain

またパッケージ内で検索出来るようになりました。特に最近はパッケージが増えてきていて探すのが面倒になってきていたので、コレはありがたいんじゃないかなという気がします。

まぁ検索が効くのはパッケージ名だけで、カテゴリとかはスルーされてるんですが

f:id:tsubaki_t1:20180913211932j:plain

 

パッケージがローカルにキャッシュされるようになった

PackageManagerから入手したパッケージは基本的に特別なフォルダにキャッシュされていたのですが、今回これがプロジェクトの子階層にキャッシュされるようになりました。

tsubakit1.hateblo.jp

これが何を意味するかは難しい所ですが、PackageManagerのコードを書き換えても他のプロジェクトに影響しにくくなったと言えるかもしれません。

  • パス : {プロジェクト名}\Library\PackageCache

ただ、普通のプロジェクトではプロジェクトを共有する際にLibraryは抜くので、ココを改造するとプロジェクトが簡単に破綻するかもしれません。改造を目的とするなら、何処か適当なフォルダにコピーしPackageManager経由でインポートする手順を使ったほうが良さそうです。
あくまで「間違ってファイルを書き換えた時に事故る」対策なのかもしれません。

f:id:tsubaki_t1:20180913212310j:plain

Unityの基本機能の取り外し

前から出来ましたが、Unityの基本機能の取り外しがある意味分かりやすくなりました。正確にはエンジンの機能というよりはエンジンへアクセスするC#インターフェースを取り除けます。
エンジンに完全に機能が組み込まれてしまっている部分では効果は薄いかもしれませんが、WebGLなど非使用コードが積極的に削除されるプラットフォームや、ストリップ可能なC#で殆どの機能が記述されている場合(例えばTimeline)にはかなり有効な機能なんじゃないかなと思います。

  1. PackageManagerのBuilt in packagesの項目を選択します。
  2. 好きな機能(例えばアニメーション)を選択します。
  3. Disableで止めます

f:id:tsubaki_t1:20180913213338j:plain

これて止めると該当するAPIが使用できなくなります。

f:id:tsubaki_t1:20180913213452j:plain

何故明示的に削るのかというと、UnityにはCode Stripという不使用のコードを削る機能があるのですが、これはゲームに使用されている(SceneやPrefab等で)だけでなく、完全に不使用のサンプルコードから使用されている場合にも「使用する」判定となり、多くの場合あまりうまく機能していませんでした。

 なのでバイナリサイズを抑えようと思ったら不使用コードは全部プロジェクトから取り除き全コードを注意深く観察する必要があったのですが、今回コレで強引にプロジェクトから剥がすことが出来るようになったという訳です。