Как я могу заменить GameObject сборным файлом и добавить в сборный файл все компоненты и настройки свойств компонентов в сборный файл?

#c# #unity3d

#c# #unity-game-engine

Вопрос:

В редакторе скриптов PrefabReplace сначала введите Window перед внесением изменений, я проверил, не имеют ли выбранные gameobject / ы каких-либо компонентов типа monobehaviour, а затем выполнил замену. Но теперь я хочу иметь возможность также заменять и дублировать выбранные объекты в режиме редактора, а также в режиме выполнения. Итак, я изменил обе переменные newObject и components и сделал их статическими.

Тогда я больше не проверяю, есть ли у выбранных gameobject / ов какие-либо компоненты типа monobehaviour.

И создал новое расширение класса с методом, который должен скопировать компоненты в новый сборный файл. Но это выдает мне исключение, и я не уверен, будет ли это работать каким-либо образом.

Скрипт класса расширения:

 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);
    }
}
  

Но я получаю исключение в классе расширения в строках: 14 и 20:

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

И

 go.AddComponent1(comp);
  

Исключением является:

TargetException: Нестатический метод требует целевого

Основная цель состоит в том, чтобы заменить gameobject сборным объектом и чтобы сборный объект имел те же компоненты и все настройки компонентов и значения, что и gameobject, который был заменен.

Например, если у меня есть куб, который вращается или перемещается из стороны в сторону, и я заменяю куб сферой, чтобы сфера тоже вращалась или перемещалась из стороны в сторону.

Исключение null, которое я получаю:

 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 -> что там говорится?

2. @derHugo Справа добавил исключение. Не уверен, стоит ли добавлять весь стек исключений, это немного длинновато.

3. Я попытался сделать переменные newObject и components статическими, чтобы при переходе к классу расширения они были статичными, но это не решило проблему исключения.

4. когда именно вы получаете свое исключение? Я попробовал ваши скрипты, и, похоже, это не ошибка компилятора… Я также могу открыть окно, выполнить поиск объектов и т.д. Итак, что именно не работает?

5. @derHugo В моем редакторе у меня есть в иерархии куб, и я прикрепил к нему жесткое тело и скрипт какого-нибудь простого типа monobehaviour, а затем я запускаю игру и открываю окно замены сборного объекта, перетаскиваю сборный объект, в моем случае капсулу, затем, когда я нажимаю кнопку Заменить, я получаю исключение.

Ответ №1:

Я использовал это вместо этого, и, похоже, теперь это работает (исходный код):

 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;
    }
}
  

Не уверен на 100%, но я думаю, что разница заключается в flags параметре для ограничения того, какие свойства копируются. По-видимому, вы пытаетесь скопировать туда также некоторые статические значения.


Однако, похоже, есть еще одна проблема с вашим selection .. он обновляется некорректно, поэтому, если я не выберу другой GameObject в иерархии, а не первый, я снова получу null for selection[i] в InstantiatePrefab .


Почему вы делаете

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

и не

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

Обновить

Вот как я это использовал (немного переработал ваш скрипт)

 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 amp;amp; selection.Count > 0 amp;amp;
                    selectedObject.scene.name != null
                    amp;amp; _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);
            //}
        }
    }
}
  

Комментарии:

1. Как вы используете его сейчас? Я все еще выполняю цикл над массивом components в скрипте замены сборного файла и вызываю Init: Extension. Инициализировать (newObject, components[i); Но затем получить нулевое исключение в скрипте расширения в строке 22 var type = comp. GetType(); Должен ли я также использовать и вызывать метод AddComponent, как я делал раньше?

2. о, извините, я только что создал Init также метод расширения и провел некоторую реорганизацию… поэтому вместо Extension.Init(newObject, components[ii]); я называю это как foreach (var comp in components) { newObject.Init(comp); }

3. Странно, что при переходе к методу getcopy из comp значение равно null. Но я видел, что в скрипте замены сборного файла в цикле foreach компоненты не равны null, и у него есть 3 компонента в массиве. И я изменил компоненты, чтобы получить typeof Component. компоненты = выбраны. getComponents(typeof(Component)); по-прежнему в GetCopyOf comp значение null, но оно не было null при инициализации в скрипте расширения. Я использовал точку останова для его отладки.

4. Я все еще получаю исключение null. Я добавил к вопросу полное сообщение об исключении, может быть, вы сможете взглянуть на него? И в скрипте я увидел, что вы получаете component typeof MonoBehaviour, поэтому я изменил его на Component, но все то же исключение. И я попробовал другие объекты для замены того же исключения.

5. Вот ссылка моего onedrive на файл пакета моего проекта, который я экспортировал. Может быть, вы можете загрузить его только 31 МБ и увидеть проблему? 1drv.ms/u/s !AjeFgOHf0Ubnat6e6GhHEqgLu-E