【Unity】【エディタ拡張】Prefabバリアントから派生元のPrefabやコンポーネントを得るメソッドと謎の挙動

PrefabUtilityに定義されているメソッドのうち、下の記事で適当に流してしまった
GetCorrespondingObjectFromSource()GetCorrespondingObjectFromOriginalSource()について改めて調べました。

light11.hatenadiary.com

これら二つのメソッドの違いと、おかしな挙動を発見したのでまとめます。

Unity2018.3.12

二つのメソッドの違い

いま、prefab_0という名前のPrefabがあるとします。
このprefab_0からバリアントprefab_1を派生させます。
さらに、prefab_1からバリアントprefab_2を派生させます。

これらを図でまとめると次のような派生関係になります。

f:id:halya_11:20190418201012p:plain

ここで、prefab_2に対してGetCorrespondingObjectFromSource()を使うと一つ上のprefab_1が取得できます。

f:id:halya_11:20190418201524p:plain

一方、prefab_2に対してGetCorrespondingObjectFromOriginalSource()を使うとprefab_0が取れます。

f:id:halya_11:20190418201452p:plain

つまり、PrefabのSourceとは位置階層上の派生元のPrefabを指し、OriginalSourceとは大元のPrefabを指すようです。

実装はC# Referenceに載ってる、けど・・・

このあたりのソースコードは公開されているのでC# Referenceを見るとわかります。

github.com

実装は単純で、GetCorrespondingObjectFromOriginalSource()は内部的にGetCorrespondingObjectFromSource()を何回も読んでるだけです。

private static Object GetCorrespondingObjectFromOriginalSource_Internal(Object instanceOrAsset)
{
    var sourceObjectInPrefabAsset = instanceOrAsset;

    if (!EditorUtility.IsPersistent(sourceObjectInPrefabAsset))
    {
        sourceObjectInPrefabAsset = GetCorrespondingObjectFromSource(sourceObjectInPrefabAsset);
        if (sourceObjectInPrefabAsset == null)
            return null;
    }

    while (true)
    {
        var inner = GetCorrespondingObjectFromSource(sourceObjectInPrefabAsset);
        if (inner == null)
            return sourceObjectInPrefabAsset;
        sourceObjectInPrefabAsset = inner;
    }
}

でもこれ最初にvar sourceObjectInPrefabAsset = instanceOrAsset;のように自身のインスタンスを変数に入れているので、派生元が無かったら自身が返ってしまう気がします。
その場合派生先のバリアントでオーバーライドしたGameObjectやコンポーネントに対して使った時に期待する結果が得られなそうです。

試してみる

そんなわけで実際に試してみます。

まずprefab_0のバリアントprefab_1にCharacterControllerをアタッチします。Applyはしません。

f:id:halya_11:20190418201930p:plain

この状態でCharacterControllerに対してGetCorrespondingObjectFromSource()GetCorrespondingObjectFromOriginalSource()を使ってみます。

var prefab = Selection.activeObject as GameObject;
var controller = prefab.GetComponent<CharacterController>();
Debug.Log(PrefabUtility.GetCorrespondingObjectFromSource(controller));
Debug.Log(PrefabUtility.GetCorrespondingObjectFromOriginalSource(controller));

期待する結果としては、派生元にCharacterControllerがアタッチされていないため両方ともnullになりそうなものですが、
前節の理由からGetCorrespondingObjectFromOriginalSource()の方だけ自身のインスタンスが返ってきました。

f:id:halya_11:20190418195843p:plain

こういう仕様なのかよくわかりませんが、使うときには注意が必要そうです。

オーバーライドを取りたければGetAddedComponents()

ちなみに上記のようなケースで、例えばバリアントでオーバーライドしたコンポーネントを得たいときにはGetAddedComponents()を使うのがよさそうです。

var prefab = Selection.activeObject as GameObject;
var overrideComponents = PrefabUtility.GetAddedComponents(prefab);

また、オーバーライドしたGameObjectを得たいときにはGetAddedGameObjects()を使うのがよさそうです。

var prefab = Selection.activeObject as GameObject;
var overrideGameObjects = PrefabUtility.GetAddedGameObjects(prefab);