/
PrefabHelper.cs
288 lines (244 loc) · 12.7 KB
/
PrefabHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
using UnityEngine;
using UnityEditor;
using UnityEditor.Experimental.SceneManagement;
namespace Wappen.Editor
{
/// <summary>
/// Helps determine every aspect of GameObject prefab state.
/// </summary>
public static class PrefabHelper
{
// Good explanation is here:
// https://docs.unity3d.com/ScriptReference/PrefabUtility.IsOutermostPrefabInstanceRoot.html
public class Properties
{
readonly GameObject originalGameObject;
public Properties( GameObject g )
{
originalGameObject = g;
}
/* Prefab stage ///////////////////////*/
/// <summary>
/// Is root or child node under prefab stage.
/// </summary>
public bool isPartOfPrefabStage;
/// <summary>
/// This node is root of editing prefab stage.
/// </summary>
public bool isPrefabStageRoot;
/* Prefab instance /////////////////////*/
/// <summary>
/// Is root or child node under prefab instance.
/// </summary>
public bool isPartOfPrefabInstance;
/// <summary>
/// This node is prefab instance root.
/// But it could be positioned in scene, under prefab stage or under prefab asset.
/// </summary>
public bool isPrefabInstanceRoot;
/// <summary>
/// Get node that is nearest instance root above this node.
/// Or itself if isPrefabInstanceRoot=true.
/// </summary>
public GameObject nearestInstanceRoot;
/* Prefab asset ////////////////////////*/
/// <summary>
/// Is root of prefab asset.
/// </summary>
public bool isPrefabAssetRoot;
/// <summary>
/// Is root or part of prefab asset.
/// </summary>
public bool isPartOfPrefabAsset;
/// <summary>
/// Type of prefab asset, only if isPartOfPrefabAsset=true.
/// </summary>
public PrefabAssetType prefabAssetType;
/// <summary>
/// This is variant prefab of other prefab asset.
/// </summary>
public bool isPrefabAssetVariant => prefabAssetType == PrefabAssetType.Variant;
/// <summary>
/// Nearest Asset path of prefab around selected gameObject.
/// null if selected object is not part of any prefab.
/// </summary>
public string prefabAssetPath;
/// <summary>
/// Valid only when isPrefabAssetRoot = true.
/// Top most prefab asset root.
/// Is this object itself when isPrefabAssetRoot = true, could be otherwise when false.
/// </summary>
public GameObject prefabAssetRoot;
/* Misc /////////////////////////////////*/
/// <summary>
/// This is object placed into scene.
/// </summary>
public bool isSceneObject => (!isPartOfPrefabAsset && !isPartOfPrefabStage);
/// <summary>
/// This game object is root or part of any prefab instance or asset prefab or prefab in prefab stage.
/// </summary>
public bool isPartOfAnyPrefab => prefabAssetPath != null;
/// <summary>
/// This game object is indeed root of some prefab.
/// </summary>
public bool isRootOfAnyPrefab => isPrefabAssetRoot || isPrefabInstanceRoot || isPrefabStageRoot;
/* Extra queries ////////////////////////*/
/// <summary>
/// Walk one level up of prefab inheritance step.
/// If this object is variant, this returns object it created from. (Could be another variant or prefab asset)
/// If this object is prefab instance, this returns prefab it instanced from.
/// </summary>
public GameObject GetSourcePrefab( )
{
return PrefabUtility.GetCorrespondingObjectFromSource( originalGameObject );
}
/// <summary>
/// It is like calling GetSourcePrefab (GetCorrespondingObjectFromSource) in chain all the way to the base.
/// </summary>
public GameObject GetFirstSourcePrefab( )
{
return PrefabUtility.GetCorrespondingObjectFromOriginalSource( originalGameObject );
}
}
/// <summary>
/// Get every basic prefab-related info about this node.
/// </summary>
public static Properties GetPrefabProperties( GameObject gameObject )
{
if( gameObject == null )
return null;
Properties p = new Properties( gameObject );
#if false // Does not required anymore, also generate warning in Editor
// Wappen hack: From https://forum.unity.com/threads/problem-with-prefab-api-upgrade.537074/
// Check if it is nesting prefab
//bool isPartOfPrefabInstance = PrefabUtility.IsPartOfPrefabInstance(gameObject);
#endif
// First group of check is to test everything about "Prefab Asset"
{
// First is to determine isPartOfPrefabAsset
p.isPartOfPrefabAsset = PrefabUtility.IsPartOfPrefabAsset( gameObject );
// Second alternate test, using direct AssetDatabase
if( !p.isPartOfPrefabAsset )
p.isPartOfPrefabAsset = !string.IsNullOrEmpty( AssetDatabase.GetAssetPath( gameObject ) );
// If it is under prefab asset, determine its root
// Use gameObject.transform.root.gameObject to test for prefab asset root.
if( p.isPartOfPrefabAsset )
{
p.prefabAssetRoot = gameObject.transform.root.gameObject;
p.isPrefabAssetRoot = (gameObject == p.prefabAssetRoot);
p.prefabAssetType = PrefabUtility.GetPrefabAssetType( gameObject );
}
}
// Second group of check is to test everything about "Prefab Instance"
// This is when you instance an prefab asset into scene or nested under other prefab.
// First check is obvious, it should not and cannot be prefab asset root in the first place.
if( !p.isPrefabAssetRoot )
{
// Use PrefabUtility.GetNearestPrefabInstanceRoot to test for prefab instance root.
/* Basic visualization
* The node with its [Name] inside bracket is blue gameobject node.
* See image in https://docs.unity3d.com/ScriptReference/PrefabUtility.GetNearestPrefabInstanceRoot.html
* For possible prefab configuration
*
* - [OUTERMOST]
* - CHILD 1
* - CHILD 2
* - [prefab instance head] < If we are running on this node, isPrefabInstanceRoot = true
* - child 1
* - child 2 < If we are running on this node, isPrefabInstanceRoot = false, but we still have nearestInstanceRoot points to 'prefab instance head'
*/
// Note: "PrefabUtility.GetNearestPrefabInstanceRoot" Causes following warning message on editor:
// SendMessage cannot be called during Awake, CheckConsistency, or OnValidate
// If this API is called in such timing, it is unavoidable, live with it
// https://forum.unity.com/threads/sendmessage-cannot-be-called-during-awake-checkconsistency-or-onvalidate-can-we-suppress.537265/
p.nearestInstanceRoot = PrefabUtility.GetNearestPrefabInstanceRoot( gameObject );
p.isPartOfPrefabInstance = (p.nearestInstanceRoot != null);
p.isPrefabInstanceRoot = gameObject == p.nearestInstanceRoot; // Equivalent to PrefabUtility.IsAnyPrefabInstanceRoot, see UnityReferenceSource
}
// Third test is testing everything about Prefab stage property
// needed to be checked separately as it is very special rule
var editorPrefabStage = PrefabStageUtility.GetCurrentPrefabStage( );
if( editorPrefabStage != null )
{
// We are in prefab stage, but is selected gameobject really an object from prefab stage?
// There is no 100% direct API to determine this, plus there is following observed obstacle when in prefab stage:
// - root object editing in prefab stage wont have connection to its original prefab (it becomes gray gameobject icon), so we cannot test it with any prefab API. (bad)
// - Also cannot test for 'editorPrefabStage.prefabContentsRoot == gameObject.transform.root.gameObject' directly, both seems to always be different object instance.
// but we could derive from the following fact:
// If it was NOT under prefab asset (from above test)
// we can almost sure that it is in prefab stage
if( p.isPartOfPrefabAsset == false )
p.isPartOfPrefabStage = true;
// Editor prefab stage root object wont have transform.parent
// That's how we determine if it is isPrefabStageRoot
if( p.isPartOfPrefabStage && gameObject.transform.parent == null )
p.isPrefabStageRoot = true;
}
// AssetPath determination
// PrefabHelper will try its best to detect most accurate prefab path for this prefab
// Has to be done separately based on API priority
if( p.isRootOfAnyPrefab )
{
if( p.isPrefabStageRoot )
{
p.prefabAssetPath = editorPrefabStage.prefabAssetPath;
}
else if( p.isPrefabInstanceRoot ) // It is nested prefab instance inside another prefab
{
// Note: GetPrefabAssetPathOfNearestInstanceRoot is the only way to obtain real asset path of this nested prefab instance
// internally it uses PrefabUtility.GetOriginalSourceOrVariantRoot which is internal function to Unity, see UnityReferenceSource
// FYI: Calling GetAssetPath will get the "top/outermost" one. Not this nested one. (dont want that)
p.prefabAssetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot( p.nearestInstanceRoot );
}
else if( p.isPrefabAssetRoot )
{
// Ordinary prefab asset root
p.prefabAssetPath = AssetDatabase.GetAssetPath( gameObject );
}
}
else
{
// This object is not root of any prefab, but could still be part of some prefab, find that nearest asset path
if( p.isPartOfPrefabStage )
{
p.prefabAssetPath = editorPrefabStage.prefabAssetPath;
}
else if( p.isPartOfPrefabInstance )
{
p.prefabAssetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot( p.nearestInstanceRoot );
}
else if( p.isPartOfPrefabAsset )
{
p.prefabAssetPath = AssetDatabase.GetAssetPath( gameObject.transform.root.gameObject );
}
}
return p;
}
public static bool IsPartOfPrefabStage( GameObject gameObject, out PrefabStage pfs )
{
pfs = PrefabStageUtility.GetPrefabStage( gameObject );
return pfs != null;
}
public static bool IsRootOfPrefabStage( GameObject gameObject, out PrefabStage pfs )
{
// Note: Cannot use pfs.IsPartOfPrefabContents or pfs.prefabContentsRoot
// InvalidOperationException: Requesting 'prefabContentsRoot' from Awake and OnEnable are not supported
if( IsPartOfPrefabStage( gameObject, out pfs ) && IsRootOfScene( gameObject ) )
return true;
return false;
}
public static bool IsRootOfScene( GameObject gameObject )
{
if( gameObject.transform.parent == null )
{
// Note: on first frame of entering prefab stage. It will called with scene not loaded
if( gameObject.scene.isLoaded == false )
return true;
// Also see if it is first root object in stage
GameObject[] allRoots = gameObject.scene.GetRootGameObjects( );
return allRoots[0] == gameObject;
}
return false;
}
}
}