【问题标题】:Unity: How to dynamically attach an unknown script to a GameObject (custom editor)Unity:如何将未知脚本动态附加到游戏对象(自定义编辑器)
【发布时间】:2023-03-15 12:30:01
【问题描述】:

我目前正在为 Unity 编辑器(自定义检查器和自定义窗口)制作一个系统,该系统将自动化并使我们正在制作的游戏的艺术家们更轻松地工作,但我遇到了障碍。

我正在尝试找到一种方法,通过编辑器文本字段输入和 GUI 按钮将未知脚本动态添加到场景中的游戏对象。 艺术家/程序员将在文本字段中输入脚本的名称,它将搜索并添加到游戏对象中,但我不知道如何进行此操作,特别是因为 gameObject.AddComponent() 的某些功能自 Unity 5.3 起已弃用

这是我尝试做的:

public string scriptname;
GameObject obj = null;
scriptname = EditorGUILayout.TextField("Script name:", scriptname, GUILayout.MaxHeight(25));
if (GUILayout.Button("Attach script"))
{
    //search for the script to check if it exists, using DirectoryInfo
    DirectoryInfo dir = new DirectoryInfo(Application.dataPath);
    FileInfo[] info = dir.GetFiles("*.*", SearchOption.AllDirectories);
    foreach (FileInfo f in info) // cycles through all the files
    {
        if(f.Name == scriptname)
        {
            //attaches to the gameobject (NOT WORKING)
            System.Type MyScriptType = System.Type.GetType(scriptname + ",Assembly-CSharp"); 
            obj.AddComponent(MyScriptType);
        }
    }
}

(当然,这是一个总结版本,我从脚本的不同部分复制了相关的行)。

但它不起作用。 有什么想法吗?

【问题讨论】:

  • 究竟是什么不起作用?找到脚本,将其附加到游戏对象或两者兼而有之?执行代码时会发生什么?
  • 附加到游戏对象是这里的问题,GUILayout.Button里面的两行。找到我正在使用 DirectoryInfo (实际上是为了检查它是否存在)。我也会将我正在使用的搜索系统添加到上面的代码中。
  • 要查找该类型是否存在,您只需执行assembly.GetTypes().Any(t => t.Name == scriptname);
  • 控制台有输出吗?比如警告或错误?
  • @NathanDanzmann 你试过我添加的解决方案了吗?

标签: c# unity3d unity5


【解决方案1】:

经过广泛的实验,我得到了这个。这也涵盖了所有 Unity 组件。只是将其作为一种扩展方法,让生活更轻松。

public static class ExtensionMethod
{
    public static Component AddComponentExt(this GameObject obj, string scriptName)
    {
        Component cmpnt = null;


        for (int i = 0; i < 10; i++)
        {
            //If call is null, make another call
            cmpnt = _AddComponentExt(obj, scriptName, i);

            //Exit if we are successful
            if (cmpnt != null)
            {
                break;
            }
        }


        //If still null then let user know an exception
        if (cmpnt == null)
        {
            Debug.LogError("Failed to Add Component");
            return null;
        }
        return cmpnt;
    }

    private static Component _AddComponentExt(GameObject obj, string className, int trials)
    {
        //Any script created by user(you)
        const string userMadeScript = "Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
        //Any script/component that comes with Unity such as "Rigidbody"
        const string builtInScript = "UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";

        //Any script/component that comes with Unity such as "Image"
        const string builtInScriptUI = "UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

        //Any script/component that comes with Unity such as "Networking"
        const string builtInScriptNetwork = "UnityEngine.Networking, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

        //Any script/component that comes with Unity such as "AnalyticsTracker"
        const string builtInScriptAnalytics = "UnityEngine.Analytics, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";

        //Any script/component that comes with Unity such as "AnalyticsTracker"
        const string builtInScriptHoloLens = "UnityEngine.HoloLens, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";

        Assembly asm = null;

        try
        {
            //Decide if to get user script or built-in component
            switch (trials)
            {
                case 0:

                    asm = Assembly.Load(userMadeScript);
                    break;

                case 1:
                    //Get UnityEngine.Component Typical component format
                    className = "UnityEngine." + className;
                    asm = Assembly.Load(builtInScript);
                    break;
                case 2:
                    //Get UnityEngine.Component UI format
                    className = "UnityEngine.UI." + className;
                    asm = Assembly.Load(builtInScriptUI);
                    break;

                case 3:
                    //Get UnityEngine.Component Video format
                    className = "UnityEngine.Video." + className;
                    asm = Assembly.Load(builtInScript);
                    break;

                case 4:
                    //Get UnityEngine.Component Networking format
                    className = "UnityEngine.Networking." + className;
                    asm = Assembly.Load(builtInScriptNetwork);
                    break;
                case 5:
                    //Get UnityEngine.Component Analytics format
                    className = "UnityEngine.Analytics." + className;
                    asm = Assembly.Load(builtInScriptAnalytics);
                    break;

                case 6:
                    //Get UnityEngine.Component EventSystems format
                    className = "UnityEngine.EventSystems." + className;
                    asm = Assembly.Load(builtInScriptUI);
                    break;

                case 7:
                    //Get UnityEngine.Component Audio format
                    className = "UnityEngine.Audio." + className;
                    asm = Assembly.Load(builtInScriptHoloLens);
                    break;

                case 8:
                    //Get UnityEngine.Component SpatialMapping format
                    className = "UnityEngine.VR.WSA." + className;
                    asm = Assembly.Load(builtInScriptHoloLens);
                    break;

                case 9:
                    //Get UnityEngine.Component AI format
                    className = "UnityEngine.AI." + className;
                    asm = Assembly.Load(builtInScript);
                    break;
            }
        }
        catch (Exception e)
        {
            //Debug.Log("Failed to Load Assembly" + e.Message);
        }

        //Return if Assembly is null
        if (asm == null)
        {
            return null;
        }

        //Get type then return if it is null
        Type type = asm.GetType(className);
        if (type == null)
            return null;

        //Finally Add component since nothing is null
        Component cmpnt = obj.AddComponent(type);
        return cmpnt;
    }
}

用法

gameObject.AddComponentExt("YourScriptOrComponentName");

了解我是如何做到这一点很重要,这样您就可以在以后的任何 Unity 更新中添加对新组件的支持。

对于用户创建的任何脚本

1。找出需要在Assembly.Load 函数中的??? 中包含的内容。

Assembly asm = Assembly.Load("???");

您可以通过将其放入脚本中来做到这一点:

Debug.Log("Info: " + this.GetType().Assembly);

我得到了:Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

我们现在应该用它替换 ???

Assembly asm = Assembly.Load("Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");

2。找出需要在asm.GetType 函数中的??? 中包含的内容。

Assembly asm = Assembly.Load("Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
Type type = asm.GetType(???); 

在这种情况下,它只是您要添加到游戏对象的脚本的名称。

假设您的脚本名称是NathanScript

Assembly asm = Assembly.Load("Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
Type type = asm.GetType("NathanScript"); 
gameObject.AddComponent(type);

对于非用户创建的 Unity 内置脚本/组件脚本

这方面的示例是RigidbodyLinerendererImage 组件。任何不是由用户创建的组件。

1。找出需要在Assembly.Load 函数的??? 中的内容。

Assembly asm = Assembly.Load("???");

您可以通过将其放入脚本中来做到这一点:

ParticleSystem pt = gameObject.AddComponent<ParticleSystem>();
Debug.Log("Info11: " + pt.GetType().Assembly);

我得到了:UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

我们现在应该用它替换 ???

Assembly asm = Assembly.Load("UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");

2。找出需要在asm.GetType 函数中的??? 中包含的内容。

Assembly asm = Assembly.Load("UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
Type type = asm.GetType(???); 

您可以通过将其放入脚本中来做到这一点:

ParticleSystem pt = gameObject.AddComponent<ParticleSystem>();
Debug.Log("Info: " + pt.GetType());

我得到了:UnityEngine.ParticleSystem

请记住,这里使用ParticleSystem 作为示例。因此,将转到asm.GetType 函数的最终字符串将按如下方式计算:

string typeString = "UnityEngine." + componentName;

假设你要添加的组件是LineRenderer:

Assembly asm = Assembly.Load("UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
string typeString = "UnityEngine." + "LineRenderer";
Type type = asm.GetType(typeString); 
gameObject.AddComponent(type);

把它放在一个扩展方法中

如您所见,添加您创建的脚本和 Unity 附带的脚本/组件需要完全不同的过程。您可以通过检查类型是否为null 来解决此问题。如果类型为null,则执行其他步骤。如果另一个步​​骤也是null,那么脚本只是退出。

【讨论】:

  • 即使这种方法相当不错,但当您想在嵌套命名空间中检查项目的类型时,它也会失败,比如说Namespace1.Namespace2.ClassDefinition,这不需要太多调查:http://rextester.com/KHVGJ63716
  • @m.rogalski It is made like that by design。当我创建那个函数时,我问自己“当你有两个同名但在不同命名空间中的类时会发生什么?”这是一个冲突,所以只需输入完全限定名称。它会找到它。所以,gameObject.AddComponentExt("Namespace1.Namespace2.ClassDefinition"); 会起作用,它只会找到那个脚本。
  • @Programmer 我确实尝试过使用您的扩展方法,但它似乎无法加载程序集,控制台显示:FileNotFoundException: Could not load file or assembly 'UnityEngine.Analytics, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.。它不适用于扩展或从this.GetType().Assembly() 手动加载程序集。很抱歉延迟反馈,正在(压倒性地)从事其他事情。 `
  • 您所要做的就是将 switch 语句放在 try catch 块中。检查更新的代码。如果有问题请告诉我。
  • @Programmer 巧合的是,我刚刚做到了,继续不加载它并引发您所做的Failed to Add component 异常。在捕获时,它会通过 UnityEngine.AnalyticsUnityEngine.HoloLens 组件不断提高 FileNotFoundException
【解决方案2】:

我建议这样做:

if(GUILayout.Button("Attach script"))
{
    // check if type is contained in your assembly:
    Type type = typeof(MeAssemblyType).Assembly.GetTypes().FirstOrDefault(t => t.Name == scriptname);
    if(type != null)
    {
        // script exists in the same assembly that MeAssemblyType is
        obj.AddComponent(type); // add the component
    }
    else
    { 
        // display some error message
    }
}

当然,如果您使用一些包含其他组件的插件(依赖项),这将失败,但要解决此问题,您只需检查程序集的依赖项即可:

typeof(MeAssemblyType) // your type from Assembly-CSharp 
    .Assembly // Assembly-CSharp assembly
    .GetReferencedAssemblies() // get referenced assemblies
    .FirstOrDefault(m => 
        m.Assembly // from this assembly
        .GetTypes() // get all types
        .FirstOrDefault(t => 
            t.Name == scriptname // select first one that matches the name
        )
    )

备注:

GetReferencedAssemblies 方法将仅返回您的程序集“使用”(加载)的程序集。为了清楚起见,假设您正在引用这些程序集:

  1. System.Xml,
  2. NewtonsoftJson

还有这段代码:

static void Main()
{
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(<some_xml_input>);
}

那么GetReferencedAssemblies 的输出会看起来像这样:

>>> System.Xml, Version=<version>, Culture=neutral, PublicKeyToken=<key>

意味着它不会加载 NewtonsoftJson,因为它没有在该程序集中使用。

更好的建议:

我建议您混合@Programmer 答案中的方法,但不要加载程序集,因为它们在 Unity 的编辑器启动您的项目时已经加载。而是使用GetReferencedAssemblies 方法,并从那里调用GetTypes 方法来检索该程序集中所有可能的类型。 (这会很慢,但会保证你得到想要的结果)之后你可以使用FirstOrDefault或者自己遍历Type[]来找到你想要的。

【讨论】:

    【解决方案3】:

    这仍然是可能的。使用这个

    UnityEngineInternal.APIUpdaterRuntimeServices.AddComponent(GameObject go, "", string componentName);
    

    希望有帮助

    【讨论】:

    • 这和"AddComponent(string)一样已被弃用。
    • 已弃用意味着它将很快被删除。它在后台使用AddComponent(string)
    • 问题是关于当前 Unity 版本而不是未来版本。该解决方案目前适用于 Unity 5.5
    • 它说在我这边已弃用。那么,您鼓励 OP 使用已弃用的 API?这根本不能解决问题。
    • 猜猜你还没试过这是否真的有效? @程序员
    【解决方案4】:

    反编译Unity的AddComponentWindow。还加了链接:
    AddComponentAdjusted

    然后像这样调用窗口:

      ws.winx.editor.windows.AddComponentWindow.Show(rect);
    
                ws.winx.editor.windows.AddComponentWindow.OnClose += OnCloseComponentSelectedFromPopUpMenu;
                ws.winx.editor.windows.AddComponentWindow.ComponentSelected += (menuPath) => ComponentSelectedFromPopUpMenu(positionData.Item1, menuPath);
    

    处理返回

        private void ComponentSelectedFromPopUpMenu(Vector2 position, string menuPath) {
            
            
                        MonoScript monoScript;
            
                        char[] kPathSepChars = new char[]
                        {
                            '/',
                            '\\'
                        };
            
                        menuPath = menuPath.Replace(" ", "");
                        string[] pathElements = menuPath.Split(kPathSepChars);
            
                        string fileName = pathElements[pathElements.Length - 1].Replace(".cs", "");
            
            
            
            
                        if (pathElements[0] == "Assets") {
            
                            Debug.LogWarning("Unity need to compile new added file so can be included");
            
            
                        } else if (pathElements.Length == 2) {
            
    //use fileName
                            //do something
            
                            
                        } else if (pathElements[1] == "Scripts") {//Component/Scripts/MyScript.cs
                            
            
                            string[] guids = AssetDatabase.FindAssets("t:Script " + fileName.Replace(".cs", ""));
            
                            if (guids.Length > 0) {
            
                                for (int i = 0; i < guids.Length; i++) {
                                    monoScript = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i]), typeof(MonoScript)) as MonoScript;
                                    Type typet = monoScript.GetClass();
            
                                    if (typet == null) continue;
            
            
                                   
            
                        } else {//Component/Physics/Rigidbody
                            //try to find by type, cos probably Unity type
                            Type unityType = ReflectionUtility.GetType("UnityEngine." + fileName);
            
                            if (unityType != null) {
            
        //do something
            
                                return;
            
                            }
            
            
            
            
            
            //Based on attribute  [AddComponentMenu("Logic/MyComponent")] 
                            //Component/Logics/MyComponent
                            string[] guids = AssetDatabase.FindAssets("t:Script " + fileName.Replace(".cs", ""));
            
                            if (guids.Length > 0) {
            
                                for (int i = 0; i < guids.Length; i++) {
                                    monoScript = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i]), typeof(MonoScript)) as MonoScript;
                                    Type typet = monoScript.GetClass();
            
                                    if (typet == null) continue;
            
                                    object[] addComponentMenuAttributes = typet.GetCustomAttributes(typeof(AddComponentMenu), true);
            
            
            
                                    if (addComponentMenuAttributes != null && addComponentMenuAttributes.Length > 0 && "Component/" + ((AddComponentMenu)addComponentMenuAttributes[0]).componentMenu == menuPath)
                                    {
            
                                        //do somethings
            
                                    }
                                }
            
            
                            }
            
            
                        }
                    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-08
      • 1970-01-01
      • 1970-01-01
      • 2023-01-06
      相关资源
      最近更新 更多