Unity:如何将未知脚本动态附加到GameObject(自定义编辑器)

我目前正在为Unity编辑器(自定义检查器和自定义窗口)创建一个系统,该系统将自动化并使我们正在制作游戏的艺术家更容易,但我已经碰到了一堵砖墙。

我正在尝试找到一种方法,通过编辑器Textfield输入和GUI按钮,将未知脚本动态添加到场景中的游戏对象。 艺术家/程序员将在文本字段中键入脚本的名称,它将搜索并添加到游戏对象,但我不知道如何继续执行此操作,特别是因为gameObject.AddComponent()某些function已弃用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); } } } 

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

但它不起作用。 有任何想法吗?

经过广泛的实验,我得到了这个。 这也涵盖了所有 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(???); 

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

假设您的脚本名称是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(); 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(); 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则脚本不会退出。

我建议这样做:

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

那么GetReferencedAssemblies的输出看起来有点像:

 >>> System.Xml, Version=, Culture=neutral, PublicKeyToken= 

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

更好的建议:

我建议你混合@Programmer回答的方法但不加载程序集,因为当Unity的编辑器从你的项目开始时它们已经被加载了。 而是使用GetReferencedAssemblies方法,然后从那里调用GetTypes方法来检索该程序集中的所有可能类型。 (它会很慢,但会保证你想要的结果)之后你可以使用FirstOrDefault或只是自己迭代Type[]找到你想要的那个。

这仍然是可能的。 用这个

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

希望有所帮助

反编译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); 

处理返回(棘手的部分,再次从聪明的Unity同性恋中学习)

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