簡體   English   中英

Unity:如何將未知腳本動態附加到GameObject(自定義編輯器)

[英]Unity: How to dynamically attach an unknown script to a GameObject (custom editor)

我目前正在為Unity編輯器(自定義檢查器和自定義窗口)制作一個系統,該系統可以自動化,並使從事我們正在制作的游戲的美術師更輕松,但是我遇到了麻煩。

我正在嘗試找到一種方法,通過編輯器Textfield輸入和GUI按鈕向場景中的游戲對象動態添加未知腳本。 美術師/程序員將在文本字段中鍵入腳本的名稱,它將搜索並添加到游戲對象中,但是我不知道如何進行此操作,特別是因為從某些gameObject.AddComponent()不推薦使用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);
        }
    }
}

(當然,這是一個總結版本,我從腳本的不同部分復制了相關行)。

但這是行不通的。 有任何想法嗎?

經過廣泛的實驗,我能夠做到這一點。 這也涵蓋了所有 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<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;

假設您要添加的Component是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(<some_xml_input>);
}

然后, GetReferencedAssemblies的輸出將看起來像這樣:

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

這意味着它將不會加載NewtonsoftJson因為它沒有在該程序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

                                }
                            }


                        }


                    }
                }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM