3

I'm currently making a system for the Unity Editor (custom inspector and custom windows) that will automate and make things easier for the artists working on the game we're making, but I've hit a brick wall.

I'm trying to find a way to dynamically add, through an editor Textfield input and a GUI button, an unknown script to a gameobject in the scene. The artist/programmer will type the name of the script in the textfield and it will search and add to a gameobject, but I don't know how to proceed with this, specially since some functions of gameObject.AddComponent() are deprecated as of Unity 5.3

Here's what I tried to do:

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

(Of course, this is a summed up version, I copied the relevant lines from different parts of the script).

But it doesn't work. Any ideas?

5
  • What exactly is not working? Finding the script, attaching it to the gameobject or both? What happens when you execute your code? Commented Feb 20, 2017 at 14:46
  • Attaching to the gameobject is the problem here, the two lines inside the GUILayout.Button. To find I'm using DirectoryInfo (actually to check if it exists). I'll add the search system I'm using to the code above as well. Commented Feb 20, 2017 at 14:47
  • To find if the type exists you can just do assembly.GetTypes().Any(t => t.Name == scriptname); Commented Feb 20, 2017 at 14:49
  • Do you get any output in the console? Such as warnings or errors? Commented Feb 20, 2017 at 15:38
  • @NathanDanzmann Did you try the solution I added? Commented Feb 22, 2017 at 8:01

6 Answers 6

6

After extensive experiment I was able get this. This covers all the Unity components too. Just made it an extension method to make life easier.

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

Usage:

gameObject.AddComponentExt("YourScriptOrComponentName");

It is important to understand how I did it so that you can add support for new components in any future Unity updates.

For any script created by users:

1.Find out what needs to be in the ??? in the Assembly.Load function.

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

You can do that by putting this in your script:

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

I got: Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

We should now replace ??? with that.

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

2.Find out what needs to be in the ??? in the asm.GetType function.

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

In this case, it is simply the name of the script you want to add to the GameObject.

Let's say that your script name is NathanScript:

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

For Unity built in scripts/components scripts not created by users:

Example of this is the Rigidbody, Linerenderer, Image components. Just any component not created by the user.

1.Find out what needs to be in the ??? in the Assembly.Load function.

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

You can do that by putting this in your script:

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

I got: UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

We should now replace ??? with that.

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

2.Find out what needs to be in the ??? in the asm.GetType function.

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

You can do that by putting this in your script:

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

I got: UnityEngine.ParticleSystem

Remember that ParticleSystem is used as an example here. So the final string that will go to the asm.GetType function will be calculated like this:

string typeString = "UnityEngine." + componentName;

Let's say that the Component you want to add is 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);

Putting it together in an extension method:

As you can see, adding scripts you created and the script/components that comes with Unity requires totally different process. You can fix this by checking if the type if null. If the type is null, perform the other step. If the other step is null too then the script simply does not exit.

Sign up to request clarification or add additional context in comments.

13 Comments

Even though this method is fairly good it will fail when you would want to check for the type from your project within nested namespace let's say Namespace1.Namespace2.ClassDefinition which does not require much of an investigation : http://rextester.com/KHVGJ63716
@m.rogalski It is made like that by design. When I was making that function, I asked my self "what happens when you have two classes with the-same name but in different namespace?" That's a conflict right there so all have to do is type in the fully qualified name. It will find it. So, gameObject.AddComponentExt("Namespace1.Namespace2.ClassDefinition"); will work and it will find just that script.
@Programmer I did try using your extension method but it doesn't seem to be able to load the assembly, console shows: 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.. It doesn't work with the extension or from manually loading the Assembly from this.GetType().Assembly(). Sorry for the delay on feedback, was (overwhelmingly) working on other things. `
All you have to do is put the switch statement inside a try catch block. Check the updated code. Let me know if there is a problem.
@Programmer As a matter of coincidence I just did, keeps on not loading it and raising the Failed to Add component exception you made. On the catch it keeps raising FileNotFoundException with the UnityEngine.Analytics and UnityEngine.HoloLens Assemblies.
|
1

I would suggest doing this like so :

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

Of course this will fail if you use some plugins ( dependencies ) which contains other components but to deal with this you can just check dependencies of your assembly :

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

Remarks :

GetReferencedAssemblies method will only return assemblies that were "used" ( loaded ) by your assembly. To make it a bit clear, assume you're referencing these assemblies:

  1. System.Xml,
  2. NewtonsoftJson

And this piece of code :

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

Then the output of GetReferencedAssemblies will look somewhat like that :

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

Meaning it will not load NewtonsoftJson because it was not used inside of that assembly.

Better suggestion :

I would suggest you mixing up the method from @Programmer answer but not load the assemblies because they are already loaded when Unity's editor starts with your project. Instead use GetReferencedAssemblies method and from there you should call GetTypes method to retrieve all possible types in that assembly. ( It will be slow but will guarantee you the desired results ) After that you can just use FirstOrDefault or just iterate through the Type[] yourself to find the one you want.

Comments

0

This is still possible. Use this

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

Hope that helps

8 Comments

This is deprecated as well as "AddComponent(string).
Deprecated means it will be removed very soon. It uses AddComponent(string) under the hood.
Question is regarding current Unity version not future ones. This solution does work currently with Unity 5.5
It says deprecated on my side. So, you are encouraging OP to use a deprecated API? This simply does not solve the problem.
Guess you haven't tried if this actually work? @Programmer
|
0

Decompile AddComponentWindow of Unity. Also added the link:
AddComponentAdjusted

Then call the window something like this:

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

Handle return

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

Comments

0

You can iterate over the assemblies in the app domain. Then search for that name in that assembly's GetTypes() (like @mrogal.ski's answer)

static Type FindType(string typeName, string namespaceString = "") 
{
    foreach(var asm in AppDomain.CurrentDomain.GetAssemblies()) 
    {
        var type = asm.GetTypes().FirstOrDefault(t => 
            t.Name == typeName && (string.IsNullOrEmpty(namespaceString) || namespaceString == t.Namespace));

        if (type != null) { return type; }
    }
    return null;
}

Usage like this:

var someGameObject = new GameObject("FindTypeDemoObject");
var rbType = FindType("Rigidbody");
someGameObject.AddComponent(rbType);

var someMonoBType = FindType("SomeMonoB", "Fake.Example.Namespace");
someGameObject.AddComponent(someMonoBType);

Comments

0

this worked for me obj.AddComponent(System.Type.GetType(ScriptToAdd.name + ",Assembly-CSharp"));

  • ScriptToAdd is UnityEngine.Object

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.