0
votes

In the PrefabReplace script EditorWindow type first before the changes I checked if the selected gameobject/s don't have any components type of monobehaviour and then did the replace. But now I want to be able also to replace and duplicate the selected object/s in editor mode and also in run time mode. So I changed both variables newObject and components and made them static.

Then I'm not checking anymore if the selected gameobject/s have any componets of type monobehaviour.

And created a new class Extension with a method that should make the copy of the components to the new prefab. But it's giving me exception and not sure if it will work in any way.

The Extension class script:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

public static class Extension
{
    public static T AddComponent1<T>(this GameObject game, T duplicate) where T : Component
    {
        T target = game.AddComponent<T>();
        foreach (PropertyInfo x in typeof(T).GetProperties())
            if (x.CanWrite)
                x.SetValue(target, x.GetValue(duplicate));
        return target;
    }

    public static void Init(GameObject go, Component comp)
    {
        go.AddComponent1(comp);
    }
}

But I'm getting exception in the Extension class on the lines: 14 and 20:

x.SetValue(target, x.GetValue(duplicate));

And

go.AddComponent1(comp);

The exception is:

TargetException: Non-static method requires a target

The main goal is to replace a gameobject with prefab and that the prefab will have the same components and all components settings and values as the gameobject that was replaced.

For example if I have a cube that spin or move from side to side and I'm replacing the cube with a sphere so the sphere will spin or move from side to side either.

The null exception I'm getting:

NullReferenceException: Object reference not set to an instance of an object
Extension.GetCopyOf[T] (UnityEngine.Component comp, T other) (at Assets/Scripts/Extension.cs:22)
Extension.AddComponent[T] (UnityEngine.GameObject go, T toAdd) (at Assets/Scripts/Extension.cs:12)
Extension.Init[T] (UnityEngine.GameObject go, T comp) (at Assets/Scripts/Extension.cs:17)
PrefabReplace.InstantiatePrefab (System.Collections.Generic.IReadOnlyList`1[T] selection) (at Assets/Editor/PrefabReplace.cs:222)
PrefabReplace.Replacing () (at Assets/Editor/PrefabReplace.cs:144)
PrefabReplace.OnGUI () (at Assets/Editor/PrefabReplace.cs:44)
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <d7ac571ca2d04b2f981d0d886fa067cf>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <d7ac571ca2d04b2f981d0d886fa067cf>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <d7ac571ca2d04b2f981d0d886fa067cf>:0)
UnityEditor.HostView.Invoke (System.String methodName, System.Object obj) (at C:/buildslave/unity/build/Editor/Mono/HostView.cs:342)
UnityEditor.HostView.Invoke (System.String methodName) (at C:/buildslave/unity/build/Editor/Mono/HostView.cs:336)
UnityEditor.HostView.InvokeOnGUI (UnityEngine.Rect onGUIPosition, UnityEngine.Rect viewRect) (at C:/buildslave/unity/build/Editor/Mono/HostView.cs:310)
UnityEditor.DockArea.DrawView (UnityEngine.Rect viewRect, UnityEngine.Rect dockAreaRect, System.Boolean customBorder, System.Boolean floatingWindow, System.Boolean isBottomTab) (at C:/buildslave/unity/build/Editor/Mono/GUI/DockArea.cs:361)
UnityEditor.DockArea.OldOnGUI () (at C:/buildslave/unity/build/Editor/Mono/GUI/DockArea.cs:320)
UnityEngine.Experimental.UIElements.IMGUIContainer.DoOnGUI (UnityEngine.Event evt, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect, System.Boolean isComputingLayout) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:266)
UnityEngine.Experimental.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:438)
UnityEngine.Experimental.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:421)
UnityEngine.Experimental.UIElements.IMGUIContainer.HandleEvent (UnityEngine.Experimental.UIElements.EventBase evt) (at C:/buildslave/unity/build/Modules/UIElements/IMGUIContainer.cs:401)
UnityEngine.Experimental.UIElements.EventDispatcher.ProcessEvent (UnityEngine.Experimental.UIElements.EventBase evt, UnityEngine.Experimental.UIElements.IPanel panel) (at C:/buildslave/unity/build/Modules/UIElements/EventDispatcher.cs:511)
UnityEngine.Experimental.UIElements.EventDispatcher.Dispatch (UnityEngine.Experimental.UIElements.EventBase evt, UnityEngine.Experimental.UIElements.IPanel panel, UnityEngine.Experimental.UIElements.DispatchMode dispatchMode) (at C:/buildslave/unity/build/Modules/UIElements/EventDispatcher.cs:307)
UnityEngine.Experimental.UIElements.BaseVisualElementPanel.SendEvent (UnityEngine.Experimental.UIElements.EventBase e, UnityEngine.Experimental.UIElements.DispatchMode dispatchMode) (at C:/buildslave/unity/build/Modules/UIElements/Panel.cs:176)
UnityEngine.Experimental.UIElements.UIElementsUtility.DoDispatch (UnityEngine.Experimental.UIElements.BaseVisualElementPanel panel) (at C:/buildslave/unity/build/Modules/UIElements/UIElementsUtility.cs:245)
UnityEngine.Experimental.UIElements.UIElementsUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr) (at C:/buildslave/unity/build/Modules/UIElements/UIElementsUtility.cs:68)
UnityEngine.GUIUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr) (at C:/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:179)
1
But I'm getting exception -> what does it say? - derHugo
@derHugo Right added the exception. Not sure if to add the whole exception stack it's a bit long. - Dubi Duboni
I tried to make the variables newObject and components static so when they pass over to the extension class they will be static but it didn't solve the exception. - Dubi Duboni
when exactly do you get your exception? I tried your scripts and it seems not to be a compiler error ... I also can open the window, search objects etc so what exactly is not working? - derHugo
@derHugo In my editor I have in the Hierarchy a Cube and I attached to it a Rigidbody and a script some simple monobehaviour type and then I'm running the game and open the Prefab Replace window drag in a prefab in my case a Capsule then when I click the button Replace I'm getting the exception. - Dubi Duboni

1 Answers

1
votes

I used this instead and it seems to work now (source):

public static class Extension
{

    public static T AddComponent<T>(this GameObject go, T toAdd) where T : Component
    {
        return go.AddComponent<T>().GetCopyOf(toAdd);
    }

    public static void Init<T>(this GameObject go, T comp) where T : Component
    {
        go.AddComponent(comp);
    }

    public static T GetCopyOf<T>(this Component comp, T other) where T : Component
    {
        var type = comp.GetType();
        if (type != other.GetType()) return null; // type mis-match

        const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Default | BindingFlags.DeclaredOnly;

        var pinfos = type.GetProperties(flags);
        foreach (var pinfo in pinfos.Where(pinfo => pinfo.CanWrite))
        {
            try
            {
                pinfo.SetValue(comp, pinfo.GetValue(other, null), null);
            }
            catch { } // In case of NotImplementedException being thrown. For some reason specifying that exception didn't seem to catch it, so I didn't catch anything specific.
        }

        var finfos = type.GetFields(flags);
        foreach (var finfo in finfos)
        {
            finfo.SetValue(comp, finfo.GetValue(other));
        }

        return comp as T;
    }
}

Not 100% sure but I think the difference lies in the flags parameter for limitting which properties are copied. Apparently you where trying to copy also some static values there.


However there seems to be one more issue with your selection .. it is not updated correctly so unless I select another GameObject in the hierachy and than the first one again I get null for selection[i] in InstantiatePrefab.


Why do you do

components = selected.GetComponents(typeof(MonoBehaviour));

and not

components = selected.GetComponents(typeof(Component));

Update

This is how I used it (refactured your script a bit)

public class PrefabReplace : EditorWindow
{
    [SerializeField] private GameObject _prefab;

    private bool _selectionChanged;
    private string _objectsToSearch = "";
    private List<GameObject> _foundObjects = new List<GameObject>();
    private readonly GUIStyle _guiStyle = new GUIStyle(); //create a new variable
    private int _count;
    private bool _addFoundObjects;
    private bool _keepNames = true;
    private bool _keepPlaceInHierarchy = true;
    private static GameObject _newObject;
    private static Component[] _components;

    [MenuItem("Tools/Prefab Replace")]
    private static void CreateReplaceWithPrefab()
    {
        const int width = 340;
        const int height = 600;

        var x = (Screen.currentResolution.width - width) / 2;
        var y = (Screen.currentResolution.height - height) / 2;

        GetWindow<PrefabReplace>().position = new Rect(x, y, width, height);
    }

    private void OnGUI()
    {
        _guiStyle.fontSize = 15; //change the font size
        Searching();
        GUILayout.Space(10);
        Replacing();
        GUILayout.Space(50);
        Settings();
    }

    private void Searching()
    {
        //GUI.Label(new Rect(10, 15, 150, 20), "Search by name", guiStyle);
        _objectsToSearch = GUI.TextField(new Rect(90, 35, 150, 20), _objectsToSearch, 25);

        if (_objectsToSearch != "")
        {
            GUI.enabled = true;
        }
        else
        {
            GUI.enabled = false;
            _count = 0;
        }
        GUILayout.Space(15);
        if (GUILayout.Button("Search"))
        {
            _foundObjects = new List<GameObject>();
            _count = 0;

            foreach (var gameObj in FindObjectsOfType<GameObject>().Where(gameObj => gameObj.name == _objectsToSearch))
            {
                _count += 1;
                _foundObjects.Add(gameObj);
                foreach (Transform child in gameObj.transform)
                {
                    _count += 1;
                    _foundObjects.Add(child.gameObject);
                }
            }

            if (_foundObjects.Count == 0)
            {
                _count = 0;
            }
        }

        GUI.enabled = true;
        EditorGUI.LabelField(new Rect(90, 65, 210, 15), "Number of found objects and childs");
        GUI.TextField(new Rect(90, 80, 60, 15), _count.ToString(), 25);

        GUILayout.Space(100);
        GUI.enabled = _count > 0;
        if (GUILayout.Button("Replace found objects"))
        {
            if (_prefab != null)
            {
                InstantiatePrefab(_foundObjects);
            }
        }

        GUI.enabled = true;
    }

    private void Replacing()
    {
        GUILayout.Space(20);
        GUILayout.BeginVertical(GUI.skin.box);
        GUILayout.Label("Replacing");
        GUILayout.Space(20);

        _prefab = (GameObject)EditorGUILayout.ObjectField("Prefab", _prefab, typeof(GameObject), false);

        var selection = Selection.objects.OfType<GameObject>().ToList();

        if (_selectionChanged)
        {
            if (selection.Count == 0)
            {
                GUI.enabled = false;
            }

            for (var i = selection.Count - 1; i >= 0; --i)
            {
                var selectedObject = selection[i];
                if (_prefab != null && selection.Count > 0 &&
                    selectedObject.scene.name != null
                    && _prefab != PrefabUtility
                    .GetCorrespondingObjectFromSource(selectedObject))
                {
                    GUI.enabled = true;
                }
                else
                {
                    GUI.enabled = false;
                }
            }
        }
        else
        {
            GUI.enabled = false;
        }

        if (GUILayout.Button("Replace"))
        {
            InstantiatePrefab(selection);
            _selectionChanged = false;
        }

        GUILayout.Space(10);
        GUI.enabled = true;
        EditorGUILayout.LabelField("Selection count: " + Selection.objects.OfType<GameObject>().Count());

        GUILayout.EndVertical();
    }

    private void Settings()
    {
        _keepPlaceInHierarchy = GUILayout.Toggle(_keepPlaceInHierarchy, "Keep order place in hierarchy");
        _keepNames = GUILayout.Toggle(_keepNames, "Keep names");
    }

    private void OnInspectorUpdate()
    {
        Repaint();
    }

    private void OnSelectionChange()
    {
        _selectionChanged = true;
    }

    private void InstantiatePrefab(IReadOnlyList<GameObject> selection)
    {
        if (_prefab == null || selection.Count <= 0) return;

        for (var i = selection.Count - 1; i >= 0; --i)
        {
            var selected = selection[i];
            _components = selected.GetComponents(typeof(MonoBehaviour));

            //if (components.Length == 0)
            //{
            SceneManager.SetActiveScene(SceneManager.GetSceneByName(selected.scene.name));

            var prefabType = PrefabUtility.GetPrefabAssetType(_prefab);
            //GameObject newObject;

            if (prefabType == PrefabAssetType.Regular)
            {
                _newObject = (GameObject)PrefabUtility.InstantiatePrefab(_prefab);
            }
            else
            {
                _newObject = Instantiate(_prefab);

                if (_keepNames == false)
                {
                    _newObject.name = _prefab.name;
                }
            }
            if (_newObject == null)
            {
                Debug.LogError("Error instantiating prefab");
                break;
            }

            Undo.RegisterCreatedObjectUndo(_newObject, "Replace With Prefabs");
            _newObject.transform.parent = selected.transform.parent;
            _newObject.transform.localPosition = selected.transform.localPosition;
            _newObject.transform.localRotation = selected.transform.localRotation;
            _newObject.transform.localScale = selected.transform.localScale;
            if (_keepPlaceInHierarchy)
            {
                _newObject.transform.SetSiblingIndex(selected.transform.GetSiblingIndex());
            }
            if (_keepNames)
            {
                _newObject.name = selected.name;
            }

            foreach (var comp in _components)
            {
                _newObject.Init(comp);
            }

            Undo.DestroyObjectImmediate(selected);
            //}
        }
    }
}