Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Replace game object with prefab?

Discussion in 'Editor & General Support' started by DaveA, Jun 4, 2009.

  1. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    223
    Oops, I didn't follow this thread for a while and didn't notice all the improvements.
    In parallel, I branched early (from Elecman's version on page 1!) and went in a different direction, focusing on prefab linking, scene object vs prefab asset as replacing object and tricky edge cases like replacing embedded prefabs.

    In the meantime I see some adjustments have been made for new versions of Unity like prefab variant, but just in case I'll drop a link to my script (it's on the develop branch so you should get all my latest changes there):

    https://bitbucket.org/hsandt/unity-commons-editor/src/develop/ReplaceGameObjects.cs
    Exact commit from today: https://bitbucket.org/hsandt/unity-...da5860690467/ReplaceGameObjects.cs?at=develop

    I'm not sure if it's worth posting it as a code snippet too at this point. But if you'd rather have it for archiving purpose I can do that.

    The key methods I'm using that I don't see used in the latest script are:
    - PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot (+
    AssetDatabase.LoadAssetAtPath)
    : to find the original prefab of a prefab instance, to replace with full prefab linking
    - AssetDatabase.Contains: to identify an actual prefab asset
    - SceneManager.MoveGameObjectToScene: to fix edge case where multiple scenes are open

    Unfortunately I don't get the very useful features like Keep Place in Hierarchy, I'll probably add this one next. But I don't have the bandwidth for a full merge, and I don't need more advanced features like the nice window, therefore I will keep my script to the minimum for now.

    If one of you is courageous enough to do a merge in the other direction, feel free to pick code snippets you feel useful from my code to the latest script posted here. To be fair, I really just added bits of code incrementally to fix edge cases and follow emerging use-cases. This is probably your case too, so you should really adjust the code when you feel it will help. So don't take my word for it, just experiment and if you get issues with prefab child replacement or whatever, then maybe my code will help you.
     
    phobos2077 likes this.
  2. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    223
    @Zan_Kievit So, I tested the latest version and it works nice with my prefabs as expected.

    However, if I select a non-prefab game object (scene hierarchy object) it just errors without a nice message. The following code in the Replace method makes me think that this case was supposed to be handled, but it's not really:

    Code (CSharp):
    1. if (newObject == null) // if the prefab is chosen from the project browser and not the hierarchy, it is null
    2.     newObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
    Since prefab is not a prefab, InstantiatePrefab returns null, and so newObject is still null.
    We should use Instantiate instead, however this doesn't seem to be enough (nothing happens and the window gets clear when I just fix the line with Instantiate).

    I'm using my own script for general object replacement so I didn't bother peeking at this too deeply, but anybody here can have a look and try to either (a) support scene object completely or (b) drop scene object support, but with a proper warning message (or, if possible, by having the game object selector filter out scene objects).

    I think that for (a), we need to keep branching further depending on whether the replacing object is a prefab or not, as prefab linking won't work if not. See my post above for a few methods to use to identify an object's type, and a link to my script using them.
     
  3. jwinn

    jwinn

    Joined:
    Sep 1, 2012
    Posts:
    88
    I'm having some issues with this. After replacement, the new prefabs in the scene have additional child object(s) added to them that are not part of the prefab (plus icon showing the override of the additional Gameobject, giving me duplicate meshes). I can't find a way to revert all these overrides without also reverting the transforms (including scale).

    Edit: Also, after a revert all, it appears that something with nested prefabs & overrides is not working properly with this script, because the replaced prefabs in the scene do not reflect the one used to replace.

    I also get errors:
    Instantiating mesh due to calling MeshFilter.mesh during edit mode. This will leak meshes. Please use MeshFilter.sharedMesh instead


    (2021.2.7f1)

    I really wish this functionality would be added to Unity as a built-in feature, as others have mentioned in this thread. It's really a necessity.
     
    Last edited: Jan 31, 2022
  4. markasuter

    markasuter

    Joined:
    May 20, 2019
    Posts:
    22
    Fun fact: To create that array of non-prefab gameobjects (to utilize this script), you can do it in the inspector:
    1. On the dummy gameobject that holds this script, select it to see the currently empty public GameObject array.
    2. In the top-right of the Inspector, click the little lock icon, this keeps the inspector on the dummy gameobject
    3. Select all your non-prefabs and drop them onto the name of the prefab in your dummy object.
     
  5. KristianHJ

    KristianHJ

    Joined:
    May 28, 2010
    Posts:
    343
    You are right, its a good way of doing it. I know its such a long time since I originally posted my additions to this mini-tool in this thread, but I recently wrote a brand new implementation for "Asset swapping". This one allows for keeping any script or modified value that exist on the scene object i.e. if you have a bunch of enemy_A assets that you want to swap with Enemy_B, but you have a bunch of scripts or values you want to carry over to the replaced object. It also allows you to select a single asset, and then replace ALL of that type in scene. Read about it here.

    An additional benefit is that it retains any in-scene reference to the replaced objects i.e. if you have a scripts in your scene that references the objects you are replacing, those references will be kept. On top of that it allows you to retain Tags, Labels, Parent and children.

    Currently its a part of a level design tool i made called SmartBuilder, but I have been thinking about opensourcing just the replacement part of the tool. Would require a bit of work, so I'm trying to gauge whether or not there is enough interest in that for me to actually do it. So anyone, let me know if this is a feature you would find useful, or if its just an overenginered brainfart :)
     
    Last edited: Feb 18, 2022
    wedgiebee likes this.
  6. wedgiebee

    wedgiebee

    Joined:
    Aug 9, 2016
    Posts:
    38
    @KristianHJ I would definitely find the replacement window useful - at the start of a project I'm never sure what my prefab>prefab variant structure is gonna be, so there are many points where I end up replacing all the instances in a scene with something else.

    In the forum post, I notice from the screenshots it looks like if you want to completely replace an object with another prefab, you have to check every box indicating that you want to completely replace the object. Is there already a way to check every box so that you can easily do a complete replacement?
     
  7. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    223
    Hey, I've added a few fixes to my own script. Some of them may be useful to inject in the big script that is gradually improved in this thread (currently v.2.4.0 (2020-11-9) with last change by Zan Kievit).

    1. Avoid replacing child of prefab instance in Scene Hierarchy

    To do this, check:

    Code (CSharp):
    1. if (PrefabUtility.GetPrefabInstanceStatus(replacedGameObject) == PrefabInstanceStatus.Connected && !PrefabUtility.IsOutermostPrefabInstanceRoot(replacedGameObject)) { /* cannot replace */ }
    This is not implemented in v2.4.0 and worth adding.

    To understand the issue, try to delete a child of prefab instance in the Hierarchy manually, and you'll see an error popup.

    Currently, if we try to do this via the Editor Window, we get an error: "InvalidOperationException: Destroying a GameObject inside a Prefab instance is not allowed."

    Commit details: https://bitbucket.org/hsandt/unity-commons-editor/commits/1978a00c09cd94bce63cf3f551584a7eff89bb66 (same as 2.)

    2. Avoid replacing prefab root in Prefab Edit Mode. A quick way to check this is:

    Code (CSharp):
    1. PrefabStage currentPrefabStage = StageUtility.GetCurrentStage() as PrefabStage;
    2. if (currentPrefabStage != null && replacedGameObject == currentPrefabStage.prefabContentsRoot) { /* cannot replace */ }
    This is not implemented in v2.4.0 and worth adding.

    The issue exists because we do not replace the prefab root on the spot: instead, we create a new object then delete the root. Deleting the root of the edited prefab is, of course, forbidden. Trying to replace it with script v2.4.0 would give this:

    upload_2022-6-30_15-1-50.png

    The perfect solution would be to replace each component of the object in-place, but that's cumbersome (and we won't get prefab linking anyway). So I suggest just logging an error (or showing it in advance in the editor window) if you try to do that.

    Commit details: https://bitbucket.org/hsandt/unity-commons-editor/commits/1978a00c09cd94bce63cf3f551584a7eff89bb66 (same as 1.)

    3. Support removing instance of missing prefab (shown in red)

    The trick is to check Selection.gameObjects to get all selected objects (including missing prefab instances and children of selection roots), then remove children of selection roots.

    v2.4.0 seems to work with Selection.gameObjects anyway, so it doesn't have this issue.

    Commit details: https://bitbucket.org/hsandt/unity-commons-editor/commits/06bcf862731eadc662bd2cf4a988aa85d98b7502

    ===

    I found some instability when testing v2.4.0 as the editor window didn't always react properly to selection change, so I couldn't test all the cases. From my few tests though, I'd say that the biggest difference between my script and v2.4.0 is that v2.4.0 tries to preserve existing children as much as possible, adding them as prefab instance "override objects" if needed. While my script would just delete the original object completely.

    Nevertheless, I will keep posting my findings on how to handle edge cases if they can be applied to the script evolving on this thread.
     
    Xelnath likes this.
  8. KristianHJ

    KristianHJ

    Joined:
    May 28, 2010
    Posts:
    343
    Hi Wedgiebee, sorry about the late response. completely missed your message for some reason.
    Complete replacement is the default, all the other stuff is simply if you want to keep certain parts of the old gameobject (Basically carrying it over to the new gameobject)
     
    wedgiebee likes this.
  9. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    reflection value copy of properties no longer allowed because some people that I won't name like to put logic in there so it'll break if initialization is needed.
    Code (CSharp):
    1. /*=================== Replace with Prefab ===================
    2. Unity Forum Community Thread https://forum.unity.com/threads/replace-game-object-with-prefab.24311/
    3. Tested in 2018.4.19f1, 2019.3.6f1, 2020.1.12f1
    4. Should work in pre-2018.3 versions with old prefab workflow (Needs testing)
    5. Changelog and contributors:
    6. v1.0.0 (2010-03) Original CopyComponents by Michael L. Croswell for Colorado Game Coders, LLC
    7. v1.1.0 (2011-06) by Kristian Helle Jespersen
    8. v1.2.0 (2015-04) by Connor Cadellin McKee for Excamedia
    9. v1.3.0 (2015-04) by Fernando Medina (fermmmm)
    10. v1.4.0 (2015-07) by Julien Tonsuso (www.julientonsuso.com)
    11. v1.5.0 (2017-06) by Alex Dovgodko
    12.                  Changed into editor window and added instant preview in scene view
    13. v1.6.0 (2018-03) by ???
    14.                  Made changes to make things work with Unity 5.6.1
    15. v1.7.0 (2018-05) by Carlos Diosdado (hypertectonic)
    16.                  Added link to community thread, booleans to choose if scale and rotation are applied, mark scene as dirty, changed menu item
    17. v1.8.0 (2018-??) by Virgil Iordan
    18.                  Added KeepPlaceInHierarchy
    19. v1.9.0 (2019-01) by Dev Bye-A-Jee, Sanjay Sen & Nick Rodriguez for Ravensbourne University London
    20.                  Added unique numbering identifier in the hierarchy to each newly instantiated prefab, also accounts for existing numbers
    21. v2.0.0 (2020-03-22) by Zan Kievit
    22.                     Made compatible with the new Prefab system of Unity 2018. Made more user friendly and added undo.
    23. v2.1.0 (2020-03-22) by Carlos Diosdado (hypertectonic)
    24.                     Added options to use as a utility window (show from right click in the hierarchy), min/max window size,
    25.                     backwards compatibility for old prefab system, works with prefabs selected in project browser, fixed not replacing prefabs,
    26.                     added version numbers, basic documentation, Community namespace to avoid conflicts, cleaned up code for readability.
    27. v2.2.0 (2020-03-22) by GGHitman
    28.                     Add Search by tag or by layer
    29.                     the object will replace the tag and the layer of the original object
    30.                     compare and exchange materials
    31. v.2.3.0 (2020-10-20) by Jade Annand
    32.                      Added recursive game object, component, field and value copying.
    33. v.2.4.0 (2020-11-9) by Zan Kievit
    34.                     Fixed Rename errors. Added Numbering Schemes from project settings to Rename, dynamic preview of Rename, improved overal UX.
    35.                     Added NaturalComparer class from https://www.codeproject.com/Articles/22517/Natural-Sort-Comparer for human readable sorting.
    36. v.2.5.0 (2022-12-07) by Laurent Lavigne
    37.                             no longer copy property values because some people like to put logic in their getters and that'll
    38.                             usually break if not initialized
    39. Known Errors: None
    40. ============================================================*/
    41. using UnityEngine;
    42. using UnityEditor;
    43. using UnityEditor.SceneManagement;
    44. using System;
    45. using System.Collections.Generic;
    46. using System.Text.RegularExpressions;
    47. using System.Linq;
    48. using System.Reflection;
    49. using UnityEngine.SceneManagement;
    50.  
    51. namespace Community
    52. {
    53.     /// <summary>
    54.     /// An editor tool to replace selected GameObjects with a specified Prefab.
    55.     /// </summary>
    56.     public class ReplaceWithPrefab : EditorWindow
    57.     {
    58.         public GameObject       prefab           = null;
    59.         public GameObject       oldPrefab        = null;
    60.         public List<GameObject> objectsToReplace = new();
    61.         public List<GameObject> newObjects       = new();
    62.         public List<string>     objectPreview    = new();
    63.         public bool             editMode         = false;
    64.         public struct ReplacementPreferences
    65.         {
    66.             public bool renameObjects;
    67.             public bool orderHierarchyToPreview;
    68.             public bool applyRotation;
    69.             public bool applyScale;
    70.         }
    71.         public ReplacementPreferences replacementPreferences;
    72.         NamingScheme                  namingScheme;
    73.         public enum NamingScheme
    74.         {
    75.             SpaceParenthesis,
    76.             Dot,
    77.             Underscore
    78.         }
    79.         public bool                                         SearchWithTag = false;
    80.         public string                                       TagForSearch  = "Untagged";
    81.         public GameObject[]                                 searchResult;
    82.         public bool                                         SearchWithLayer = false;
    83.         public int                                          LayerForSearch;
    84.         Vector2                                             windowMinSize = new(450, 300);
    85.         Vector2                                             windowMaxSize = new(800, 1000);
    86.         Vector2                                             scrollPosition;
    87.         static readonly IDictionary<Type, IComponentCopier> componentCopiers      = new Dictionary<Type, IComponentCopier>();
    88.         static readonly IDictionary<Type, ISet<string>>     componentPartAvoiders = new Dictionary<Type, ISet<string>>();
    89.  
    90.         static ReplaceWithPrefab()
    91.         {
    92.             RegisterComponentCopiers();
    93.             RegisterComponentPartAvoiders();
    94.         }
    95.  
    96.         /// <summary>
    97.         /// Gets or creates a new Replace with Prefab window.
    98.         /// </summary>
    99.         [MenuItem("Tools/Community/Replace with Prefab")]
    100.         static void StartWindow()
    101.         {
    102.             var window = (ReplaceWithPrefab) GetWindow(typeof(ReplaceWithPrefab));
    103.             window.Show();
    104.             window.titleContent = new GUIContent("Replace with Prefab");
    105.             window.minSize      = window.windowMinSize;
    106.             window.maxSize      = window.windowMaxSize;
    107.         }
    108.  
    109.         public ReplaceWithPrefab()
    110.         {
    111.             replacementPreferences.renameObjects           = false;
    112.             replacementPreferences.orderHierarchyToPreview = false;
    113.             replacementPreferences.applyRotation           = true;
    114.             replacementPreferences.applyScale              = true;
    115.         }
    116.  
    117.         /// <summary>
    118.         /// Handles getting the selected objects when the selection changes.
    119.         /// </summary>
    120.         void OnSelectionChange()
    121.         {
    122.             GetSelection();
    123.             Repaint();
    124.         }
    125.  
    126.         /// <summary>
    127.         /// Draws the window content: object list, configuration and execution buttons.
    128.         /// </summary>
    129.         void OnGUI()
    130.         {
    131. #region Draw Top Buttons
    132.             GUILayout.BeginHorizontal(EditorStyles.toolbar);
    133.             {
    134.                 editMode = GUILayout.Toggle(editMode, new GUIContent("Start replacing", "Start using this feature"), EditorStyles.toggle);
    135.                 GUILayout.FlexibleSpace();
    136.             }
    137.             GUILayout.EndHorizontal();
    138. #endregion
    139. #region "TAG LAYER"
    140.             SearchWithTag   = GUILayout.Toggle(!SearchWithLayer ? SearchWithTag : false, "Apply Search By Tag", EditorStyles.toggle);
    141.             SearchWithLayer = GUILayout.Toggle(!SearchWithTag ? SearchWithLayer : false, "Apply Search By Layer");
    142.             if (SearchWithTag)
    143.             {
    144.                 GUILayout.Space(5);
    145.                 TagForSearch = EditorGUILayout.TagField("Set tag :  ", TagForSearch);
    146.             }
    147.             else
    148.             {
    149.                 if (SearchWithLayer)
    150.                 {
    151.                     GUILayout.Space(5);
    152.                     LayerForSearch = EditorGUILayout.LayerField("Set layer :  ", LayerForSearch);
    153.                 }
    154.             }
    155. #endregion "TAG LAYER"
    156.             if (GUI.changed)
    157.             {
    158.                 if (editMode)
    159.                 {
    160.                     GetSelection();
    161.                 }
    162.                 else
    163.                 {
    164.                     ResetPreview();
    165.                 }
    166.             }
    167.             GUILayout.Space(10);
    168.             if (editMode)
    169.             {
    170.                 SetNamingScheme();
    171.                 RenamePreview();
    172. #region Draw Prefab and List
    173.                 GUILayout.Label("Prefab: ", EditorStyles.boldLabel);
    174.                 prefab = (GameObject) EditorGUILayout.ObjectField(prefab, typeof(GameObject), true);
    175.                 if (oldPrefab != prefab)
    176.                 {
    177.                     GetSelection();
    178.                     oldPrefab = prefab;
    179.                 }
    180.                 GUILayout.Space(10);
    181.                 if (prefab != null)
    182.                 {
    183.                     if (objectsToReplace.Count > 0)
    184.                     {
    185.                         GUILayout.Label(new GUIContent("Objects to be Replaced:", !SearchWithTag && !SearchWithLayer ? "Multi-select all the objects you want to replace with your Prefab" : ""), EditorStyles.boldLabel);
    186.                         objectPreview.Sort(new NaturalComparer());
    187.                         scrollPosition = GUILayout.BeginScrollView(scrollPosition, EditorStyles.helpBox);
    188.                         {
    189.                             GUILayout.BeginHorizontal(EditorStyles.helpBox);
    190.                             {
    191.                                 var previewText = "No Preview";
    192.                                 if (replacementPreferences.renameObjects && !replacementPreferences.orderHierarchyToPreview)
    193.                                 {
    194.                                     previewText = "Preview of Renaming";
    195.                                 }
    196.                                 else
    197.                                 {
    198.                                     if (replacementPreferences.orderHierarchyToPreview && !replacementPreferences.renameObjects)
    199.                                     {
    200.                                         previewText = "Preview of Hierarchy Order";
    201.                                     }
    202.                                     else
    203.                                     {
    204.                                         if (replacementPreferences.orderHierarchyToPreview && replacementPreferences.renameObjects)
    205.                                         {
    206.                                             previewText = "Preview of Renaming and Hierarchy Order";
    207.                                         }
    208.                                     }
    209.                                 }
    210.                                 GUILayout.Label(previewText, EditorStyles.miniLabel);
    211.                             }
    212.                             GUILayout.EndHorizontal();
    213.                             foreach (var go in objectPreview)
    214.                             {
    215.                                 GUILayout.BeginHorizontal(EditorStyles.toolbar);
    216.                                 GUILayout.Label(go);
    217.                                 GUILayout.EndHorizontal();
    218.                             }
    219.                             GUILayout.Space(2);
    220.                         }
    221.                         GUILayout.EndScrollView();
    222.                         GUILayout.Space(5);
    223.                         replacementPreferences.renameObjects           = GUILayout.Toggle(replacementPreferences.renameObjects,           "Rename replaced objects",   EditorStyles.toggle);
    224.                         replacementPreferences.orderHierarchyToPreview = GUILayout.Toggle(replacementPreferences.orderHierarchyToPreview, "Oder hierarchy to preview", EditorStyles.toggle);
    225.                         GUILayout.Space(10);
    226.                         replacementPreferences.applyRotation = GUILayout.Toggle(replacementPreferences.applyRotation, "Apply rotation", EditorStyles.toggle);
    227.                         replacementPreferences.applyScale    = GUILayout.Toggle(replacementPreferences.applyScale,    "Apply scale",    EditorStyles.toggle);
    228.                     }
    229.                     else
    230.                     {
    231.                         if (!SearchWithTag && !SearchWithLayer)
    232.                         {
    233.                             GUILayout.Label(new GUIContent("Multi-select all the objects you want to replace with your Prefab"), EditorStyles.boldLabel);
    234.                         }
    235.                     }
    236.                 }
    237.                 else
    238.                 {
    239.                     GUILayout.Label("Select a Prefab to replace objects with", EditorStyles.boldLabel);
    240.                 }
    241. #endregion
    242. #region Draw Bottom Buttons
    243.                 GUILayout.Space(5);
    244.                 GUILayout.BeginHorizontal();
    245.                 {
    246.                     if (prefab != null && objectsToReplace.Count > 0)
    247.                     {
    248.                         if (GUILayout.Button("Apply"))
    249.                         {
    250.                             foreach (var go in objectsToReplace)
    251.                             {
    252.                                 Replace(go);
    253.                                 DestroyImmediate(go);
    254.                             }
    255.                             if (replacementPreferences.renameObjects)
    256.                             {
    257.                                 Rename();
    258.                             }
    259.                             editMode = false;
    260.                             EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); // Important so that we don't forget to save!
    261.                         }
    262.                         else
    263.                         {
    264.                             if (GUILayout.Button("Cancel"))
    265.                             {
    266.                                 objectsToReplace.Clear();
    267.                                 objectPreview.Clear();
    268.                                 ResetPreview();
    269.                                 prefab = null;
    270.                             }
    271.                         }
    272.                     }
    273.                 }
    274.                 GUILayout.EndHorizontal();
    275. #endregion
    276.             }
    277.             else
    278.             {
    279.                 objectsToReplace.Clear();
    280.                 objectPreview.Clear();
    281.                 newObjects.Clear();
    282.                 prefab = null;
    283.             }
    284.         }
    285.  
    286.         /// <summary>
    287.         /// Renames the gameObjects, adding numbering following the Naming Scheme Set in the Project Settings.
    288.         /// It checks for already used numbers.
    289.         /// </summary>
    290.         void Rename()
    291.         {
    292.             var count           = 0;
    293.             var ExistingNumbers = new List<int>();
    294.             SetExistingNumbers(newObjects, ExistingNumbers, namingScheme);
    295.  
    296.             //Apply new names
    297.             foreach (var go in newObjects)
    298.             {
    299.                 count = GetCount(count, ExistingNumbers);
    300.                 switch (namingScheme)
    301.                 {
    302.                     case NamingScheme.SpaceParenthesis:
    303.                         go.name = prefab.name + " (" + count + ")";
    304.                         break;
    305.                     case NamingScheme.Dot:
    306.                         go.name = prefab.name + "." + count;
    307.                         break;
    308.                     case NamingScheme.Underscore:
    309.                         go.name = prefab.name + "_" + count;
    310.                         break;
    311.                 }
    312.             }
    313.         }
    314.  
    315.         /// <summary>
    316.         /// Renames the list of names, adding numbering following the Naming Scheme Set in the Project Settings.
    317.         /// It checks for already used numbers.
    318.         /// </summary>
    319.         void RenamePreview()
    320.         {
    321.             var count           = 0;
    322.             var ExistingNumbers = new List<int>();
    323.             objectPreview.Clear();
    324.             if (replacementPreferences.renameObjects)
    325.             {
    326.                 SetExistingNumbers(objectsToReplace, ExistingNumbers, namingScheme);
    327.             }
    328.             //Apply new names
    329.             for (var n = 0; n < objectsToReplace.Count; n++)
    330.             {
    331.                 if (replacementPreferences.renameObjects)
    332.                 {
    333.                     count = GetCount(count, ExistingNumbers);
    334.                     switch (namingScheme)
    335.                     {
    336.                         case NamingScheme.SpaceParenthesis:
    337.                             objectPreview.Add(prefab.name + " (" + count + ")");
    338.                             break;
    339.                         case NamingScheme.Dot:
    340.                             objectPreview.Add(prefab.name + "." + count);
    341.                             break;
    342.                         case NamingScheme.Underscore:
    343.                             objectPreview.Add(prefab.name + "_" + count);
    344.                             break;
    345.                     }
    346.                 }
    347.                 else
    348.                 {
    349.                     if (!replacementPreferences.renameObjects)
    350.                     {
    351.                         objectPreview.Add(objectsToReplace[n].name);
    352.                     }
    353.                 }
    354.             }
    355.         }
    356.  
    357.         /// <summary>
    358.         /// Set existing numbers based on the naming scheme set in the project settings
    359.         /// </summary>
    360.         /// <param name="objects">The list of objects to get the names from</param>
    361.         /// <param name="ExistingNumbers">The list of ExistingNumbers to set</param>
    362.         /// <param name="namingScheme">The naming scheme to use</param>
    363.         void SetExistingNumbers(List<GameObject> objects, List<int> ExistingNumbers, NamingScheme namingScheme)
    364.         {
    365.             foreach (var obj in objects)
    366.             {
    367.                 var name = obj.name;
    368.                 if (name.Contains(prefab.name) && name.Any(char.IsDigit))
    369.                 {
    370.                     var num = 0;
    371.                     switch (namingScheme)
    372.                     {
    373.                         case NamingScheme.SpaceParenthesis:
    374.                             char[] charsToTrim = {'(', ')'};
    375.                             num = GetExistingNumber(name, name.Split(' '), charsToTrim);
    376.                             break;
    377.                         case NamingScheme.Dot:
    378.                             num = GetExistingNumber(name, name.Split('.'));
    379.                             break;
    380.                         case NamingScheme.Underscore:
    381.                             num = GetExistingNumber(name, name.Split('_'));
    382.                             break;
    383.                     }
    384.                     if (num != 0)
    385.                     {
    386.                         ExistingNumbers.Add(num);
    387.                     }
    388.                     else
    389.                     {
    390.                         Debug.LogError("The selected object cannot be renamed, as there are several naming schemes used");
    391.                     }
    392.                 }
    393.             }
    394.         }
    395.  
    396.         /// <summary>
    397.         /// Finds the "space" character in the name to identify where the number is, then if needed, strips provided extra characters.
    398.         /// </summary>
    399.         /// <param name="name">The name of the object</param>
    400.         /// <param name="splitChars">The string array</param>
    401.         /// <param name="charsToTrim">The extra characters to trim before converting the string to an int</param>
    402.         int GetExistingNumber(string name, string[] splitChars, char[] charsToTrim = null)
    403.         {
    404.             var count = 1;
    405.             if (splitChars.Length > 1)
    406.             {
    407.                 var digit = splitChars[1]; // substring which contains number
    408.  
    409.                 //Get the substring that contains digits
    410.                 while (GetDigits(digit) == "")
    411.                 {
    412.                     count++;
    413.                     digit = GetDigits(splitChars[count]);
    414.                 }
    415.                 if (charsToTrim != null)
    416.                 {
    417.                     digit = digit.Trim(charsToTrim);
    418.                 }
    419.                 return int.Parse(GetDigits(digit)); // convert string to number
    420.             }
    421.             else
    422.             {
    423.                 return int.Parse(GetDigits(name));
    424.             }
    425.         }
    426.  
    427.         /// <summary>
    428.         /// The number to give the
    429.         /// </summary>
    430.         /// <param name="count">The name of the object</param>
    431.         /// <param name="ExistingNumbers">The existing numbers to keep</param>
    432.         int GetCount(int count, List<int> ExistingNumbers)
    433.         {
    434.             if (ExistingNumbers.Count > 0)
    435.             {
    436.                 var i = 0;
    437.                 while (i < ExistingNumbers.Count)
    438.                 {
    439.                     if (count == ExistingNumbers[i])
    440.                     {
    441.                         count++;
    442.                         i = 0;
    443.                         return count;
    444.                     }
    445.                     else
    446.                     {
    447.                         i++;
    448.                     }
    449.                 }
    450.             }
    451.             count++;
    452.             return count;
    453.         }
    454.  
    455.         /// <summary>
    456.         /// Replaces a given gameObject with a previously chosen prefab.
    457.         /// </summary>
    458.         /// <param name="obj">The gameObject to replace.</param>
    459.         void Replace(GameObject obj)
    460.         {
    461.             GameObject newObject;
    462.             newObject = PrefabUtility.InstantiatePrefab(PrefabUtility.GetCorrespondingObjectFromSource(prefab)) as GameObject;
    463.             if (newObject == null) // if the prefab is chosen from the project browser and not the hierarchy, it is null
    464.             {
    465.                 newObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
    466.             }
    467.             newObject.transform.SetParent(obj.transform.parent, true);
    468.             newObjects.Add(newObject);
    469.             CopyContentsToNew(obj, newObject);
    470.             Undo.RegisterCreatedObjectUndo(newObject, "Replaced Objects");
    471.             Undo.DestroyObjectImmediate(obj);
    472.         }
    473.  
    474.         void CopyContentsToNew(GameObject oldObject, GameObject newObject)
    475.         {
    476.             newObject.tag   = oldObject.tag;
    477.             newObject.layer = oldObject.layer;
    478.             var components = oldObject.GetComponents(typeof(Component));
    479.             foreach (var component in components)
    480.             {
    481.                 IComponentCopier copier        = null;
    482.                 var              componentType = component.GetType();
    483.                 while (copier == null)
    484.                 {
    485.                     if (componentCopiers.ContainsKey(componentType))
    486.                     {
    487.                         copier = componentCopiers[componentType];
    488.                     }
    489.                     else
    490.                     {
    491.                         if (componentType.BaseType == typeof(object) || componentType.BaseType == null)
    492.                         {
    493.                             copier = defaultComponentCopier;
    494.                         }
    495.                         else
    496.                         {
    497.                             componentType = componentType.BaseType; // Components may be derivatives, so look up the tree.
    498.                         }
    499.                     }
    500.                 }
    501.                 copier.CopyComponent(replacementPreferences, component, newObject);
    502.             }
    503.             var childCount = oldObject.transform.childCount;
    504.             for (var i = 0; i < oldObject.transform.childCount; i++)
    505.             {
    506.                 var        child = oldObject.transform.GetChild(i).gameObject;
    507.                 GameObject newChild;
    508.                 var        newChildTransform = newObject.transform.Find(child.name);
    509.                 if (newChildTransform == null)
    510.                 {
    511.                     newChild = new GameObject(child.name);
    512.                     newChild.transform.SetParent(newObject.transform, false);
    513.                 }
    514.                 else
    515.                 {
    516.                     newChild = newChildTransform.gameObject;
    517.                 }
    518.                 CopyContentsToNew(child, newChild);
    519.             }
    520.         }
    521.  
    522.         /// <summary>
    523.         /// Gets the currently selected game objects.
    524.         /// </summary>
    525.         void GetSelection()
    526.         {
    527.             SetNamingScheme();
    528.             if (editMode && Selection.activeGameObject != null && (prefab == null || (!SearchWithTag && !SearchWithLayer)))
    529.             {
    530.                 if (prefab == null) // get the prefab 1st
    531.                 {
    532.                     var t = PrefabUtility.GetPrefabAssetType(Selection.activeGameObject);
    533.                     if (t == PrefabAssetType.Regular || t == PrefabAssetType.Variant)
    534.                     {
    535.                         prefab    = Selection.activeGameObject;
    536.                         oldPrefab = Selection.activeGameObject;
    537.                     }
    538.                 }
    539.                 else // get the other objects
    540.                 {
    541.                     ResetPreview();
    542.                     objectPreview.Clear();
    543.                     objectsToReplace.Clear();
    544.                     foreach (var obj in Selection.gameObjects)
    545.                     {
    546.                         if (obj != prefab)
    547.                         {
    548.                             objectsToReplace.Add(obj);
    549.                         }
    550.                     }
    551.                 }
    552.             }
    553.             if (editMode && prefab != null && (SearchWithTag || SearchWithLayer))
    554.             {
    555.                 if (SearchWithTag)
    556.                 {
    557.                     ResetPreview();
    558.                     objectPreview.Clear();
    559.                     objectsToReplace.Clear();
    560.                     var allGameObjects = FindObjectsOfType<GameObject>();
    561.                     foreach (var gg in allGameObjects)
    562.                     {
    563.                         if (gg.tag == TagForSearch)
    564.                         {
    565.                             if (gg != prefab)
    566.                             {
    567.                                 objectsToReplace.Add(gg);
    568.                             }
    569.                         }
    570.                     }
    571.                 }
    572.                 else
    573.                 {
    574.                     if (SearchWithLayer)
    575.                     {
    576.                         ResetPreview();
    577.                         objectPreview.Clear();
    578.                         objectsToReplace.Clear();
    579.                         var allGameObjects = FindObjectsOfType<GameObject>();
    580.                         foreach (var gg in allGameObjects)
    581.                         {
    582.                             if (gg.layer == LayerForSearch)
    583.                             {
    584.                                 if (gg != prefab)
    585.                                 {
    586.                                     objectsToReplace.Add(gg);
    587.                                 }
    588.                             }
    589.                         }
    590.                     }
    591.                 }
    592.             }
    593.             else
    594.             {
    595.                 if (editMode && Selection.activeGameObject == null && prefab != null && !SearchWithTag && !SearchWithLayer)
    596.                 {
    597.                     ResetPreview();
    598.                     objectPreview.Clear();
    599.                     objectsToReplace.Clear();
    600.                 }
    601.             }
    602.         }
    603.  
    604.         void SetNamingScheme()
    605.         {
    606. #if UNITY_2020_OR_NEWER
    607.             namingScheme = EditorSettings.gameObjectNamingScheme;
    608. #else
    609.             namingScheme = NamingScheme.SpaceParenthesis;
    610. #endif
    611.         }
    612.  
    613.         /// <summary>
    614.         /// Resets the gameObject preview.
    615.         /// </summary>
    616.         void ResetPreview()
    617.         {
    618.             if (newObjects != null)
    619.             {
    620.                 foreach (var go in newObjects)
    621.                 {
    622.                     DestroyImmediate(go);
    623.                 }
    624.             }
    625.             newObjects.Clear();
    626.         }
    627.  
    628.         /// <summary>
    629.         /// Handles window destruction.
    630.         /// </summary>
    631.         void OnDestroy()
    632.         {
    633.             ResetPreview();
    634.         }
    635.  
    636.         /// <summary>
    637.         /// Takes all digits from a string and returns them as one string.
    638.         /// </summary>
    639.         /// <param name="text">The string to get the digits from</param>
    640.         /// <returns>A string of digits</returns>
    641.         string GetDigits(string text)
    642.         {
    643.             var digits = "";
    644.             for (var i = 0; i < text.Length; i++)
    645.             {
    646.                 if (char.IsDigit(text[i]))
    647.                 {
    648.                     digits += text[i];
    649.                 }
    650.             }
    651.             return digits;
    652.         }
    653.  
    654.         /// <summary>
    655.         /// ASCII comparer class
    656.         /// </summary>
    657.         public class NaturalComparer : Comparer<string>, IDisposable
    658.         {
    659.             Dictionary<string, string[]> table;
    660.  
    661.             public NaturalComparer()
    662.             {
    663.                 table = new Dictionary<string, string[]>();
    664.             }
    665.  
    666.             public void Dispose()
    667.             {
    668.                 table.Clear();
    669.                 table = null;
    670.             }
    671.  
    672.             public override int Compare(string x, string y)
    673.             {
    674.                 if (x == y)
    675.                 {
    676.                     return 0;
    677.                 }
    678.                 string[] x1, y1;
    679.                 if (!table.TryGetValue(x, out x1))
    680.                 {
    681.                     x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
    682.                     table.Add(x, x1);
    683.                 }
    684.                 if (!table.TryGetValue(y, out y1))
    685.                 {
    686.                     y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
    687.                     table.Add(y, y1);
    688.                 }
    689.                 for (var i = 0; i < x1.Length && i < y1.Length; i++)
    690.                 {
    691.                     if (x1[i] != y1[i])
    692.                     {
    693.                         return PartCompare(x1[i], y1[i]);
    694.                     }
    695.                 }
    696.                 if (y1.Length > x1.Length)
    697.                 {
    698.                     return 1;
    699.                 }
    700.                 else
    701.                 {
    702.                     if (x1.Length > y1.Length)
    703.                     {
    704.                         return -1;
    705.                     }
    706.                     else
    707.                     {
    708.                         return 0;
    709.                     }
    710.                 }
    711.             }
    712.  
    713.             static int PartCompare(string left, string right)
    714.             {
    715.                 int x, y;
    716.                 if (!int.TryParse(left, out x))
    717.                 {
    718.                     return left.CompareTo(right);
    719.                 }
    720.                 if (!int.TryParse(right, out y))
    721.                 {
    722.                     return left.CompareTo(right);
    723.                 }
    724.                 return x.CompareTo(y);
    725.             }
    726.         }
    727.  
    728.         /// <summary>
    729.         /// Register component-specific copy objects for any components that require special handling.
    730.         /// </summary>
    731.         static void RegisterComponentCopiers()
    732.         {
    733.             componentCopiers.Add(typeof(Transform),           new TransformComponentCopier());
    734.             componentCopiers.Add(typeof(MeshRenderer),        new MeshRendererComponentCopier());
    735.             componentCopiers.Add(typeof(SkinnedMeshRenderer), new SkinnedMeshRendererComponentCopier());
    736.         }
    737.  
    738.         /// <summary>
    739.         /// Register component-specific property names to avoid copying in a default manner.
    740.         /// </summary>
    741.         static void RegisterComponentPartAvoiders()
    742.         {
    743.             ISet<string> transformAvoiders = new HashSet<string> {"localRotation", "localScale", "name", "parent"};
    744.             componentPartAvoiders.Add(typeof(Transform), transformAvoiders);
    745.         }
    746.  
    747.         // The interface for component-specific copiers from old to new GameObjects.
    748.         public interface IComponentCopier
    749.         {
    750.             void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject);
    751.         }
    752.  
    753.         // For anything that does not have a component-specific copier, or anything that does but wants to include default copy behaviour.
    754.         public class DefaultComponentCopier : IComponentCopier
    755.         {
    756.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    757.             {
    758.                 var type         = original.GetType();
    759.                 var partAvoiders = componentPartAvoiders.ContainsKey(type) ? componentPartAvoiders[type] : null;
    760.                 var dst          = newObject.GetComponent(type);
    761.                 if (!dst)
    762.                 {
    763.                     dst = newObject.AddComponent(type);
    764.                 }
    765.                 var fields = type.GetFields();
    766.                 foreach (var field in fields)
    767.                 {
    768.                     if (field.IsStatic)
    769.                     {
    770.                         continue;
    771.                     }
    772.                     if (partAvoiders != null && partAvoiders.Contains(field.Name))
    773.                     {
    774.                         continue;
    775.                     }
    776.                     field.SetValue(dst, field.GetValue(original));
    777.                 }
    778.                 var props = type.GetProperties();
    779.                 foreach (var prop in props)
    780.                 {
    781.                     if (!prop.CanWrite || prop.Name == "name" || prop.Name == "parent" || prop.MemberType == MemberTypes.Property)
    782.                     {
    783.                         continue;
    784.                     }
    785.                     if (partAvoiders != null && partAvoiders.Contains(prop.Name))
    786.                     {
    787.                         continue;
    788.                     }
    789.                     prop.SetValue(dst, prop.GetValue(original));
    790.                 }
    791.                 // NOTE: Some properties are references to other things and a prefab replacement can break them.
    792.                 // TODO: Should we record any reference types in order to map them to new references later?
    793.             }
    794.         }
    795.  
    796.         // Shared instance of default copier.
    797.         public static DefaultComponentCopier defaultComponentCopier = new();
    798.         /// <summary>
    799.         /// Transform-specific component copier.
    800.         /// </summary>
    801.         public class TransformComponentCopier : IComponentCopier
    802.         {
    803.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    804.             {
    805.                 var oldTransform = (Transform) original;
    806.                 if (replacementPreferences.applyRotation)
    807.                 {
    808.                     newObject.transform.localRotation = oldTransform.localRotation;
    809.                 }
    810.                 if (replacementPreferences.applyScale)
    811.                 {
    812.                     newObject.transform.localScale = oldTransform.localScale;
    813.                 }
    814.                 if (!replacementPreferences.renameObjects)
    815.                 {
    816.                     newObject.transform.name = oldTransform.name;
    817.                 }
    818.                 if (!replacementPreferences.orderHierarchyToPreview)
    819.                 {
    820.                     newObject.transform.SetSiblingIndex(oldTransform.GetSiblingIndex());
    821.                 }
    822.                 defaultComponentCopier.CopyComponent(replacementPreferences, original, newObject);
    823.             }
    824.         }
    825.         /// <summary>
    826.         /// Special for-purpose component copier for mesh renderer.
    827.         /// </summary>
    828.         public class MeshRendererComponentCopier : IComponentCopier
    829.         {
    830.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    831.             {
    832.                 var meshRenderer    = (MeshRenderer) original;
    833.                 var newMeshRenderer = newObject.GetComponent<MeshRenderer>();
    834.                 // QUESTION Should we instantiate one if one is not present?
    835.                 if (newMeshRenderer)
    836.                 {
    837.                     if (meshRenderer.sharedMaterials.Length == newMeshRenderer.sharedMaterials.Length)
    838.                     {
    839.                         var CacheMaterials = new Material[meshRenderer.sharedMaterials.Length];
    840.                         for (var a = 0; a < meshRenderer.sharedMaterials.Length; a++)
    841.                         {
    842.                             CacheMaterials[a] = meshRenderer.sharedMaterials[a];
    843.                         }
    844.                         for (var b = 0; b < CacheMaterials.Length; b++)
    845.                         {
    846.                             newMeshRenderer.sharedMaterials[b] = CacheMaterials[b];
    847.                         }
    848.                     }
    849.                 }
    850.             }
    851.         }
    852.         /// <summary>
    853.         /// Special for-purpose component copier for skinned mesh renderer.
    854.         /// </summary>
    855.         public class SkinnedMeshRendererComponentCopier : IComponentCopier
    856.         {
    857.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    858.             {
    859.                 var meshRenderer    = (SkinnedMeshRenderer) original;
    860.                 var newMeshRenderer = newObject.GetComponent<SkinnedMeshRenderer>();
    861.                 // QUESTION Should we instantiate one if one is not present?
    862.                 if (newMeshRenderer)
    863.                 {
    864.                     if (meshRenderer.sharedMaterials.Length == newMeshRenderer.sharedMaterials.Length)
    865.                     {
    866.                         var CacheMaterials = new Material[meshRenderer.sharedMaterials.Length];
    867.                         for (var a = 0; a < meshRenderer.sharedMaterials.Length; a++)
    868.                         {
    869.                             CacheMaterials[a] = meshRenderer.sharedMaterials[a];
    870.                         }
    871.                         for (var b = 0; b < CacheMaterials.Length; b++)
    872.                         {
    873.                             newMeshRenderer.sharedMaterials[b] = CacheMaterials[b];
    874.                         }
    875.                     }
    876.                 }
    877.             }
    878.         }
    879.     }
    880. }
     
    hamza_unity995, Ne9nX_ and gghitman69 like this.
  10. BurningthumbStudios

    BurningthumbStudios

    Joined:
    Apr 28, 2008
    Posts:
    95
    I found a bug in this script. It seems to not set the transform position. I added it at line 477. Maybe I was doing something wrong?
     
  11. metaspl0it

    metaspl0it

    Joined:
    Jun 4, 2022
    Posts:
    1
    Obviously, it won't make a big contribution, but I added the feature to apply the positions.
    Code (CSharp):
    1. /*=================== Replace with Prefab ===================
    2. Unity Forum Community Thread https://forum.unity.com/threads/replace-game-object-with-prefab.24311/
    3. Tested in 2018.4.19f1, 2019.3.6f1, 2020.1.12f1
    4. Should work in pre-2018.3 versions with old prefab workflow (Needs testing)
    5. Changelog and contributors:
    6. v1.0.0 (2010-03) Original CopyComponents by Michael L. Croswell for Colorado Game Coders, LLC
    7. v1.1.0 (2011-06) by Kristian Helle Jespersen
    8. v1.2.0 (2015-04) by Connor Cadellin McKee for Excamedia
    9. v1.3.0 (2015-04) by Fernando Medina (fermmmm)
    10. v1.4.0 (2015-07) by Julien Tonsuso (www.julientonsuso.com)
    11. v1.5.0 (2017-06) by Alex Dovgodko
    12.                  Changed into editor window and added instant preview in scene view
    13. v1.6.0 (2018-03) by ???
    14.                  Made changes to make things work with Unity 5.6.1
    15. v1.7.0 (2018-05) by Carlos Diosdado (hypertectonic)
    16.                  Added link to community thread, booleans to choose if scale and rotation are applied, mark scene as dirty, changed menu item
    17. v1.8.0 (2018-??) by Virgil Iordan
    18.                  Added KeepPlaceInHierarchy
    19. v1.9.0 (2019-01) by Dev Bye-A-Jee, Sanjay Sen & Nick Rodriguez for Ravensbourne University London
    20.                  Added unique numbering identifier in the hierarchy to each newly instantiated prefab, also accounts for existing numbers
    21. v2.0.0 (2020-03-22) by Zan Kievit
    22.                     Made compatible with the new Prefab system of Unity 2018. Made more user friendly and added undo.
    23. v2.1.0 (2020-03-22) by Carlos Diosdado (hypertectonic)
    24.                     Added options to use as a utility window (show from right click in the hierarchy), min/max window size,
    25.                     backwards compatibility for old prefab system, works with prefabs selected in project browser, fixed not replacing prefabs,
    26.                     added version numbers, basic documentation, Community namespace to avoid conflicts, cleaned up code for readability.
    27. v2.2.0 (2020-03-22) by GGHitman
    28.                     Add Search by tag or by layer
    29.                     the object will replace the tag and the layer of the original object
    30.                     compare and exchange materials
    31. v.2.3.0 (2020-10-20) by Jade Annand
    32.                      Added recursive game object, component, field and value copying.
    33. v.2.4.0 (2020-11-9) by Zan Kievit
    34.                     Fixed Rename errors. Added Numbering Schemes from project settings to Rename, dynamic preview of Rename, improved overal UX.
    35.                     Added NaturalComparer class from https://www.codeproject.com/Articles/22517/Natural-Sort-Comparer for human readable sorting.
    36. v.2.5.0 (2022-12-07) by Laurent Lavigne
    37.                             no longer copy property values because some people like to put logic in their getters and that'll
    38.                             usually break if not initialized
    39. v.2.6.0 (2023-03-16) by Muhammet Emin Turgut
    40.                      Added option to apply position to new instantiated prefabs.
    41. Known Errors: None
    42. ============================================================*/
    43. using UnityEngine;
    44. using UnityEditor;
    45. using UnityEditor.SceneManagement;
    46. using System;
    47. using System.Collections.Generic;
    48. using System.Text.RegularExpressions;
    49. using System.Linq;
    50. using System.Reflection;
    51. using UnityEngine.SceneManagement;
    52. namespace Community
    53. {
    54.     /// <summary>
    55.     /// An editor tool to replace selected GameObjects with a specified Prefab.
    56.     /// </summary>
    57.     public class ReplaceWithPrefab : EditorWindow
    58.     {
    59.         public GameObject       prefab           = null;
    60.         public GameObject       oldPrefab        = null;
    61.         public List<GameObject> objectsToReplace = new();
    62.         public List<GameObject> newObjects       = new();
    63.         public List<string>     objectPreview    = new();
    64.         public bool             editMode         = false;
    65.         public struct ReplacementPreferences
    66.         {
    67.             public bool renameObjects;
    68.             public bool orderHierarchyToPreview;
    69.             public bool applyPosition;
    70.             public bool applyRotation;
    71.             public bool applyScale;
    72.         }
    73.         public ReplacementPreferences replacementPreferences;
    74.         NamingScheme                  namingScheme;
    75.         public enum NamingScheme
    76.         {
    77.             SpaceParenthesis,
    78.             Dot,
    79.             Underscore
    80.         }
    81.         public bool                                         SearchWithTag = false;
    82.         public string                                       TagForSearch  = "Untagged";
    83.         public GameObject[]                                 searchResult;
    84.         public bool                                         SearchWithLayer = false;
    85.         public int                                          LayerForSearch;
    86.         Vector2                                             windowMinSize = new(450, 300);
    87.         Vector2                                             windowMaxSize = new(800, 1000);
    88.         Vector2                                             scrollPosition;
    89.         static readonly IDictionary<Type, IComponentCopier> componentCopiers      = new Dictionary<Type, IComponentCopier>();
    90.         static readonly IDictionary<Type, ISet<string>>     componentPartAvoiders = new Dictionary<Type, ISet<string>>();
    91.         static ReplaceWithPrefab()
    92.         {
    93.             RegisterComponentCopiers();
    94.             RegisterComponentPartAvoiders();
    95.         }
    96.         /// <summary>
    97.         /// Gets or creates a new Replace with Prefab window.
    98.         /// </summary>
    99.         [MenuItem("Tools/Community/Replace with Prefab")]
    100.         static void StartWindow()
    101.         {
    102.             var window = (ReplaceWithPrefab) GetWindow(typeof(ReplaceWithPrefab));
    103.             window.Show();
    104.             window.titleContent = new GUIContent("Replace with Prefab");
    105.             window.minSize      = window.windowMinSize;
    106.             window.maxSize      = window.windowMaxSize;
    107.         }
    108.         public ReplaceWithPrefab()
    109.         {
    110.             replacementPreferences.renameObjects           = false;
    111.             replacementPreferences.orderHierarchyToPreview = false;
    112.             replacementPreferences.applyPosition           = true;
    113.             replacementPreferences.applyRotation           = true;
    114.             replacementPreferences.applyScale              = true;
    115.         }
    116.         /// <summary>
    117.         /// Handles getting the selected objects when the selection changes.
    118.         /// </summary>
    119.         void OnSelectionChange()
    120.         {
    121.             GetSelection();
    122.             Repaint();
    123.         }
    124.         /// <summary>
    125.         /// Draws the window content: object list, configuration and execution buttons.
    126.         /// </summary>
    127.         void OnGUI()
    128.         {
    129. #region Draw Top Buttons
    130.             GUILayout.BeginHorizontal(EditorStyles.toolbar);
    131.             {
    132.                 editMode = GUILayout.Toggle(editMode, new GUIContent("Start replacing", "Start using this feature"), EditorStyles.toggle);
    133.                 GUILayout.FlexibleSpace();
    134.             }
    135.             GUILayout.EndHorizontal();
    136. #endregion
    137. #region "TAG LAYER"
    138.             SearchWithTag   = GUILayout.Toggle(!SearchWithLayer ? SearchWithTag : false, "Apply Search By Tag", EditorStyles.toggle);
    139.             SearchWithLayer = GUILayout.Toggle(!SearchWithTag ? SearchWithLayer : false, "Apply Search By Layer");
    140.             if (SearchWithTag)
    141.             {
    142.                 GUILayout.Space(5);
    143.                 TagForSearch = EditorGUILayout.TagField("Set tag :  ", TagForSearch);
    144.             }
    145.             else
    146.             {
    147.                 if (SearchWithLayer)
    148.                 {
    149.                     GUILayout.Space(5);
    150.                     LayerForSearch = EditorGUILayout.LayerField("Set layer :  ", LayerForSearch);
    151.                 }
    152.             }
    153. #endregion "TAG LAYER"
    154.             if (GUI.changed)
    155.             {
    156.                 if (editMode)
    157.                 {
    158.                     GetSelection();
    159.                 }
    160.                 else
    161.                 {
    162.                     ResetPreview();
    163.                 }
    164.             }
    165.             GUILayout.Space(10);
    166.             if (editMode)
    167.             {
    168.                 SetNamingScheme();
    169.                 RenamePreview();
    170. #region Draw Prefab and List
    171.                 GUILayout.Label("Prefab: ", EditorStyles.boldLabel);
    172.                 prefab = (GameObject) EditorGUILayout.ObjectField(prefab, typeof(GameObject), true);
    173.                 if (oldPrefab != prefab)
    174.                 {
    175.                     GetSelection();
    176.                     oldPrefab = prefab;
    177.                 }
    178.                 GUILayout.Space(10);
    179.                 if (prefab != null)
    180.                 {
    181.                     if (objectsToReplace.Count > 0)
    182.                     {
    183.                         GUILayout.Label(new GUIContent("Objects to be Replaced:", !SearchWithTag && !SearchWithLayer ? "Multi-select all the objects you want to replace with your Prefab" : ""), EditorStyles.boldLabel);
    184.                         objectPreview.Sort(new NaturalComparer());
    185.                         scrollPosition = GUILayout.BeginScrollView(scrollPosition, EditorStyles.helpBox);
    186.                         {
    187.                             GUILayout.BeginHorizontal(EditorStyles.helpBox);
    188.                             {
    189.                                 var previewText = "No Preview";
    190.                                 if (replacementPreferences.renameObjects && !replacementPreferences.orderHierarchyToPreview)
    191.                                 {
    192.                                     previewText = "Preview of Renaming";
    193.                                 }
    194.                                 else
    195.                                 {
    196.                                     if (replacementPreferences.orderHierarchyToPreview && !replacementPreferences.renameObjects)
    197.                                     {
    198.                                         previewText = "Preview of Hierarchy Order";
    199.                                     }
    200.                                     else
    201.                                     {
    202.                                         if (replacementPreferences.orderHierarchyToPreview && replacementPreferences.renameObjects)
    203.                                         {
    204.                                             previewText = "Preview of Renaming and Hierarchy Order";
    205.                                         }
    206.                                     }
    207.                                 }
    208.                                 GUILayout.Label(previewText, EditorStyles.miniLabel);
    209.                             }
    210.                             GUILayout.EndHorizontal();
    211.                             foreach (var go in objectPreview)
    212.                             {
    213.                                 GUILayout.BeginHorizontal(EditorStyles.toolbar);
    214.                                 GUILayout.Label(go);
    215.                                 GUILayout.EndHorizontal();
    216.                             }
    217.                             GUILayout.Space(2);
    218.                         }
    219.                         GUILayout.EndScrollView();
    220.                         GUILayout.Space(5);
    221.                         replacementPreferences.renameObjects           = GUILayout.Toggle(replacementPreferences.renameObjects,           "Rename replaced objects",   EditorStyles.toggle);
    222.                         replacementPreferences.orderHierarchyToPreview = GUILayout.Toggle(replacementPreferences.orderHierarchyToPreview, "Oder hierarchy to preview", EditorStyles.toggle);
    223.                         GUILayout.Space(10);
    224.                         replacementPreferences.applyPosition = GUILayout.Toggle(replacementPreferences.applyPosition, "Apply position", EditorStyles.toggle);
    225.                         replacementPreferences.applyRotation = GUILayout.Toggle(replacementPreferences.applyRotation, "Apply rotation", EditorStyles.toggle);
    226.                         replacementPreferences.applyScale    = GUILayout.Toggle(replacementPreferences.applyScale,    "Apply scale",    EditorStyles.toggle);
    227.                     }
    228.                     else
    229.                     {
    230.                         if (!SearchWithTag && !SearchWithLayer)
    231.                         {
    232.                             GUILayout.Label(new GUIContent("Multi-select all the objects you want to replace with your Prefab"), EditorStyles.boldLabel);
    233.                         }
    234.                     }
    235.                 }
    236.                 else
    237.                 {
    238.                     GUILayout.Label("Select a Prefab to replace objects with", EditorStyles.boldLabel);
    239.                 }
    240. #endregion
    241. #region Draw Bottom Buttons
    242.                 GUILayout.Space(5);
    243.                 GUILayout.BeginHorizontal();
    244.                 {
    245.                     if (prefab != null && objectsToReplace.Count > 0)
    246.                     {
    247.                         if (GUILayout.Button("Apply"))
    248.                         {
    249.                             foreach (var go in objectsToReplace)
    250.                             {
    251.                                 Replace(go);
    252.                                 DestroyImmediate(go);
    253.                             }
    254.                             if (replacementPreferences.renameObjects)
    255.                             {
    256.                                 Rename();
    257.                             }
    258.                             editMode = false;
    259.                             EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); // Important so that we don't forget to save!
    260.                         }
    261.                         else
    262.                         {
    263.                             if (GUILayout.Button("Cancel"))
    264.                             {
    265.                                 objectsToReplace.Clear();
    266.                                 objectPreview.Clear();
    267.                                 ResetPreview();
    268.                                 prefab = null;
    269.                             }
    270.                         }
    271.                     }
    272.                 }
    273.                 GUILayout.EndHorizontal();
    274. #endregion
    275.             }
    276.             else
    277.             {
    278.                 objectsToReplace.Clear();
    279.                 objectPreview.Clear();
    280.                 newObjects.Clear();
    281.                 prefab = null;
    282.             }
    283.         }
    284.         /// <summary>
    285.         /// Renames the gameObjects, adding numbering following the Naming Scheme Set in the Project Settings.
    286.         /// It checks for already used numbers.
    287.         /// </summary>
    288.         void Rename()
    289.         {
    290.             var count           = 0;
    291.             var ExistingNumbers = new List<int>();
    292.             SetExistingNumbers(newObjects, ExistingNumbers, namingScheme);
    293.             //Apply new names
    294.             foreach (var go in newObjects)
    295.             {
    296.                 count = GetCount(count, ExistingNumbers);
    297.                 switch (namingScheme)
    298.                 {
    299.                     case NamingScheme.SpaceParenthesis:
    300.                         go.name = prefab.name + " (" + count + ")";
    301.                         break;
    302.                     case NamingScheme.Dot:
    303.                         go.name = prefab.name + "." + count;
    304.                         break;
    305.                     case NamingScheme.Underscore:
    306.                         go.name = prefab.name + "_" + count;
    307.                         break;
    308.                 }
    309.             }
    310.         }
    311.         /// <summary>
    312.         /// Renames the list of names, adding numbering following the Naming Scheme Set in the Project Settings.
    313.         /// It checks for already used numbers.
    314.         /// </summary>
    315.         void RenamePreview()
    316.         {
    317.             var count           = 0;
    318.             var ExistingNumbers = new List<int>();
    319.             objectPreview.Clear();
    320.             if (replacementPreferences.renameObjects)
    321.             {
    322.                 SetExistingNumbers(objectsToReplace, ExistingNumbers, namingScheme);
    323.             }
    324.             //Apply new names
    325.             for (var n = 0; n < objectsToReplace.Count; n++)
    326.             {
    327.                 if (replacementPreferences.renameObjects)
    328.                 {
    329.                     count = GetCount(count, ExistingNumbers);
    330.                     switch (namingScheme)
    331.                     {
    332.                         case NamingScheme.SpaceParenthesis:
    333.                             objectPreview.Add(prefab.name + " (" + count + ")");
    334.                             break;
    335.                         case NamingScheme.Dot:
    336.                             objectPreview.Add(prefab.name + "." + count);
    337.                             break;
    338.                         case NamingScheme.Underscore:
    339.                             objectPreview.Add(prefab.name + "_" + count);
    340.                             break;
    341.                     }
    342.                 }
    343.                 else
    344.                 {
    345.                     if (!replacementPreferences.renameObjects)
    346.                     {
    347.                         objectPreview.Add(objectsToReplace[n].name);
    348.                     }
    349.                 }
    350.             }
    351.         }
    352.         /// <summary>
    353.         /// Set existing numbers based on the naming scheme set in the project settings
    354.         /// </summary>
    355.         /// <param name="objects">The list of objects to get the names from</param>
    356.         /// <param name="ExistingNumbers">The list of ExistingNumbers to set</param>
    357.         /// <param name="namingScheme">The naming scheme to use</param>
    358.         void SetExistingNumbers(List<GameObject> objects, List<int> ExistingNumbers, NamingScheme namingScheme)
    359.         {
    360.             foreach (var obj in objects)
    361.             {
    362.                 var name = obj.name;
    363.                 if (name.Contains(prefab.name) && name.Any(char.IsDigit))
    364.                 {
    365.                     var num = 0;
    366.                     switch (namingScheme)
    367.                     {
    368.                         case NamingScheme.SpaceParenthesis:
    369.                             char[] charsToTrim = {'(', ')'};
    370.                             num = GetExistingNumber(name, name.Split(' '), charsToTrim);
    371.                             break;
    372.                         case NamingScheme.Dot:
    373.                             num = GetExistingNumber(name, name.Split('.'));
    374.                             break;
    375.                         case NamingScheme.Underscore:
    376.                             num = GetExistingNumber(name, name.Split('_'));
    377.                             break;
    378.                     }
    379.                     if (num != 0)
    380.                     {
    381.                         ExistingNumbers.Add(num);
    382.                     }
    383.                     else
    384.                     {
    385.                         Debug.LogError("The selected object cannot be renamed, as there are several naming schemes used");
    386.                     }
    387.                 }
    388.             }
    389.         }
    390.         /// <summary>
    391.         /// Finds the "space" character in the name to identify where the number is, then if needed, strips provided extra characters.
    392.         /// </summary>
    393.         /// <param name="name">The name of the object</param>
    394.         /// <param name="splitChars">The string array</param>
    395.         /// <param name="charsToTrim">The extra characters to trim before converting the string to an int</param>
    396.         int GetExistingNumber(string name, string[] splitChars, char[] charsToTrim = null)
    397.         {
    398.             var count = 1;
    399.             if (splitChars.Length > 1)
    400.             {
    401.                 var digit = splitChars[1]; // substring which contains number
    402.                 //Get the substring that contains digits
    403.                 while (GetDigits(digit) == "")
    404.                 {
    405.                     count++;
    406.                     digit = GetDigits(splitChars[count]);
    407.                 }
    408.                 if (charsToTrim != null)
    409.                 {
    410.                     digit = digit.Trim(charsToTrim);
    411.                 }
    412.                 return int.Parse(GetDigits(digit)); // convert string to number
    413.             }
    414.             else
    415.             {
    416.                 return int.Parse(GetDigits(name));
    417.             }
    418.         }
    419.         /// <summary>
    420.         /// The number to give the
    421.         /// </summary>
    422.         /// <param name="count">The name of the object</param>
    423.         /// <param name="ExistingNumbers">The existing numbers to keep</param>
    424.         int GetCount(int count, List<int> ExistingNumbers)
    425.         {
    426.             if (ExistingNumbers.Count > 0)
    427.             {
    428.                 var i = 0;
    429.                 while (i < ExistingNumbers.Count)
    430.                 {
    431.                     if (count == ExistingNumbers[i])
    432.                     {
    433.                         count++;
    434.                         i = 0;
    435.                         return count;
    436.                     }
    437.                     else
    438.                     {
    439.                         i++;
    440.                     }
    441.                 }
    442.             }
    443.             count++;
    444.             return count;
    445.         }
    446.         /// <summary>
    447.         /// Replaces a given gameObject with a previously chosen prefab.
    448.         /// </summary>
    449.         /// <param name="obj">The gameObject to replace.</param>
    450.         void Replace(GameObject obj)
    451.         {
    452.             GameObject newObject;
    453.             newObject = PrefabUtility.InstantiatePrefab(PrefabUtility.GetCorrespondingObjectFromSource(prefab)) as GameObject;
    454.             if (newObject == null) // if the prefab is chosen from the project browser and not the hierarchy, it is null
    455.             {
    456.                 newObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
    457.             }
    458.             newObject.transform.SetParent(obj.transform.parent, true);
    459.             newObjects.Add(newObject);
    460.             CopyContentsToNew(obj, newObject);
    461.             Undo.RegisterCreatedObjectUndo(newObject, "Replaced Objects");
    462.             Undo.DestroyObjectImmediate(obj);
    463.         }
    464.         void CopyContentsToNew(GameObject oldObject, GameObject newObject)
    465.         {
    466.             newObject.tag   = oldObject.tag;
    467.             newObject.layer = oldObject.layer;
    468.             var components = oldObject.GetComponents(typeof(Component));
    469.             foreach (var component in components)
    470.             {
    471.                 IComponentCopier copier        = null;
    472.                 var              componentType = component.GetType();
    473.                 while (copier == null)
    474.                 {
    475.                     if (componentCopiers.ContainsKey(componentType))
    476.                     {
    477.                         copier = componentCopiers[componentType];
    478.                     }
    479.                     else
    480.                     {
    481.                         if (componentType.BaseType == typeof(object) || componentType.BaseType == null)
    482.                         {
    483.                             copier = defaultComponentCopier;
    484.                         }
    485.                         else
    486.                         {
    487.                             componentType = componentType.BaseType; // Components may be derivatives, so look up the tree.
    488.                         }
    489.                     }
    490.                 }
    491.                 copier.CopyComponent(replacementPreferences, component, newObject);
    492.             }
    493.             var childCount = oldObject.transform.childCount;
    494.             for (var i = 0; i < oldObject.transform.childCount; i++)
    495.             {
    496.                 var        child = oldObject.transform.GetChild(i).gameObject;
    497.                 GameObject newChild;
    498.                 var        newChildTransform = newObject.transform.Find(child.name);
    499.                 if (newChildTransform == null)
    500.                 {
    501.                     newChild = new GameObject(child.name);
    502.                     newChild.transform.SetParent(newObject.transform, false);
    503.                 }
    504.                 else
    505.                 {
    506.                     newChild = newChildTransform.gameObject;
    507.                 }
    508.                 CopyContentsToNew(child, newChild);
    509.             }
    510.         }
    511.         /// <summary>
    512.         /// Gets the currently selected game objects.
    513.         /// </summary>
    514.         void GetSelection()
    515.         {
    516.             SetNamingScheme();
    517.             if (editMode && Selection.activeGameObject != null && (prefab == null || (!SearchWithTag && !SearchWithLayer)))
    518.             {
    519.                 if (prefab == null) // get the prefab 1st
    520.                 {
    521.                     var t = PrefabUtility.GetPrefabAssetType(Selection.activeGameObject);
    522.                     if (t == PrefabAssetType.Regular || t == PrefabAssetType.Variant)
    523.                     {
    524.                         prefab    = Selection.activeGameObject;
    525.                         oldPrefab = Selection.activeGameObject;
    526.                     }
    527.                 }
    528.                 else // get the other objects
    529.                 {
    530.                     ResetPreview();
    531.                     objectPreview.Clear();
    532.                     objectsToReplace.Clear();
    533.                     foreach (var obj in Selection.gameObjects)
    534.                     {
    535.                         if (obj != prefab)
    536.                         {
    537.                             objectsToReplace.Add(obj);
    538.                         }
    539.                     }
    540.                 }
    541.             }
    542.             if (editMode && prefab != null && (SearchWithTag || SearchWithLayer))
    543.             {
    544.                 if (SearchWithTag)
    545.                 {
    546.                     ResetPreview();
    547.                     objectPreview.Clear();
    548.                     objectsToReplace.Clear();
    549.                     var allGameObjects = FindObjectsOfType<GameObject>();
    550.                     foreach (var gg in allGameObjects)
    551.                     {
    552.                         if (gg.tag == TagForSearch)
    553.                         {
    554.                             if (gg != prefab)
    555.                             {
    556.                                 objectsToReplace.Add(gg);
    557.                             }
    558.                         }
    559.                     }
    560.                 }
    561.                 else
    562.                 {
    563.                     if (SearchWithLayer)
    564.                     {
    565.                         ResetPreview();
    566.                         objectPreview.Clear();
    567.                         objectsToReplace.Clear();
    568.                         var allGameObjects = FindObjectsOfType<GameObject>();
    569.                         foreach (var gg in allGameObjects)
    570.                         {
    571.                             if (gg.layer == LayerForSearch)
    572.                             {
    573.                                 if (gg != prefab)
    574.                                 {
    575.                                     objectsToReplace.Add(gg);
    576.                                 }
    577.                             }
    578.                         }
    579.                     }
    580.                 }
    581.             }
    582.             else
    583.             {
    584.                 if (editMode && Selection.activeGameObject == null && prefab != null && !SearchWithTag && !SearchWithLayer)
    585.                 {
    586.                     ResetPreview();
    587.                     objectPreview.Clear();
    588.                     objectsToReplace.Clear();
    589.                 }
    590.             }
    591.         }
    592.         void SetNamingScheme()
    593.         {
    594. #if UNITY_2020_OR_NEWER
    595.             namingScheme = EditorSettings.gameObjectNamingScheme;
    596. #else
    597.             namingScheme = NamingScheme.SpaceParenthesis;
    598. #endif
    599.         }
    600.         /// <summary>
    601.         /// Resets the gameObject preview.
    602.         /// </summary>
    603.         void ResetPreview()
    604.         {
    605.             if (newObjects != null)
    606.             {
    607.                 foreach (var go in newObjects)
    608.                 {
    609.                     DestroyImmediate(go);
    610.                 }
    611.             }
    612.             newObjects.Clear();
    613.         }
    614.         /// <summary>
    615.         /// Handles window destruction.
    616.         /// </summary>
    617.         void OnDestroy()
    618.         {
    619.             ResetPreview();
    620.         }
    621.         /// <summary>
    622.         /// Takes all digits from a string and returns them as one string.
    623.         /// </summary>
    624.         /// <param name="text">The string to get the digits from</param>
    625.         /// <returns>A string of digits</returns>
    626.         string GetDigits(string text)
    627.         {
    628.             var digits = "";
    629.             for (var i = 0; i < text.Length; i++)
    630.             {
    631.                 if (char.IsDigit(text[i]))
    632.                 {
    633.                     digits += text[i];
    634.                 }
    635.             }
    636.             return digits;
    637.         }
    638.         /// <summary>
    639.         /// ASCII comparer class
    640.         /// </summary>
    641.         public class NaturalComparer : Comparer<string>, IDisposable
    642.         {
    643.             Dictionary<string, string[]> table;
    644.             public NaturalComparer()
    645.             {
    646.                 table = new Dictionary<string, string[]>();
    647.             }
    648.             public void Dispose()
    649.             {
    650.                 table.Clear();
    651.                 table = null;
    652.             }
    653.             public override int Compare(string x, string y)
    654.             {
    655.                 if (x == y)
    656.                 {
    657.                     return 0;
    658.                 }
    659.                 string[] x1, y1;
    660.                 if (!table.TryGetValue(x, out x1))
    661.                 {
    662.                     x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
    663.                     table.Add(x, x1);
    664.                 }
    665.                 if (!table.TryGetValue(y, out y1))
    666.                 {
    667.                     y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
    668.                     table.Add(y, y1);
    669.                 }
    670.                 for (var i = 0; i < x1.Length && i < y1.Length; i++)
    671.                 {
    672.                     if (x1[i] != y1[i])
    673.                     {
    674.                         return PartCompare(x1[i], y1[i]);
    675.                     }
    676.                 }
    677.                 if (y1.Length > x1.Length)
    678.                 {
    679.                     return 1;
    680.                 }
    681.                 else
    682.                 {
    683.                     if (x1.Length > y1.Length)
    684.                     {
    685.                         return -1;
    686.                     }
    687.                     else
    688.                     {
    689.                         return 0;
    690.                     }
    691.                 }
    692.             }
    693.             static int PartCompare(string left, string right)
    694.             {
    695.                 int x, y;
    696.                 if (!int.TryParse(left, out x))
    697.                 {
    698.                     return left.CompareTo(right);
    699.                 }
    700.                 if (!int.TryParse(right, out y))
    701.                 {
    702.                     return left.CompareTo(right);
    703.                 }
    704.                 return x.CompareTo(y);
    705.             }
    706.         }
    707.         /// <summary>
    708.         /// Register component-specific copy objects for any components that require special handling.
    709.         /// </summary>
    710.         static void RegisterComponentCopiers()
    711.         {
    712.             componentCopiers.Add(typeof(Transform),           new TransformComponentCopier());
    713.             componentCopiers.Add(typeof(MeshRenderer),        new MeshRendererComponentCopier());
    714.             componentCopiers.Add(typeof(SkinnedMeshRenderer), new SkinnedMeshRendererComponentCopier());
    715.         }
    716.         /// <summary>
    717.         /// Register component-specific property names to avoid copying in a default manner.
    718.         /// </summary>
    719.         static void RegisterComponentPartAvoiders()
    720.         {
    721.             ISet<string> transformAvoiders = new HashSet<string> {"localRotation", "localScale", "name", "parent"};
    722.             componentPartAvoiders.Add(typeof(Transform), transformAvoiders);
    723.         }
    724.         // The interface for component-specific copiers from old to new GameObjects.
    725.         public interface IComponentCopier
    726.         {
    727.             void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject);
    728.         }
    729.         // For anything that does not have a component-specific copier, or anything that does but wants to include default copy behaviour.
    730.         public class DefaultComponentCopier : IComponentCopier
    731.         {
    732.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    733.             {
    734.                 var type         = original.GetType();
    735.                 var partAvoiders = componentPartAvoiders.ContainsKey(type) ? componentPartAvoiders[type] : null;
    736.                 var dst          = newObject.GetComponent(type);
    737.                 if (!dst)
    738.                 {
    739.                     dst = newObject.AddComponent(type);
    740.                 }
    741.                 var fields = type.GetFields();
    742.                 foreach (var field in fields)
    743.                 {
    744.                     if (field.IsStatic)
    745.                     {
    746.                         continue;
    747.                     }
    748.                     if (partAvoiders != null && partAvoiders.Contains(field.Name))
    749.                     {
    750.                         continue;
    751.                     }
    752.                     field.SetValue(dst, field.GetValue(original));
    753.                 }
    754.                 var props = type.GetProperties();
    755.                 foreach (var prop in props)
    756.                 {
    757.                     if (!prop.CanWrite || prop.Name == "name" || prop.Name == "parent" || prop.MemberType == MemberTypes.Property)
    758.                     {
    759.                         continue;
    760.                     }
    761.                     if (partAvoiders != null && partAvoiders.Contains(prop.Name))
    762.                     {
    763.                         continue;
    764.                     }
    765.                     prop.SetValue(dst, prop.GetValue(original));
    766.                 }
    767.                 // NOTE: Some properties are references to other things and a prefab replacement can break them.
    768.                 // TODO: Should we record any reference types in order to map them to new references later?
    769.             }
    770.         }
    771.         // Shared instance of default copier.
    772.         public static DefaultComponentCopier defaultComponentCopier = new();
    773.         /// <summary>
    774.         /// Transform-specific component copier.
    775.         /// </summary>
    776.         public class TransformComponentCopier : IComponentCopier
    777.         {
    778.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    779.             {
    780.                 var oldTransform = (Transform) original;
    781.                 if (replacementPreferences.applyPosition)
    782.                 {
    783.                     newObject.transform.localPosition = oldTransform.localPosition;
    784.                 }
    785.                 if (replacementPreferences.applyRotation)
    786.                 {
    787.                     newObject.transform.localRotation = oldTransform.localRotation;
    788.                 }
    789.                 if (replacementPreferences.applyScale)
    790.                 {
    791.                     newObject.transform.localScale = oldTransform.localScale;
    792.                 }
    793.                 if (!replacementPreferences.renameObjects)
    794.                 {
    795.                     newObject.transform.name = oldTransform.name;
    796.                 }
    797.                 if (!replacementPreferences.orderHierarchyToPreview)
    798.                 {
    799.                     newObject.transform.SetSiblingIndex(oldTransform.GetSiblingIndex());
    800.                 }
    801.                 defaultComponentCopier.CopyComponent(replacementPreferences, original, newObject);
    802.             }
    803.         }
    804.         /// <summary>
    805.         /// Special for-purpose component copier for mesh renderer.
    806.         /// </summary>
    807.         public class MeshRendererComponentCopier : IComponentCopier
    808.         {
    809.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    810.             {
    811.                 var meshRenderer    = (MeshRenderer) original;
    812.                 var newMeshRenderer = newObject.GetComponent<MeshRenderer>();
    813.                 // QUESTION Should we instantiate one if one is not present?
    814.                 if (newMeshRenderer)
    815.                 {
    816.                     if (meshRenderer.sharedMaterials.Length == newMeshRenderer.sharedMaterials.Length)
    817.                     {
    818.                         var CacheMaterials = new Material[meshRenderer.sharedMaterials.Length];
    819.                         for (var a = 0; a < meshRenderer.sharedMaterials.Length; a++)
    820.                         {
    821.                             CacheMaterials[a] = meshRenderer.sharedMaterials[a];
    822.                         }
    823.                         for (var b = 0; b < CacheMaterials.Length; b++)
    824.                         {
    825.                             newMeshRenderer.sharedMaterials[b] = CacheMaterials[b];
    826.                         }
    827.                     }
    828.                 }
    829.             }
    830.         }
    831.         /// <summary>
    832.         /// Special for-purpose component copier for skinned mesh renderer.
    833.         /// </summary>
    834.         public class SkinnedMeshRendererComponentCopier : IComponentCopier
    835.         {
    836.             public void CopyComponent(ReplacementPreferences replacementPreferences, Component original, GameObject newObject)
    837.             {
    838.                 var meshRenderer    = (SkinnedMeshRenderer) original;
    839.                 var newMeshRenderer = newObject.GetComponent<SkinnedMeshRenderer>();
    840.                 // QUESTION Should we instantiate one if one is not present?
    841.                 if (newMeshRenderer)
    842.                 {
    843.                     if (meshRenderer.sharedMaterials.Length == newMeshRenderer.sharedMaterials.Length)
    844.                     {
    845.                         var CacheMaterials = new Material[meshRenderer.sharedMaterials.Length];
    846.                         for (var a = 0; a < meshRenderer.sharedMaterials.Length; a++)
    847.                         {
    848.                             CacheMaterials[a] = meshRenderer.sharedMaterials[a];
    849.                         }
    850.                         for (var b = 0; b < CacheMaterials.Length; b++)
    851.                         {
    852.                             newMeshRenderer.sharedMaterials[b] = CacheMaterials[b];
    853.                         }
    854.                     }
    855.                 }
    856.             }
    857.         }
    858.     }
    859. }