2017-02-20 18 views
2

В настоящее время я создаю систему для редактора Unity (пользовательский инспектор и пользовательские окна), который будет автоматизировать и упростить работу художников, работающих на мы делаем, но я ударил кирпичную стену.Unity: Как динамически присоединить неизвестный скрипт к GameObject (настраиваемый редактор)

Я пытаюсь найти способ динамически добавлять через редактор Textfield ввод и кнопку 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); 
     } 
    } 
} 

(конечно, это суммируется до версии, я скопировал соответствующие строки из различных частей сценария).

Но это не работает. Любые идеи?

+0

Что именно не работает? Поиск скрипта, прикрепление его к игровому объекту или к тому и другому? Что происходит при выполнении кода? –

+0

Прикрепление к игровому объекту - проблема здесь, две строки внутри GUILayout.Button. Чтобы найти, я использую DirectoryInfo (на самом деле проверить, существует ли он). Я добавлю систему поиска, которую я использую в код выше. –

+0

Чтобы узнать, существует ли тип, вы можете просто выполнить 'assembly.GetTypes(). Любой (t => t.Name == scriptname);' –

ответ

2

После обширного эксперимента я смог получить это. Это покрывает все компоненты 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.

Для любого сценария, созданного пользователями:

.find, что нужно, чтобы быть в ??? в функции 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"); 

.find, что должно быть в ??? в функции 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 построен в скриптах/компонентов сценариев не созданных пользователями:

Пример этого является Rigidbody, Linerenderer , Image компонентов. Просто любой компонент, не созданный пользователем.

. Отметить, что должно быть в ??? в функции 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"); 

.find, что должно быть в ??? в функции 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); 

Собираем вместе в способе расширения:

Как вы можете видеть, добавление скриптов, которые вы создали, и скрипт/компонент ents, которые поставляются с Unity, требует совершенно другого процесса. Вы можете исправить это, проверив тип, если null. Если тип null, выполните другой шаг. Если другой шаг равен null, то скрипт просто делает не выход.

+0

Несмотря на то, что этот метод довольно хорош, он не удастся, если вы захотите проверить тип вашего проекта в пределах вложенного пространства имен, скажем, 'Namespace1.Namespace2.ClassDefinition', который не требует большой части расследования: [http: // rextester .com/KHVGJ63716] (http://rextester.com/KHVGJ63716) –

+0

@ m.rogalski 'Сделано это по дизайну'. Когда я выполнял эту функцию, я спросил себя: «Что происходит, когда у вас есть два класса с одним и тем же именем, но в разных пространствах имен?» Это конфликт прямо там, поэтому все, что нужно сделать, это ввести * полное имя *. Он найдет его. Итак, 'gameObject.AddComponentExt (" Namespace1.Namespace2.ClassDefinition ");' будет работать, и он найдет именно этот скрипт. – Programmer

+0

@Programmer Я попытался использовать ваш метод расширения, но он, похоже, не может загрузить сборку, консоль показывает: 'FileNotFoundException: не удалось загрузить файл или сборку UnityEngine.Analytics, Version = 0.0.0.0, Culture = neutral , PublicKeyToken = null 'или одна из его зависимостей. Система не может найти указанный файл. '. Он не работает с расширением или вручную загружает сборку из 'this.GetType(). Assembly()'. Извините за задержку с отзывами, было (в подавляющем большинстве) работать над другими вещами. ' –

1

Я предложил бы делать это так:

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[] себя, чтобы найти тот, который вы хотите.

0

Это все еще возможно.Используйте этот

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

Надежда, что помогает

+0

Это устарело, а также '' AddComponent (string) '. – Programmer

+0

Все еще работает в Unity 5.5 –

+0

Устаревшее означает, что он будет удален очень скоро. Он использует' AddComponent (string) 'под капотом. – Programmer

0

Декомпилировать AddComponentWindow of Unity. Узнайте, как это делается. Не изобретайте велосипед. Вы ленивы увидеть ссылку

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 

           } 
          } 


         } 


        } 
       }