@@ -207,6 +207,112 @@ public static GameObject[] CreateInstantiatedModelPrefab(
207
207
}
208
208
return converted . ToArray ( ) ;
209
209
}
210
+
211
+ /// <summary>
212
+ /// For all scene objects holding a reference to origObj, replaces the references to newObj.
213
+ ///
214
+ /// If one of the scene objects is toConvertRoot or a child of it, then do not fix its references as it
215
+ /// will be deleted after conversion.
216
+ /// </summary>
217
+ /// <param name="origObj"></param>
218
+ /// <param name="newObj"></param>
219
+ /// <param name="toConvertRoot"></param>
220
+ internal static void FixSceneReferences ( Object origObj , Object newObj , GameObject toConvertRoot )
221
+ {
222
+ var sceneObjs = GetSceneReferencesToObject ( origObj ) ;
223
+
224
+ // try to fix references on each component of each scene object, if applicable
225
+ foreach ( var sceneObj in sceneObjs )
226
+ {
227
+ var go = ModelExporter . GetGameObject ( sceneObj ) ;
228
+ if ( go && go . transform . IsChildOf ( toConvertRoot . transform ) )
229
+ {
230
+ // if this is a child of what we are converting, don't update its references.
231
+ continue ;
232
+ }
233
+
234
+ var components = sceneObj . GetComponents < Component > ( ) ;
235
+ foreach ( var component in components )
236
+ {
237
+ var serializedComponent = new SerializedObject ( component ) ;
238
+ var property = serializedComponent . GetIterator ( ) ;
239
+ property . Next ( true ) ; // skip generic field
240
+ // For SkinnedMeshRenderer, the bones array doesn't have visible children, but may have references that need to be fixed.
241
+ // For everything else, filtering by visible children in the while loop and then copying properties that don't have visible children,
242
+ // ensures that only the leaf properties are copied over. Copying other properties is not usually necessary and may break references that
243
+ // were not meant to be copied.
244
+ while ( property . Next ( ( component is SkinnedMeshRenderer ) ? property . hasChildren : property . hasVisibleChildren ) )
245
+ {
246
+ if ( ! property . hasVisibleChildren )
247
+ {
248
+ // with Undo operations, copying m_Father reference causes issues. Also, it is not required as the reference is fixed when
249
+ // the transform is parented under the correct hierarchy (which happens before this).
250
+ if ( property . propertyType == SerializedPropertyType . ObjectReference && property . propertyPath != "m_GameObject" &&
251
+ property . propertyPath != "m_Father" && property . objectReferenceValue &&
252
+ ( property . objectReferenceValue == origObj ) )
253
+ {
254
+ property . objectReferenceValue = newObj ;
255
+ serializedComponent . ApplyModifiedProperties ( ) ;
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ /// <summary>
264
+ /// Helper for getting a property from an instance with reflection.
265
+ /// </summary>
266
+ /// <param name="instance"></param>
267
+ /// <param name="propertyName"></param>
268
+ /// <param name="isPublic"></param>
269
+ /// <returns></returns>
270
+ private static object GetPropertyReflection ( object instance , string propertyName , bool isPublic )
271
+ {
272
+ return instance . GetType ( ) . GetProperty ( propertyName , ( isPublic ? System . Reflection . BindingFlags . Public : System . Reflection . BindingFlags . NonPublic ) |
273
+ System . Reflection . BindingFlags . Instance ) . GetValue ( instance , null ) ;
274
+ }
275
+
276
+ /// <summary>
277
+ /// Returns a list of GameObjects in the scene that contain references to the given object.
278
+ /// </summary>
279
+ /// <param name="obj"></param>
280
+ /// <returns></returns>
281
+ internal static List < GameObject > GetSceneReferencesToObject ( Object obj )
282
+ {
283
+ var sceneHierarchyWindowType = typeof ( UnityEditor . SearchableEditorWindow ) . Assembly . GetType ( "UnityEditor.SceneHierarchyWindow" ) ;
284
+ var sceneHierarchyWindow = EditorWindow . GetWindow ( sceneHierarchyWindowType ) ;
285
+ var instanceID = obj . GetInstanceID ( ) ;
286
+ var idFormat = "ref:{0}:" ;
287
+
288
+ var sceneHierarchy = GetPropertyReflection ( sceneHierarchyWindow , "sceneHierarchy" , isPublic : true ) ;
289
+ var previousSearchFilter = sceneHierarchy . GetType ( ) . GetField ( "m_SearchFilter" , System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Instance ) . GetValue ( sceneHierarchy ) ;
290
+
291
+ // Set the search filter to find all references in the scene to the given object
292
+ var setSearchFilterMethod = sceneHierarchyWindowType . GetMethod ( "SetSearchFilter" , System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Instance ) ;
293
+ setSearchFilterMethod . Invoke ( sceneHierarchyWindow , new object [ ] { string . Format ( idFormat , instanceID ) , SearchableEditorWindow . SearchMode . All , true , false } ) ;
294
+
295
+ // Get objects from list of instance IDs of currently visible objects
296
+ var treeView = GetPropertyReflection ( sceneHierarchy , "treeView" , isPublic : false ) ;
297
+ var data = GetPropertyReflection ( treeView , "data" , isPublic : true ) ;
298
+ var getRows = data . GetType ( ) . GetMethod ( "GetRows" ) ;
299
+ var rows = getRows . Invoke ( data , null ) as IEnumerable ;
300
+
301
+ var sceneObjects = new List < GameObject > ( ) ;
302
+ foreach ( var row in rows )
303
+ {
304
+ var id = ( int ) GetPropertyReflection ( row , "id" , isPublic : true ) ;
305
+ var gameObject = EditorUtility . InstanceIDToObject ( id ) as GameObject ;
306
+ if ( gameObject )
307
+ {
308
+ sceneObjects . Add ( gameObject ) ;
309
+ }
310
+ }
311
+
312
+ // remove the filter when done
313
+ setSearchFilterMethod . Invoke ( sceneHierarchyWindow , new object [ ] { previousSearchFilter , SearchableEditorWindow . SearchMode . Name , true , false } ) ;
314
+ return sceneObjects ;
315
+ }
210
316
211
317
/// <summary>
212
318
/// Convert one object (and the hierarchy below it) to a prefab variant of a model prefab.
@@ -626,7 +732,8 @@ internal static void UpdateFromSourceRecursive(GameObject dest, GameObject sourc
626
732
PrefabUtility . UnpackPrefabInstance ( sourceGO , PrefabUnpackMode . Completely , InteractionMode . AutomatedAction ) ;
627
733
}
628
734
629
- CopyComponents ( destGO , sourceGO , goDict ) ;
735
+ FixSceneReferences ( sourceGO , destGO , source ) ;
736
+ CopyComponents ( destGO , sourceGO , source , goDict ) ;
630
737
631
738
// also make sure GameObject properties, such as tag and layer
632
739
// are copied over as well
@@ -741,8 +848,10 @@ internal static void CopySerializedProperty(SerializedObject serializedObject, S
741
848
/// are already in the FBX.
742
849
///
743
850
/// The 'from' hierarchy is not modified.
851
+ ///
852
+ /// Note: 'root' is the root object that is being converted
744
853
/// </summary>
745
- internal static void CopyComponents ( GameObject to , GameObject from , Dictionary < string , GameObject > nameMap )
854
+ internal static void CopyComponents ( GameObject to , GameObject from , GameObject root , Dictionary < string , GameObject > nameMap )
746
855
{
747
856
// copy components on "from" to "to". Don't want to copy over meshes and materials that were exported
748
857
var originalComponents = new List < Component > ( from . GetComponents < Component > ( ) ) ;
@@ -755,8 +864,15 @@ internal static void CopyComponents(GameObject to, GameObject from, Dictionary<s
755
864
continue ;
756
865
}
757
866
758
- // ignore MeshFilter and FbxPrefab (when converting LinkedPrefabs)
759
- if ( fromComponent is MeshFilter || fromComponent is UnityEngine . Formats . Fbx . Exporter . FbxPrefab )
867
+ // ignore MeshFilter, but still ensure scene references are maintained
868
+ if ( fromComponent is MeshFilter )
869
+ {
870
+ FixSceneReferences ( fromComponent , to . GetComponent < MeshFilter > ( ) , root ) ;
871
+ continue ;
872
+ }
873
+
874
+ // ignore FbxPrefab (when converting LinkedPrefabs)
875
+ if ( fromComponent is UnityEngine . Formats . Fbx . Exporter . FbxPrefab )
760
876
{
761
877
continue ;
762
878
}
@@ -804,6 +920,8 @@ internal static void CopyComponents(GameObject to, GameObject from, Dictionary<s
804
920
toComponent = to . AddComponent ( fromComponent . GetType ( ) ) ;
805
921
}
806
922
923
+ FixSceneReferences ( fromComponent , toComponent , root ) ;
924
+
807
925
// Do not try to copy materials for ParticleSystemRenderer, since it is not in the
808
926
// FBX file
809
927
if ( fromComponent is Renderer && ! ( fromComponent is ParticleSystemRenderer ) )
0 commit comments