如何使用预制件替换GameObject并向预制件中添加所有组件和组件属性设置?

时间:2019-03-03 19:48:27

标签: c# unity3d

在PrefabReplace脚本的EditorWindow类型中,首先在更改之前输入类型,然后检查所选游戏对象是否没有任何单一行为的组件类型,然后进行替换。但是现在我希望能够在编辑器模式下以及在运行时模式下替换并复制所选对象。因此,我同时更改了变量newObject和组件,并使其变为静态。

然后,我不再检查所选的游戏对象是否具有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行的Extension类中遇到了异常:

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

go.AddComponent1(comp);

例外是:

TargetException:非静态方法需要一个目标

主要目标是用预制件替换游戏对象,并且预制件将具有与被替换的游戏对象相同的组件以及所有组件设置和值。

例如,如果我有一个可以左右旋转或移动的立方体,而我要用一个球体替换该立方体,那么球体就会左右旋转或移动。

我得到的空异常:

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 个答案:

答案 0 :(得分:1)

我改用了它,现在似乎可以使用了(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;
    }
}

不确定100%,但是我认为区别在于flags参数用于限制要复制的属性。显然,您尝试在其中复制一些静态值的地方。


不过,您的selection似乎还有一个问题。它没有正确更新,因此除非我在层次中选择另一个GameObject,然后比第一个我再次获得null的{​​{ 1}}在selection[i]中。


你为什么做

InstantiatePrefab

不是

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

更新

这就是我的使用方式(稍微修改了脚本)

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