MB_ReplacePrefabsInScene.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEditor;
  5. using System;
  6. using DigitalOpus.MB.Core;
  7. namespace DigitalOpus.MB.MBEditor
  8. {
  9. public class MB_ReplacePrefabsInScene : MonoBehaviour
  10. {
  11. [System.Serializable]
  12. public class Error
  13. {
  14. public GameObject errorObj;
  15. public String error;
  16. }
  17. private class Prop2Reference
  18. {
  19. public string propertyName;
  20. public bool overriddenInSrcInstance;
  21. public UnityEngine.Object obj;
  22. public string parentArrayName;
  23. public int parentArraySize;
  24. }
  25. private class Component2MeshAndMaterials
  26. {
  27. public Component component;
  28. public List<Prop2Reference> props = new List<Prop2Reference>();
  29. }
  30. private List<Error> errors;
  31. public bool replaceEnforceStructure = true;
  32. void AddError(GameObject obj, string message)
  33. {
  34. Error err = new Error()
  35. {
  36. errorObj = obj,
  37. error = message,
  38. };
  39. errors.Add(err);
  40. Debug.LogError(message);
  41. }
  42. public int ReplacePrefabInstancesInScene(GameObject mySrcPrefab, GameObject myTargPrefab, List<Error> objsWithErrors)
  43. {
  44. errors = objsWithErrors;
  45. errors.Clear();
  46. if (mySrcPrefab == null)
  47. {
  48. AddError(mySrcPrefab, "Source Prefab was null");
  49. return 0;
  50. }
  51. if (myTargPrefab == null)
  52. {
  53. AddError(myTargPrefab, "Target Prefab was null");
  54. return 0;
  55. }
  56. if (IsSceneInstance(mySrcPrefab))
  57. {
  58. AddError(mySrcPrefab, "The source prefab was a scene instance. It must be a project asset.");
  59. return 0;
  60. }
  61. if (IsSceneInstance(myTargPrefab))
  62. {
  63. AddError(mySrcPrefab, "The target prefab was a scene instance. It must be a project asset.");
  64. return 0;
  65. }
  66. // First validate all the prefabs, check that they are "up-to-date"
  67. if (replaceEnforceStructure)
  68. {
  69. bool structureIsSame = ValidateStructureAndCollectInternalReferences(mySrcPrefab, myTargPrefab, null, null);
  70. if (!structureIsSame)
  71. {
  72. AddError(mySrcPrefab, "Prefab Structure is not the same for prefabs:" + mySrcPrefab + " and " + myTargPrefab);
  73. return 0;
  74. }
  75. }
  76. // Second pass collect all prefab instances in the scene, validate if they are replacable
  77. List<GameObject> instancesInScene = FindAllPrefabInstances(mySrcPrefab);
  78. if (replaceEnforceStructure)
  79. {
  80. for (int i = 0; i < instancesInScene.Count; i++)
  81. {
  82. if (!ValidateStructureAndCollectInternalReferences(instancesInScene[i], myTargPrefab, null, null))
  83. {
  84. AddError(instancesInScene[i], "A scene instance for prefab " + mySrcPrefab + " has a modified structure that is too different from targetPrefab:" + instancesInScene[i]);
  85. }
  86. }
  87. }
  88. if (errors.Count > 0) return 0;
  89. // Third pass replace all the prefabs in the scene.
  90. int numReplaced = 0;
  91. for (int i = 0; i < instancesInScene.Count; i++)
  92. {
  93. GameObject srcInstance = instancesInScene[i];
  94. GameObject targInstance = (GameObject)PrefabUtility.InstantiatePrefab(myTargPrefab);
  95. Undo.RegisterCreatedObjectUndo(targInstance, "Replace Prefabs");
  96. targInstance.transform.parent = srcInstance.transform.parent;
  97. targInstance.name = srcInstance.name;
  98. if (ReplaceSinglePrefabInstance(srcInstance, targInstance))
  99. {
  100. Undo.DestroyObjectImmediate(srcInstance);
  101. MB_Utility.Destroy(srcInstance);
  102. numReplaced++;
  103. }
  104. else
  105. {
  106. Undo.DestroyObjectImmediate(targInstance);
  107. MB_Utility.Destroy(targInstance);
  108. }
  109. }
  110. Debug.Log("Replaced " + numReplaced + " instances in the scene for prefab:" + mySrcPrefab);
  111. return numReplaced;
  112. }
  113. private static List<GameObject> FindAllPrefabInstances(UnityEngine.Object myPrefab)
  114. {
  115. List<GameObject> result = new List<GameObject>();
  116. GameObject[] allObjects = (GameObject[])FindObjectsOfType(typeof(GameObject));
  117. foreach (GameObject go in allObjects)
  118. {
  119. PrefabType objPrefabType = EditorUtility.GetPrefabType(go);
  120. if (objPrefabType == PrefabType.PrefabInstance ||
  121. objPrefabType == PrefabType.ModelPrefabInstance)
  122. {
  123. UnityEngine.Object GO_prefab = EditorUtility.GetPrefabParent(go);
  124. if (myPrefab == GO_prefab)
  125. {
  126. result.Add(go);
  127. }
  128. }
  129. }
  130. return result;
  131. }
  132. private bool ReplaceSinglePrefabInstance(GameObject src, GameObject targ)
  133. {
  134. Debug.Assert(IsSceneInstance(src));
  135. Debug.Assert(IsSceneInstance(targ));
  136. // Build a source 2 target map of all internal references
  137. if (replaceEnforceStructure)
  138. {
  139. Dictionary<UnityEngine.Object, UnityEngine.Object> src2targetObjMap = new Dictionary<UnityEngine.Object, UnityEngine.Object>();
  140. // Collect all references to project assets.
  141. Dictionary<Component, Component2MeshAndMaterials> component2MeshAndMats = new Dictionary<Component, Component2MeshAndMaterials>();
  142. bool identicalStructure = ValidateStructureAndCollectInternalReferences(src, targ, src2targetObjMap, component2MeshAndMats);
  143. if (!identicalStructure)
  144. {
  145. AddError(src, "Prefabs did not have identical structure " + targ);
  146. return false;
  147. }
  148. return VisitObj(src, src, targ, src2targetObjMap, component2MeshAndMats);
  149. } else
  150. {
  151. targ.layer = src.layer;
  152. targ.tag = src.tag;
  153. GameObjectUtility.SetStaticEditorFlags(targ, GameObjectUtility.GetStaticEditorFlags(src));
  154. targ.transform.localPosition = src.transform.localPosition;
  155. targ.transform.localRotation = src.transform.localRotation;
  156. targ.transform.localScale = src.transform.localScale;
  157. return true;
  158. }
  159. }
  160. /// <summary>
  161. ///
  162. /// </summary>
  163. /// <param name="srcObj"></param>
  164. /// <param name="targObj"></param>
  165. /// <param name="src2targetObjMap">Can be null.</param>
  166. /// <param name="component2MeshAndMats">Can be null</param>
  167. private bool ValidateStructureAndCollectInternalReferences(GameObject srcObj, GameObject targObj,
  168. Dictionary<UnityEngine.Object, UnityEngine.Object> src2targetObjMap,
  169. Dictionary<Component, Component2MeshAndMaterials> component2MeshAndMats)
  170. {
  171. if (src2targetObjMap != null) src2targetObjMap.Add(srcObj, targObj);
  172. Component[] srcComponents = srcObj.GetComponents<Component>();
  173. Component[] targComponents = targObj.GetComponents<Component>();
  174. if (srcComponents.Length != targComponents.Length) return false;
  175. for (int i = 0; i < srcComponents.Length; i++)
  176. {
  177. if (srcComponents[i].GetType() != targComponents[i].GetType())
  178. {
  179. Debug.Log("Components are different " + srcObj + " " + srcComponents[i] + " " + targObj + " " + targComponents[i]);
  180. return false;
  181. }
  182. if (src2targetObjMap != null && component2MeshAndMats != null)
  183. {
  184. src2targetObjMap.Add(srcComponents[i], targComponents[i]);
  185. CollectAssetReferncesForComponent(srcComponents[i], component2MeshAndMats);
  186. CollectAssetReferncesForComponent(targComponents[i], component2MeshAndMats, true);
  187. }
  188. }
  189. if (srcObj.transform.childCount != targObj.transform.childCount) return false;
  190. for (int i = 0; i < srcObj.transform.childCount; i++)
  191. {
  192. Transform srcChild = srcObj.transform.GetChild(i);
  193. Transform targChild = targObj.transform.GetChild(i);
  194. bool childIsValid = ValidateStructureAndCollectInternalReferences(srcChild.gameObject, targChild.gameObject,
  195. src2targetObjMap, component2MeshAndMats);
  196. if (!childIsValid) return false;
  197. }
  198. return true;
  199. }
  200. private static void CollectAssetReferncesForComponent(Component comp, Dictionary<Component, Component2MeshAndMaterials> propRefereces, bool log = false)
  201. {
  202. SerializedObject srcSO = new SerializedObject(comp);
  203. SerializedProperty prop = srcSO.GetIterator();
  204. List<Prop2Reference> propRefs = new List<Prop2Reference>();
  205. SerializedProperty arrayPropParent = null;
  206. if (prop.NextVisible(true))
  207. {
  208. do
  209. {
  210. if (log && prop.isArray) Debug.Log("Found Array prop: " + prop.propertyPath + " size: " + prop.arraySize);
  211. if (prop.isArray)
  212. {
  213. arrayPropParent = srcSO.FindProperty(prop.propertyPath);
  214. }
  215. // If is an internal reference
  216. // Or is a reference to a mesh or material that is different.
  217. if (prop.propertyType == SerializedPropertyType.ObjectReference)
  218. {
  219. if (!IsSceneInstanceAsset(prop.objectReferenceValue))
  220. {
  221. if (log) Debug.Log("Visiting coponent " + comp + " propName: " + prop.name + " propPath: " + prop.propertyPath + " isArray: " + prop.isArray);
  222. // Get some info about the array if this is an element of an array.
  223. string parentArrayPath;
  224. int parentArraySize;
  225. if (arrayPropParent != null &&
  226. prop.propertyPath.StartsWith(arrayPropParent.propertyPath + ".Array.data["))
  227. {
  228. parentArrayPath = arrayPropParent.propertyPath;
  229. parentArraySize = arrayPropParent.arraySize;
  230. } else
  231. {
  232. parentArrayPath = "";
  233. parentArraySize = -1;
  234. }
  235. // mesh to materials
  236. Prop2Reference srcComp2Mats = new Prop2Reference()
  237. {
  238. propertyName = prop.propertyPath,
  239. overriddenInSrcInstance = prop.prefabOverride,
  240. obj = prop.objectReferenceValue,
  241. parentArrayName = parentArrayPath,
  242. parentArraySize = parentArraySize,
  243. };
  244. propRefs.Add(srcComp2Mats);
  245. }
  246. }
  247. }
  248. while (prop.NextVisible(true));
  249. }
  250. if (propRefs.Count > 0)
  251. {
  252. Component2MeshAndMaterials srcComp2Mats = new Component2MeshAndMaterials()
  253. {
  254. component = comp,
  255. props = propRefs,
  256. };
  257. propRefereces.Add(srcComp2Mats.component, srcComp2Mats);
  258. }
  259. }
  260. public static bool IsSceneInstanceAsset(UnityEngine.Object obj)
  261. {
  262. if (obj == null) return true;
  263. string pth = AssetDatabase.GetAssetPath(obj);
  264. if (pth == null || pth.Equals("")) return true;
  265. return false;
  266. }
  267. public static bool IsSceneInstance(GameObject go)
  268. {
  269. return go.scene.name != null;
  270. }
  271. private bool CopyGameObjectDifferences(GameObject srcGO, GameObject targGO,
  272. Dictionary<UnityEngine.Object, UnityEngine.Object> src2targetObjMap,
  273. Dictionary<Component, Component2MeshAndMaterials> component2MeshAndMats)
  274. {
  275. Component[] srcComps = srcGO.GetComponents<Component>();
  276. Component[] targComps = targGO.GetComponents<Component>();
  277. if (srcComps.Length != targComps.Length)
  278. {
  279. AddError(srcGO, "Source GameObject had a different number of components than target.");
  280. return false;
  281. }
  282. for (int i = 0; i < srcComps.Length; i++)
  283. {
  284. if (srcComps[i].GetType() == targComps[i].GetType())
  285. {
  286. List<Prop2Reference> targetInernalRefs = new List<Prop2Reference>();
  287. SerializedObject serializedObject = new SerializedObject(targComps[i]);
  288. // Go through target and find all references, check if these are refs to internal gameObjects/components of the prefab
  289. // snapshot all internal refs.
  290. {
  291. SerializedProperty arrayPropParent = null;
  292. SerializedProperty prop = serializedObject.GetIterator();
  293. if (prop.NextVisible(true))
  294. {
  295. do
  296. {
  297. // Get some info about the array if this is an element of an array.
  298. string parentArrayPath;
  299. int parentArraySize;
  300. if (arrayPropParent != null &&
  301. prop.propertyPath.StartsWith(arrayPropParent.propertyPath + ".Array.data["))
  302. {
  303. parentArrayPath = arrayPropParent.propertyPath;
  304. parentArraySize = arrayPropParent.arraySize;
  305. }
  306. else
  307. {
  308. parentArrayPath = "";
  309. parentArraySize = -1;
  310. }
  311. // If is an internal reference
  312. // Or is a reference to a mesh or material that is different.
  313. if (prop.propertyType == SerializedPropertyType.ObjectReference &&
  314. src2targetObjMap.ContainsValue(prop.objectReferenceValue))
  315. {
  316. Prop2Reference p2r = new Prop2Reference()
  317. {
  318. propertyName = prop.propertyPath,
  319. obj = prop.objectReferenceValue,
  320. parentArrayName = parentArrayPath,
  321. parentArraySize = parentArraySize,
  322. };
  323. targetInernalRefs.Add(p2r);
  324. }
  325. }
  326. while (prop.NextVisible(true));
  327. }
  328. }
  329. EditorUtility.CopySerializedIfDifferent(srcComps[i], targComps[i]);
  330. serializedObject.Update();
  331. // Restore internal references.
  332. for (int refIdx = 0; refIdx < targetInernalRefs.Count; refIdx++)
  333. {
  334. Prop2Reference p2r = targetInernalRefs[refIdx];
  335. SerializedProperty sp = serializedObject.FindProperty(p2r.propertyName);
  336. sp.objectReferenceValue = targetInernalRefs[refIdx].obj;
  337. // The copy may have resized arrays. Restore the array size from the prefab.
  338. if (p2r.parentArraySize != -1)
  339. {
  340. sp = serializedObject.FindProperty(p2r.parentArrayName);
  341. sp.arraySize = p2r.parentArraySize;
  342. }
  343. }
  344. // Restore references to project assets
  345. // Don't restore renderer assets because these are materials and go with the meshes.
  346. {
  347. if (component2MeshAndMats.ContainsKey(targComps[i]))
  348. {
  349. Component2MeshAndMaterials meshAndMats = component2MeshAndMats[targComps[i]];
  350. SerializedObject targSO = new SerializedObject(meshAndMats.component);
  351. for (int prpIdx = 0; prpIdx < meshAndMats.props.Count; prpIdx++)
  352. {
  353. Prop2Reference p2r = meshAndMats.props[prpIdx];
  354. SerializedProperty prop = targSO.FindProperty(p2r.propertyName);
  355. Debug.Log("Restoring asset refs for component " + targComps[i] + " nm " + prop.name + " parentArray " + p2r.parentArrayName + " arraySize" + p2r.parentArraySize);
  356. prop.objectReferenceValue = meshAndMats.props[prpIdx].obj;
  357. // The copy may have resized arrays. Restore the array size from the prefab.
  358. if (p2r.parentArraySize != -1)
  359. {
  360. prop = targSO.FindProperty(p2r.parentArrayName);
  361. prop.arraySize = p2r.parentArraySize;
  362. }
  363. }
  364. targSO.ApplyModifiedPropertiesWithoutUndo();
  365. }
  366. }
  367. serializedObject.ApplyModifiedProperties();
  368. }
  369. else
  370. {
  371. AddError(srcGO, "Components did not match");
  372. return false;
  373. }
  374. }
  375. return true;
  376. }
  377. private bool VisitObj(GameObject srcRoot, GameObject srcChildObj, GameObject targRoot,
  378. Dictionary<UnityEngine.Object, UnityEngine.Object> src2targetObjMap,
  379. Dictionary<Component, Component2MeshAndMaterials> component2MeshAndMats)
  380. {
  381. Debug.Assert(replaceEnforceStructure, "Should only be called if enforcing structure.");
  382. // Ensure that it has the same hierarchy and every game object hase the same components
  383. Transform targChildTr = FindCorrespondingTransform(srcRoot.transform, srcChildObj.transform, targRoot.transform);
  384. if (targChildTr != null)
  385. {
  386. targChildTr.gameObject.layer = srcChildObj.gameObject.layer;
  387. targChildTr.gameObject.tag = srcChildObj.gameObject.tag;
  388. GameObjectUtility.SetStaticEditorFlags(targChildTr.gameObject, GameObjectUtility.GetStaticEditorFlags(srcChildObj.gameObject));
  389. if (!CopyGameObjectDifferences(srcChildObj, targChildTr.gameObject, src2targetObjMap, component2MeshAndMats))
  390. {
  391. return false;
  392. }
  393. }
  394. else
  395. {
  396. AddError(srcRoot, "PrefabInstance " + srcRoot + " had an child " + srcChildObj + " that was not found in the target prefab.");
  397. return false;
  398. }
  399. foreach (Transform tr in srcChildObj.transform)
  400. {
  401. if (!VisitObj(srcRoot, tr.gameObject, targRoot, src2targetObjMap, component2MeshAndMats))
  402. {
  403. return false;
  404. }
  405. }
  406. return true;
  407. }
  408. private static Transform FindCorrespondingTransform(Transform srcRoot, Transform srcChild,
  409. Transform targRoot)
  410. {
  411. if (srcRoot == srcChild) return targRoot;
  412. //build the path to the root in the source prefab
  413. List<Transform> path_root2child = new List<Transform>();
  414. Transform t = srcChild;
  415. do
  416. {
  417. path_root2child.Insert(0, t);
  418. t = t.parent;
  419. } while (t != null && t != t.root && t != srcRoot);
  420. if (t == null)
  421. {
  422. Debug.LogError("scrChild was not child of srcRoot " + srcRoot + " " + srcChild);
  423. return null;
  424. }
  425. path_root2child.Insert(0, srcRoot);
  426. //try to find a matching path in the target prefab
  427. t = targRoot;
  428. for (int i = 1; i < path_root2child.Count; i++)
  429. {
  430. Transform tSrc = path_root2child[i - 1];
  431. //try to find child in same position with same name
  432. int srcIdx = TIndexOf(tSrc, path_root2child[i]);
  433. if (srcIdx < t.childCount && path_root2child[i].name.Equals(t.GetChild(srcIdx).name))
  434. {
  435. t = t.GetChild(srcIdx);
  436. continue;
  437. }
  438. //try to find child with same name
  439. for (int j = 0; j < t.childCount; j++)
  440. {
  441. if (t.GetChild(j).name.Equals(path_root2child[i].name))
  442. {
  443. t = t.GetChild(j);
  444. continue;
  445. }
  446. }
  447. t = null;
  448. break;
  449. }
  450. return t;
  451. }
  452. private static int TIndexOf(Transform p, Transform c)
  453. {
  454. for (int i = 0; i < p.childCount; i++)
  455. {
  456. if (c == p.GetChild(i))
  457. {
  458. return i;
  459. }
  460. }
  461. return -1;
  462. }
  463. }
  464. }