【Unity】【エディタ拡張】スクリプトからPrefabを操作する(Unity2018.3以降)

スクリプトからPrefabを操作する方法です。
Unity2018.3以降の新Prefabワークフローに対応した内容となっています。

Unity2018.2以前はこちらの記事を参照してください。

light11.hatenadiary.com

Unity2018.3.1f1
※本記事のスクリプトはすべてUnityEditorをusingしています

Prefabをインスタンス化する

Prefabインスタンスをシーンに配置します。

Object assetComponentOrGameObject;

// PrefabのコンポーネントやGameObjectを渡すと、
// Prefabのインスタンスをシーンに配置する
PrefabUtility.InstantiatePrefab(assetComponentOrGameObject);

GameObject.Instantiate()を使うとUnpackされた状態でインスタンス化されます。

GameObject prefab;
// Unpackされた状態のインスタンスがシーンに置かれる
GameObject.Instantiate(prefab);

Prefabを生成/上書きする・Variantを作る

Prefabを生成/上書きするには次のようにします。

GameObject gameObject;
string assetPath;
// Prefabを新規作成・上書きする
// ただし既にPrefab化されていてPrefabと違うパスを指定した場合はVariantを作る
PrefabUtility.SaveAsPrefabAsset(gameObject, assetPath);

コメントに書いた通り、この関数は少し挙動が複雑です。
まず、まだPrefabが生成されていない場合には新規生成されます。
次に、既にPrefabが存在しているパスを引数に渡すとPrefabを上書きします。
さらに、引数にPrefabのインスタンスを渡し、かつパスがPrefabのものでない場合にはPrefab Variantが作られます。

また、上記の関数ではgameObjectがPrefabと関連付きません。
Prefabを作ってさらに関連付けるためにはSaveAsPrefabAssetAndConnect()を使います。

GameObject gameObject;
string assetPath;
// Prefabを作成or上書きして紐づける
PrefabUtility.SaveAsPrefabAssetAndConnect(gameObject, assetPath, InteractionMode.AutomatedAction);

最後の引数をInteractionMode.UserActionにするとUndoが効くようになります。
ユーザが何かのボタンを押すことでPrefabが作れるようなスクリプトの場合にはこっちを使ったほうがよさそうです。

Prefabを編集して上書き保存する

Prefabを編集して保存するにはPrefabを今のシーンにインスタンス化してもいいのですが、
PrefabUtility.LoadPrefabContents()を使ったほうがスマートです。
このメソッドはPrefabを内部的なシーン上にロードします。

使い方はこんな感じです。Unloadをお忘れなく。

// 内部的なシーンにPrefabをロードする
var contentsRoot = PrefabUtility.LoadPrefabContents(prefabPath);

// 変更を加える
var gameObject = new GameObject();
gameObject.transform.SetParent(contentsRoot.transform);

// 保存する
PrefabUtility.SaveAsPrefabAsset(contentsRoot, prefabPath);
PrefabUtility.UnloadPrefabContents(contentsRoot);

これに関しては以下の記事にも詳しくまとめています。

light11.hatenadiary.com

UnpackしてインスタンスとPrefabとの接続を切る

UnpackしてインスタンスとPrefabとの接続を切ります。

GameObject instanceRoot;

// インスタンスのルートを渡してPrefabとの接続を解除する
PrefabUtility.UnpackPrefabInstance(instanceRoot, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);

第二引数をPrefabUnpackMode.OutermostRootにするとNestedしている場合に最も近いPrefabとの接続を切ります。
PrefabUnpackMode.CompletelyにするとNestedしているすべてのPrefabとの接続を切ります。

また、Prefab生成時と同様に最後の引数をInteractionMode.UserActionにするとUndoが効くようになります。

Apply系の操作を行うメソッド一覧

Apply系の操作を行うメソッドの一覧です。

メソッド名 説明
ApplyPrefabInstance() インスタンスで変更されたすべての値をPrefabに反映する
ApplyObjectOverride() インスタンスで変更された値を引数のGameObject/コンポーネントの単位でPrefabに反映する
ApplyPropertyOverride() インスタンスで変更されたプロパティをPrefabに反映する
ApplyAddedGameObject() インスタンスに追加されたGameObjectをPrefabに反映する
ApplyAddedComponent() インスタンスに追加されたコンポーネントをPrefabに反映する
ApplyRemovedComponent() インスタンスで削除されたコンポーネントをPrefabに反映する

Revert系の操作を行うメソッド一覧

Revert系の操作を行うメソッドの一覧です。

メソッド名 説明
RevertPrefabInstance() インスタンスで変更されたすべての値を元に戻す
RevertObjectOverride() インスタンスで変更された値を引数のGameObject/コンポーネントの単位で元に戻す
RevertPropertyOverride() インスタンスで変更されたプロパティを元に戻す
RevertAddedGameObject() インスタンスに追加されたGameObjectを元に戻す
RevertAddedComponent() インスタンスに追加されたコンポーネントを元に戻す
RevertRemovedComponent() インスタンスで削除されたコンポーネントを元に戻す

インスタンスから対応するPrefabを探す

インスタンスの持つコンポーネントやGameObjectから、
そのPrefabの中の対応するコンポーネントやGameObjectを取得できます。

Object componentOrGameObject;

// インスタンスのコンポーネントやGameObjectを渡すと、
// Prefabの中の対応するコンポーネントやGameObjectを返す
Selection.activeObject = PrefabUtility.GetCorrespondingObjectFromOriginalSource(componentOrGameObject);

似たメソッドにPrefabUtility.GetCorrespondingObjectFromSource()があります。
こちらは一度Prefabと再接続を行うときに使うとのことです(理解できてない)。

docs.unity3d.com

インスタンスの一部からインスタンスのルートのGameObjectを得る

インスタンスの持つコンポーネントやGameObjectから、インスタンスのルートにあるGameObjectを取得します。

PrefabUtility.GetOutermostPrefabInstanceRoot()を使うと、一番外側のルートGameObjectを取得します。
つまり、入れ子になっていた場合には一番親のルートGameObjectが返されます。

Object componentOrGameObject;

// Prefabのインスタンスの一部(コンポーネントやGameObject)から
// PrefabのインスタンスのルートGameObjectを取得する
// Nestedである場合には一番親が返される
Selection.activeObject = PrefabUtility.GetOutermostPrefabInstanceRoot(componentOrGameObject);

PrefabUtility.GetOutermostPrefabInstanceRoot()を使うと、一番近くのルートGameObjectを取得します。
つまり、入れ子になっていた場合には子のルートGameObjectが返されます。

Object componentOrGameObject;

// Prefabのインスタンスの一部(コンポーネントやGameObject)から
// PrefabのインスタンスのルートGameObjectを取得する
// Nestedである場合には子が返される
Selection.activeObject = PrefabUtility.GetNearestPrefabInstanceRoot(componentOrGameObject);

Prefabモードで開く

PrefabをPrefabモードで開くにはAssetDatabase.OpenAsset()を使います。

AssetDatabase.OpenAsset(prefab);

関連

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

参考サイト

とりあえず使いそうなものだけまとめたので全貌はスクリプトリファレンスをご覧ください。

docs.unity3d.com