新しいprefabワークフローについて
おおよそ一年半前、Unity2018.3においてprefabの新しいワークフローが導入されました。
これによりUnityでのprefab利用が大きく拡張、変更、整理されました。
Unity2018.3以降の「Unityマニュアル」も、プレハブの項は大幅に拡充されています。
いままで明確じゃなかったところがはっきりしたところもありますし、簡単にできていたところがややこしくなってしまった部分もあります。
それまでモヤっとしていたprefabの仕組みが今回のワークフロー導入によってはっきりしたことも多いです。
今回の新しいワークフローによって変更になった点は次の3つ。
1.prefabの機能が変わった(ネステッドprefab、prefabバリアント)。
2.prefabの作り方が変わった。
3.prefabの編集方法が変わった。
って、ほとんど全変わりです。
1.には従来から望まれていた、prefabの中に別のprefabを置けるネステッドprefabもあります。
でもprefabバリアントについてはあまり想定していなかった機能ですね。prefabバリアントは元のprefabをコピーして一部を変更するものです(バリエーションを色々作るってことですね)。
ネステッドprefabはエディタ拡張などを使って近い機能を実現していたものもあったのですが、prefabバリアントに関してはUnity自体の拡張でなければちょっと作れなさそうですし。。
この点で想像が及びませんでした。
しかしこのバリアント機能の実現によって、assetbundleのバリアント機能のほうは非推奨になるなどUnityの使い方全体に関してのインパクトは大きいようです。
2.に関して言うと、かつてprefabを作るにはHierarchyビュー(Sceneビュー)にGameObjectを作成して、それをprojectビューにドラッグアンドドロップすることによって作成していました。これは手順としてもprefabがGameObjectをシリアライズ(実体化)したものであることを示しています。
しかし新しいprefabワークフローにおいてはシーン中にGameObjectを作らなくても、projectビューで完結してprefabを作成することもできるようになりました、従来のHierarchyビュー(Sceneビュー)から持ってくる方法も使えますので、これらは用途によって使い分けていくことになるでしょう。
3.は従来は、prefabの中身を編集するためには、いったんHierarchyビュー(Sceneビュー)にドラッグアンドドロップしてGameObject(prefabインスタンス)化してから、そのGameObjectを編集して最後にprefabにその結果を反映してやるという手順を実行する必要がありました。
新しいprefabのワークフローではprefab編集モードというのが登場して、そのまま直接編集することが可能になりました。
これによっていったんHierarchyビューに持ってくる必要性がなくなりました。これは2.のprojectビューでprefabを作成できるようになったことと合わせて、編集もそのままproject上でおこなえることを示しています。
そもそもprefabってなんなんだ?
そもそも論なのですがUnityのprefabってなんなのでしょうか。
prefabというのはUnityのGameObjectをシリアライズしてストレージ(UnityのAssetsディレクトリー下)に置く(永続化する)ための仕組みです。
シリアライズとは一つまたは複数のデータ、オブジェクトの構造をネットワークで転送したり、ストレージに格納するために「階層を持たないフラットなデータ構造に変換する」ことで、例えばUnityのGameObjectであれば、階層化の他のGameObjectや含むコンポーネント(MonoBehaviour)やアセットへの参照、リテラル等をフラットな構造に変換します。
元々Unityはシーン(Scene)という実行環境全体をシリアライズしたものを裏側に持っていて、プログラムコード等を修正したときにそこから実行環境の復元を試みたりします。シーンを明示的にセーブすることによってシリアライズ結果(永続化)としてシーンファイルを作れたりもするので、そのときにシーンに含まれるGameObjectのシリアライズもおこなわれています(最近では複数のシーンファイル同時に編集したりロードしたりすることができるようになったために、実行環境=シーン(Scene)=シーンファイルという単純な関係は関係は成り立たなくなってしまいました。シーンビューが表示しているのはステージで、実行環境はMainステージというそうです)。
しかしシーン全体でしかセーブできないのでは、編集や実行の都合上、粒度が大きすぎて不便なのでもっと小さい単位で、例えはGameObject個々にシリアライズして格納、編集可能にしたのがprefabです。
ただしGameObjectのシリアライズ≠prefabです。
prefabは一つのGameObjectだけではなく、階層化された複数のGameObjectを持つことができます。
prefabをproject上からHierarchyビュー(Sceneビュー)にドロップするとprefabインスタンスというprefabと結び付けられたGameObjectがシーン上に作られます。
Unityのシーン(=実行環境)は、GameObjectが樹状の階層に並んでいるだけで機能的に明確に切り分けられているわけではないので、作る側がprefabという形でグループ化して管理や作業を分けているわけです。
Unityのシーン編集では、prefabはGameObjectの型紙のような役割を持ちます。
元になった一つのprefabの内容を編集しただけで複数のシーン内にあるすべてのprefabインスタンスの内容が更新されます。これはprefabインスタンスは単なるGameObjectではなく元のprefabへの参照を持っているためです。それらprefabはシーンがロードされたときにinstantiateされてGameObject(prefabのインスタンス)が作られます。
また、シーン中で各prefabインスタンスを編集、内容を変更した場合、その変更はシーン内に記録されていて、元prefabの中身が編集されて変更されたとしても常にシーン中で上書かれて保持されることになります。この機能をインスタンスオーバーライドといいます。
インスタンスオーバーライドはUnityでの編集中だけでなく作成されたアプリの実行中にも、おこなわれます。
アプリ実行中でも、シーンがストレージから読み込まれると、シーン中にあるprefabへの参照を元に該当するprefabが一旦読み込まれてインスタンス化されGameObjectが作られます、その後にシーン内に記録されたオーバーライド情報を元にGameObjectの内容が上書かれ編集済みのGameObjectが再構築されます(この挙動によって生じる問題については後述します)。
prefabは最初からシーン内に置いておく以外に、実行中にシーン内にプログラムを使って読み出してやることもできます。prefabはロードしたたけではGameObjectにはなりません、ロードしたprefabをinstantiateすることによりprefabインスタンスというGameObjectを作成することができます。結果は、シーン内に階層構造を持ったままの複数のGameObjectが作成されますが、この場合はシーン中にオーバーライド情報はありませんのでインスタンスオーバーライドはおこなわれません。
またUnity2018.3以降、prefabはネステッドprefab機能によってprefab内の階層構造下にGameObjectではなく他のprefabへの参照を持つことがもできるようになりました。こうした参照の先のprefabも、親のprefabと同時にinstantiateがおこなわれGameObjectに置き換わります。
注:
UnityはGameObjectにとどまらずユーザーが作った任意のクラスをシリアライズする方法も持っています。これはScriptableObjectといい、ScriptableObjectを継承したクラスはシリアライズすることが可能です。ScriptableObjectは、prefabと異なりロードするだけでクラスのインスタンスが作られるので、その後instantiateしてやる必要はありません。
GameObjectはsealedなのでそれを継承するクラスを作ることはできず、拡張はMonoBehaviourを継承したクラスをコンポーネントとして追加していくことでおこなっていく等、用途と使い方が異なります。
prefabはGameObjectをHierarchyビュー(Sceneビュー)からProjectビューにドラッグアンドドロップするだけで簡単に作れますが、ScriptableObjectは作るためのエディタ拡張を書くなど、少々敷居は高いです。
用途 | 作り方 | 使い方 | 拡張性 | |
---|---|---|---|---|
prefab | GameObjectのシリアライズ | GameObjectをシーンからプロジェクトにドロップする等 | prefabをロードして、Instantiate() | MonoBehaviourを継承したコンポーネントをGameObjectに追加する |
ScriptableObject | ユーザーが作った様々なクラスのシリアライズ | ScriptableObjectを継承したクラスのインスタンスを作成して書き込むエディタ拡張を書く | ScriptableObjectで作られたアセットをロード | ScriptableObjectを継承して新しいクラスを作成する |
暗黙のprefab機能、オーバーライド
従来からあったprefab機能の中でも、モヤっとしていたところがはっきりしたと書きましたが、その代表的なものが前述の**インスタンスオーバーライド(Instance Override)**機能です。
インスタンスオーバーライドはシーン上にあるprefabインスタンスに対していくつかの制限の下
- フィールド(変数)の変更
- コンポーネントの追加
- コンポーネントの削除
- 子GameObjectの追加
といった特定の要素のみの上書きをおこなうことができるものと再定義されました。
このオーバーライドをおこなった要素は、以降、元のprefabに変更を加えても影響をうけません。
逆にオーバーライドされていない要素に関しては、元のprefabに加えられた要素はシーン上のprefabインスタンスにも即座に反映されます。
こうしたインスタンスオーバーライドを実現しているものは、シーン内にあるprefabの編集情報です、シーンはGameObjectのシリアライズ情報やprefabへの参照だけではなく、prefabインスタンスの変更部分だけに対するシリアライズ情報を持っていて、シーンをメモリーにロードするときにprefabへの参照からprefabインスタンスを作成してシーン内での変更部分をそのprefabインスタンスに適用するということはすでに述べました。
いくつかの制限とは、
- 「フィールド(変数)の変更」に関してはルートオブジェクトのTransformのPositionとRotation、Rect Transform のWidth、Height、Margins、Anchors、Pivot情報に関しては常にオーバーライドされている状態になり、元のprefabを変更してもその値はprefabインスタンスには反映されない
- 「子GameObjectの追加」においてはHierarchyビューの各階層の末端にしかGameObjectを追加できない
ということです。
前者はprefabインスタンスはたいていの場合、元のprefabとことなる位置と回転で作られるということ、後者はprefabの階層構造を変更はできないということ(編集情報としては特定の階層にGameObjectが追加されたことしか記録されない)。
またprefabインスタンスは元のprefabにあったGameObjectを削除することはできません、これもprefab内のGameObjectの階層構造を削除することはできないということを示しています。ただしGameObjectを非アクティブにすることはできます。
Unity2018.3以降はGameObjectを削除しようとすると「prefabインスタンスの再構築はできない(Cannot restructure Prefab instance)」という警告が出て、元のprefabを修正するようにprefab編集モードに飛ばされるダイアログが出ます。
UnityEditor上でこうした操作をおこなった後に、その結果を元のprefabへ適用(Apply)することも、シーン上に残しておいてシーンロード時に上書きして再現する(オーバーライド)することも可能です。
Unityユーザーの多くはこうした作業の使い分けを無意識のうちにおこなっていて、普段は問題ないのですが時として予想外の挙動にあうこともあります(後述するモデルデータのGameObject化の時に気をつける点など)。
オーバーライドされている場所は、それぞれで異なる表示になります。
- フィールド(変数)の変更
はインスペクター上で変更したフィールド名と要素が太い文字になり、表示の左端に青いラインが表示されます。
- コンポーネントの追加
は、コンポーネント名の左にある#アイコンに'+'が追加され、コンポーネント全体の左端に青いラインが表示されます。
- コンポーネントの削除
は、コンポーネント名の左のアイコンが'-'付きになり、コンポーネント名の表示が薄くなって"(Removed)"と追加されます。
- 子GameObjectの追加
は、Hierarchyビュー上のGameObjectのアイコンに'+'が追加されます。
オーバーライドのprefabへの適用
編集中のシーン内におかれているprefabインスタンスに対する編集内容はシーン内に記録されてます。
このオーバーライド状態のprefabインスタンスの内容を元のprefabに書き戻す適用(apply)という処理をおこなうことも可能です。この操作をおこなうとprefabインスタンスとprefabの内容は完全に一致しますし、オーバーライドの編集情報はすべて消去されてしまうので以降、prefabの内容は全てprefabインスタンスにも反映することになります。
Unty2018.3以前は、prefabを直接編集するモードはありませんでした。Projectビューでルートオブジェクトの中身を編集する以外は、シーン上にprefabインスタンスを作成して編集をおこないオーバーライドを作り、それを元のprefabに適用(apply)するしかなかったのです。
新しいprefabワークフローにおいてはprefabを直接編集できるようになったので、シーン上でprefabインスタンスを編集、結果のオーバーライドをprefabに適用(apply)する必要性はありません。シーン上にあるprefabインスタンスがオーバーライドを維持しているのも普通のことで、個々のprefabインスタンスの一部の要素のみを変更することによってシーン実行に様々なバリエーションを実現しようという意味も明確化されてきたということです。
そのために新しいprefabワークフローにおいてはprefabインスタンスのオーバーライドから一部のみを適用(apply)するという機能が拡充しました。
prefabインスタンス編集時のInspectorビューの上部にある「overrides ▼」を選択することによって、prefabインスタンス全体のオーバーライドからどの要素をprefabに適用(apply)するかを、元のprefab(Prefab Source)とprefabインスタンス(Override)の差分を見ながら選ぶことが可能です。
またInspectorビューで変更したコンポーネントのコンテキストメニュー(コンポーネント先頭の⋮ボタン)から、Modified Componentを選択することによって、コンポーネント単位での適用(apply)も可能になっています。
子GameObjectの追加についてはHierarchyビュー上でGameObject(またはprefabインスタンス)を直接選択してみ右ボタンから適用(apply)を選ぶことも可能です。
このようにシーン上にあえて一部の内容が変更されオーバーライドを持ったprefabをそのまま置いおくというのも新しいprefabワークフローの一つだといえます。
オーバーライドの機能の一般化
このように、拡張、整理されたインスタンスオーバーライド関連の機能ですが、その理由にはUnty2018.3以前からあるprefabインスタンスためだけというよりも、prefab全般に対して使えるようにしていこうという意図があります。
インスタンスオーバーライドはシーン内に元になるprefabへの参照と、そのprefabから作られたprefabインスタンスへの編集内容を記録することによって実現しています。
prefabバリアントとネステッドprefabの内部に他のprefabへの参照を持っているいう点ではシーンと同じような働きをします。
そして実はシーン同様にバリアントの元やネストしているprefabへの編集内容も記録して保存しているのです。
これがprefabバリアントとネステッドprefabのオーバーライドです。
prefabインスタンスのオーバーライドが、シーンにprefabインスタンスが作られたときにシーン内の編集内容でオーバーライドするものだとすれば、prefabバリアントのオーバーライドは元のprefabから派生したprefabバリアントに、バリアントに対する編集内容をオーバーライドするものです。
またネステッドprefabにおけるオーバーライドは、ネストされている子prefabに対する変更を親prefabにオーバーライド編集内容として保存して、親prefabのprefabインスタンスが作られるときに、子prefabのprefabインスタンスにオーバーライドする機能です。
こうしたオーバーライド機能は、元になったprefabの中身を派生、拡張していくものでオブジェクト志向における継承のようにprefabのバリエーションを作成、管理することを可能にします。
prefabのように使えるアセット
Unityではユーザーの利便性のため、いくつかの種類のアセットはUnityEditor上でprefab的な動作をするようになっています。
つまりproject上からHierarchyビュー(Sceneビュー)にドラッグ&ドロップすると元のアセットに紐づけられたGameObjectが作成されます。
これは元になったアセットがあたかもprefabであり、シーン上に作られたGameObjectがそのprefabインスタンスであるかのように、元のアセットへの変更がシーン上のGameObjectに反映されます。
またモデルデータに関してはprefab化さえも可能です。
これはアセットの変更が追加作業になしにスムーズにアプリにまで反映されるように考えられた特例だといえます。
アセットの種類 | 拡張子 | Hierarchyビュー(Sceneビュー) にドロップ |
バリアント化 |
---|---|---|---|
モデルデータ | .fbx等 | ◯ | ◯ |
テクスチャデータ | .png .jpg等 | ✖️ | ✖️ |
スプライトデータ | .png .jpg等 | ◯ | ✖️(Atlasに指定がある) |
オーディオデータ | .wav .mp3等 | ◯ | ✖️ |
特にモデルデータ(.fbx)はメッシュやマテリアルなど多くの参照を持っているので、これをゼロからGameObjectとして組み立てるのは容易ではなく、prefabのようにHierarchyビュー(Sceneビュー)して直接GameObject化できるのは大変便利な機能だといえるでしょう。
モデルデータのGameObject化の時に気をつける点
Unity2018.3以前で気をつけなければいけなかったのは**モデルデータ(.fbx)**を、Hierarchyビュー(Sceneビュー)にドロップして作成されたGameObjectにはその時点でのモデルデータ(.fbx)の全データが含まれてしまうということです。
モデルデータは多くのアセット自体と他に対する参照を含んでいます。
こうしてモデルデータ(.fbx)から作られたGameObjectをHierarchyビュー(Sceneビュー)上で編集をおこない、prefab化しないでGameObjectのままSceneファイルとしてストレージへセーブすると、次にSceneファイルを使うためにロードした時に、いったんモデルファイルに含まれた全データを読み込みGameObjectを作成した後に、インスタンスオーバーライドによってGameObjectの中身を上書きしていって編集済みのGameObjectを再現します。
つまりメッシュデータやマテリアル(テクスチャー)等の変更を行った後にprefab化しないでSceneセーブをおこなってしまうと、次にSceneのロードをおこなったとき、モデルファイルに含まれるメッシュやマテリアルがロードされてから、Sceneのオーバーライドによって新しいメッシュやマテリアルも読み込まれてしまうので、これらデーターがメモリー上で二重持ちになってしまいます。
さらに悪いことに、アーティストはSceneを作る時にScene上にモデルデータをprefab化しないで置くような方法を好みます。
これはモデルデータから作られたGameObjectをprefab化してしまうと、GameObjectは元のモデルファイルのインスタンスからprefabのインスタンスに変更されてしまい、アーティストがDCCツールで3Dデータの変更をおこないモデルデータ(.fbx等)の差し替えをおこなっても、シーン上のGameObjectはもはやモデルファイルのインスタンスではなく関連づけを失ってるために壊れてしまうことになるためです。そのため再度GameObjectの作成とScene上での変更をおこなわなければならなくなりません。
これを解決するために、新しいprefabワークフローでは、projectビュー上でモデルデータのバリアントを作成して(元のモデルデータを選択して右ボタン、Create>Prefab Variantで「モデルデータ名 Variant」というprefabが作られる)、そのprefabバリアントをHierarchyビュー(Sceneビュー)にドロップしてGameObject化する方法が選択できます。
prefabバリアントとはScene上でしかおこなえなかったオーバーライドをprefab間(元のprefabとprefabバリアント)でもおこなえるようにしたものです。
prefabバリアントの最大の特徴はオーバーライドの適用が、ビルド時におこなわれてしまっていることです。
アプリ実行時のprefabインスタンス作成では、完成されたprefabに対してinstantiateをおこないます。
つまりアセットの上書き等もビルド時に終了してしまっているので、アプリ実行時にオーバーライド前のアセットとオーバーライド後のアセットの双方が存在するといったことは避けられます。バリアント化したprefab上でメッシュやマテリアルの変更をおこなえば、Sceneにロードされる前に変更は解決されているのでデータ二重持ちにはならないのです。
そしてアーティストがモデルデータの変更をおこなう場合、モデルデータのReimportをおこない、prefabバリアントのReimportもおこなえば変更は伝播してScene上にあるprefabインスタンスにも反映されることになります。
新しいprefab機能
新しいprefab機能についてUnityは三つあげています。
1.ネステッドprefab
2.prefabバリアント
3.prefabモードでの編集
ネステッドprefab(マニュアルでは「ネスト状プレハブ」)はprefab内に他のprefabを直接含めることができるものです。
Unity2018.3以前はprefabには、展開されたGameObjectしか含めることができませんでした。
前述したようにネステッドprefabをinstantiateすると含まれているprefabも、親のprefabと同時にinstantiateされます。
prefabバリアントは、簡単にいうとprojectビューの中でprefabインスタンスを作ることができるものです。
実際、prefabAというプレハブのバリアントprefabA Variantを作って作成された"prefabA Variant.prefab"をUnity外で、テキストエディタ等で開いてみると、先頭部分には
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &7212557038290248691
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
というようにPrefabInstance
という記述があります。
prefabモードは、projectビュー上にある特定のprefabを選択して直接編集できるようにしたものです。
以前は、prefabをHierarchyビュー(Sceneビュー)上にドロップしてから編集(インスタンスオーバーライド)して、結果を適用(Apply)する必要がありました。そのためprefabを変な場所に落としてしまったり、編集用に空のシーンを用意したりと面倒なことも色々ありました。
prefab編集モードによってそうした手間はなくなりました。
prefab編集モードに入るとUnityエディターはシーンの編集をおこなうMainステージからprefab編集モードのステージに移行します。
このステージで編集可能なのはそのprefabのみとなり、また即座に元のシーン編集モードのMainステージに戻ることができます。
逆に「新しいprefabワークフロー」によって狭義のprefab編集作業はこのprefab編集モードかprefabのroot、GameObjectのみprojectビュー上で直接しかできなくなってしまいました。
シーン上にあるprefabインスタンスにおこなえるのはオーバーライドのみで編集作業に制限が出てしまいます。
制限を超えた編集をおこなおうとするとprefab編集モードでおこなうようにダイアログがでるようになりました。
こうした新しい機能の背景にあるのがインスタンスオーバーライドです。
旧prefabワークフローにおいては、オーバーライドをおこなうのはシーンだけでした。
シーン上にあるprefabインスタンスにはインスタンスオーバーライドができました。これはシーンはprefabの編集(オーバーライド)情報を持っていてシーンを読み込んでprefabの参照情報からprefabを読み込んでそのprefabインスタンスを作成した後に、それを使用してprefabインスタンスの内容を上書くことによって実現しています。
前述したように、これはアプリケーションの実行時にもおこなわれます(結果として元々のprefabが持っていたリソースと後からオーバーライドされたリソースがメモリー上に同時に存在してしまうこともあるということは、モデルデータを直接シーン上でGameObject化する時の問題点の項で前述しましたが)。
新しいprefabワークフローにおいては、このオーバーライド情報をprojectビュー(つまりUnityEditorでの編集時)のみではありますが、すべてのprefabが持つことになりました。シーン上でのインスタンスオーバーライドと異なるのは、prefabバリアントの場合この解決が、アプリケーションの実行時ではなくて、アプリケーションのビルド時に静的におこなわれるということです。つまりビルド時に適用(Apply)されたprefabの作成が自動的におこなわれていることになります。
全てのprefabがオーバーライド情報を持つというのはどういうことでしょうか?
ネステッドprefabの場合は、子プレハブの編集内容は親prefabがオーバーライドとして持つことになります。またインスタンスオーバーライド同様に任意の階層の子プレハブに適用(Apply)することが可能になります。
任意の子プレハブとはどういうことかというと、親、子、孫と三階層のネステッドprefabがあった場合、孫prefabへの編集結果は最初には親prefabが持つオーバーライド情報となりますが、これを子prefabへ適用(Apply)することによって、子prefabが持つ孫prefabへのオーバーライド情報に変換されることになります。または親prefabから孫prefabへ直接適用(Apply)することもでき、その場合は孫prefabの内容自体が変更されることになります。
prefabバリアントの場合は、prefabバリアントとは元になったprefabへの参照情報とオーバーライド情報のみを持つ特別なprefabとなります。元prefabの内容に対してインスタンスオーバーライドの制限内で自由に編集をおこなうことが可能で、その適用(Apply)は実行前におこなわれていて、実行時にオーバーライドの解決はおこなわれません。
実行時に解決をおこなわないということは重要で。
これはアプリビルド時に参照情報とオーバーライド情報から完全なprefabが作成されているということで、アプリ実行時にはprefabバリアントは通常のprefabデータとなり個々に全prefabデータをコピーして持つことになります。これはprefabとしてはデーターが増えてしまうことになりますが、前述の「モデルデータのGameObject化の時に気をつける点」のように実行時に余分な参照が走ってしまうことはありません。
prefabの展開は新しいワークフローでは補助的な機能で、独立した機能としてあげられることはありませんが重要な機能です。
シーン上やprefabモードでシーンやprefabに含まれたprefabを選択して、prefabの展開をおこなうことができます。
この機能は、prefabインスタンスにオーバーライドの適用(Apply)をおこなった後に単なるGameObjectへ変換します。結果、元のprefabへの関連性を全て失います。
prefabの展開は「Unpack」と「Unpack Completely」から選択でき、前者は選択したprefabのみのGameObjectへの変換、後者は選択したprefab自身とそれが含むネステッドprefabの子prefabへと階層を辿っていって展開をおこない全てをGameObjectにまで変換します。
この機能を、prefabバリアントに使用した場合、結果は特徴的です「Unpack」を行った場合はprefabバリアントはprefabバリアントの元になったprefabのprefabインスタンスに変換されます。そしてprefabバリアントが持っていたオーバーライド情報は、編集中のシーンやprefab全体が持つオーバーライド情報へと統合されます。
「Unpack Completely」をおこなった場合は、prefabバリアントは自身のオーバーライド情報を適用(Apply)した後に、GameObjectへ変換されます。
こういった自由度の高いオーバーライドと適用(Apply)は複数レベルへの選択的なオーバーライド情報の統合等を含みます。
ネステッドprefab | prefabバリアント | |
---|---|---|
ビルド時に適用 | 「prefabの展開」を使う | ◯ |
実行時に適用 | ◯ | × |
ネステッドprefabは実行時に適用され、prefabバリアントはアプリのビルド時に適用されるというのは、Unityの元々の仕組みで変更することはできません、ネステッドprefabを実行時以前に解決するには「prefabの展開」をおこなってやればいいのですが、prefabバリアントを実行時に解決する方法は今のところ用意されていないようです。つまりネステッドprefabとprefabバリアントはデータの運用という面では大きな違いがあるといえます。
prefabバリアントはビルド時にオーバーライドの適用がおこなわれために、バリアント個々が完全なGameObjectとしての全データを持つことになります。このためprefabバリアントを増やせばそのぶん単純に使用するデータ量は増えていくようにも思えますが、実際には前述の「モデルデータのGameObject化の時に気をつける点」に述べたように実行時にオーバーライドの適用がおこなわれるということはその分処理が走ることになりますし、実行中にオーバーライド前と後の状態が存在することになりprefabバリアントでなければ不必要なデーターの読み込みがおこなわれてしまうことがあります。
このようにケースバイケースで変わるので、よく状況を見極めなければなりません。
ネステッドprefabとprefabバリアント
ネステッドprefabとprefabバリアントはオブジェクト指向用語としては**has-a(包含)とis-a(継承)**にあたります。
**has-a(包含)**は、たとえば自動車がシャーシ、タイヤやエンジンなどの部品を集めて組み立てた物だということを表現するのに使われます。
ネステッドprefabを使ってもこれを表現できます(他には、自動車GameObjectにシャーシやエンジン、タイヤといったコンポーネントを追加するといった方法も考えられます)。
自動車prefabには、シャーシprefab、タイヤprefab、エンジンprefabを含めることになります。
ネステッドprefabはhas-aが想定しているような自由なフィールドを使っておこなえる参照ではなく、GameObjectが必ず持つtransformコンポーネントの位置情報のparent(親)、childs(子供)プロパティによる階層構造によって関係が繋がっています。
このため3D空間中での各位置情報が親に依存することになり自動車の位置によってシャーシprefab、タイヤprefab、エンジンprefabの位置が変化するシーングラフという構造を持つことになります。
**is-a(継承)**は、たとえば自動車を細かく分類したものが、セダンやワゴン、バンといった車種になるということを表現するのに使われます。
prefabにおいては、prefabバリアントを使ってこれを表現できます。
自動車prefabのprefabバリアントとしてセダンprefab、ワゴンprefab、バンprefabを作成して個別の特殊化した要素をオーバーライドとして設定することになります。
従来は、こうした図表は概念を示すための抽象的な表現でしかなく、オブジェクト指向システムの設計によって直接「自動車」のモデルが構築できたり、それを3D画面に表示できるものでもありませんでした、しかしUnityにおいてこうした設計はゲーム画面やシミュレーション画面において実際に「自動車」を表現するための基礎にすることもでき抽象的な概念にとどまらないということになります。
新しいprefabワークフロー
新しいprefabワークフローは編集時の利便性と実行時の効率性の両方のため、拡張されたprefab機能を使いこなす方法だといえます。
拡張されたと共に、制約も生まれたりしているので操作する前に一寸立ち止まって操作の影響について考えてみたほうがいいときもあるかもしれません。
小規模に使いか回しているぶんには概ね正しく動いていて問題が無いのですが、規模が大きくなったときに矛盾や非効率性も大きくなってしまうこともあるからです。
最初に、新しいprefabワークフローについてこうして調べようと思ったのは、Assetbundleのバリアント機能がこの機能の提供に伴い非推奨となってしまったからです。
新機能として主にネステッドprefabのほうに目が向いていたのでprefabバリアントによってどうしてAssetbundleのバリアント機能が不要になるかがわからなかったということがあります。
考えてみるとAssetbundleのバリアント機能は少々不安定な機能で、バリアントなAssetbundleは同一名のアセットを同一の構造で複数用意してやらなければならないという面倒な準備が必要なうえに、この機能によってAssetbundleビルドにアセットのユニークIDではなく名前による参照が入り込んできてしまい、ビルド時にはバリアントに関係ないところであっても同一名のアセットがあるというエラーが出るようになったりして大変でした。
またAssetbundleのバリアント機能はAssetbundleからアセットをロードするときに解決されるので、Assetbundle関係ツールで用意されていたシミュレーションモード等では動作せず、実際にAssetbundleを作成して動作を確認してやる必要がありました。
比較してprefabバリアントはAssetbundleとは無関係の機能で、実際にprefabのバリエーションは作成されていますが、それをUnityEditor上で効率的に管理、編集するための機能ということになります。このためAssetbundleのシミュレーション機能等においても問題なく動作します。
このようにprefabワークフローはUnityEditor上で確認できる安定的な機能ですが、効率性までをも考慮すると、前述のようにネステッドprefabとprefabバリアントによって解決のタイミングが、「実行時」と「ビルド時」と違っていたりして頭を使う必要があります。
近年のUnity機能全般にいえるのですが、かつてのUnityは機能の幅が狭かったためか普通に作っていれば、適切な設定でそれなりに効率的なアプリが作れていたのですが、最近では機能の幅が広くなったために適当に作っていると動きはするが大変非効率的なアプリになってしまい、そうならないように構成や設定をよく考える必要があります。prefabワークフローもその例にむれず、作業効率に加えてアプリの実行効率まで考えると、それなりの熟慮が必要だということになりますね。