Generate Script in Editor Script and GetType() returns null.

So I’ve got an Editor Script which holds a MenuItem, which when called, should create a new script with a supplied name, and automatically add the new script as a component to an object. This will never be done during play mode, so speed isn’t an issue. (Obviously a 5 day wait is out of the question).

However I can create the script, import it into the asset database, that’s all fine, the issue is when I want to add it as a component.

If I use GetType(), and supply the new script as a string, I always get null returned. I’ve tried supplying different Assembly strings, to no avail. I’ve tried using Activator.CreateInstance(), and for some reason I get a completely unrelated error with a third party plugin.

I don’t have the code at hand, but this is what I’m doing:

public class Foo : Editor
{

	[MenuItem(...)]
	public static void Bar()
	{
		string newClassName = "FooBar"; //This is actually gotten from a save dialog box.

		string[] lines = File.ReadAllLines( "Template.cs" );
		foreach( line in lines ) // I'm aware you can't re-assign line, but pesudocode.
		{
			line = line.replace("CLASS_NAME", newClassName);
		}
		
		File.WriteAllLines( newClassName + ".cs", lines);

		GameObject go = new GameObject();

		AssetDatabase.Refresh( ImportAssetDatabase.LoadSync );

		Type type = Type.GetType( newClassName );

		// type is null.

		go.AddComponent( type );

	}


}

Is there anyway to properly refresh the AssetDatabase and gain access to the type? Am I doing this the wrong way?

(The worst part of all this, is I got it to work, changed something, and never got it to work again. In my frantic Ctrl/Cmd+Z frenzy, guess what idiot hit another key. Anyway my point is, I know this is possible, which is why I haven’t given up.)

Any ideas?

(Clarify: I want to get the Type of a generated script. For some reason AssetDatabase.Refresh() still makes GetType() return null even though it’s done a full recompile.)

Oh the bane of my Code Generation existence.

@Bunny83 had the correct idea. The only change I would suggest is using an EditorPref and a callback when scripts are generated. Lucky for your this is very easy to do. The thing I hate about it is the fact that it’s hard to follow.

    internal static void AddComponents(string[] classes)
    {
      EditorPrefs.SetString(PENDING_CLASSES_KEY, JSON.Dump(classes));
      AssetDatabase.Refresh();
    }

    [DidReloadScripts] 
    internal static void AddComponentsCheck()
    {
      if (EditorPrefs.HasKey(PENDING_CLASSES_KEY))
      {
        string[] pendingClasses;

        JSON.MakeInto<string[]>(JSON.Load(EditorPrefs.GetString(PENDING_CLASSES_KEY)), out pendingClasses);

        for (int i = 0; i < pendingClasses.Count; i++)
        {
          //Do your logic with your classes
        }
        EditorPrefs.DeleteKey(PENDING_CLASSES_KEY);
      }
    }
  }

That is just one stripped out example of how I do it. Keep in mind I use TinyJson. It is really useful for this type of task.

Cheers,

I would say the way you have it at the moment it’s impossible. When Unity (re-) compiles a script it actually compiles all scripts of the same compilation group. As result a new assembly is created (which will replace the old one). However before Unity can exchange / update the assembly, the whole scripting environment is shut down. Before that happens Unity serializes everything it can. Every C# object will be recreated and deserialized after the recompilation. That’s why you can’t have any code running when Unity does the recompile. The current execution stack can’t be serialized and restored.

What might work is, when you save the script name in a member variable of a serializable object as well as the information that you want to add the new component and to which gameobject. An EditorWindow would be the most straight forward. When the recompilation is finished your EditorWindow should receive another OnEnable callback which you can use to check if an “add component task” is pending, execute that task and remove / destroy it.

This is all just a theoretical solution. You might face more problems which might involve thread concurrency depending on how and where you implement your code.

Just to make that clear: Your “Bar” method has to finish before Unity can do it’s “serialize->destroy->assembly reload->recreate->deserialize” thing which is required to access the new class.

I don’t know the usual convention for posting an answer when someone gave me the answer, so I apologise if this shouldn’t be here, but I wanted to share my findings:

So with the glorious input of @BMayne and @Bunny83, here’s how it goes:

public class Foo : Editor
{
	[MenuItem("Code Generator", false, 0)]
     public static void CreateStory()
     {
    	 // Get the name wanted, using this as an input dialog.
         string path = EditorUtility.SaveFilePanelInProject(...);
         if (string.IsNullOrEmpty(path))
         {
             Debug.LogWarning("No name specified");
             return;
         }
 	    // Take out all the path junk, and format it.
		string className = System.IO.Path.GetFileNameWithoutExtension (path);
        string classToFile = className.Replace( " ", "" ).Replace( "-", "" );
    	//(Basically remove all illegal characters, I know I’m missing a few)
    
     	// Creating an object to reference later to add the component to.
        GameObject go = new GameObject();
        go.name = classToFile;
    
    	// Get the location of the new script and the template script.
        string newPath = Application.dataPath + "/Scripts/Generated/" + classToFile + ".cs";
        string templatePath = Application.dataPath + "/Scripts/Template/Template.txt" //.txt so it doesn’t compile
	          
	    // Copy the files, this apparently can be done in lots of ways.
	    File.Copy (templatePath, newPath);
	          
	    // So copy all the template lines, replace the template names, and write them to the new file.
	    string[] lines = File.ReadAllLines (newPath);
	          
	    for(int i = 0; i < lines.Length; i++)
	    {
			lines <em>= lines*.Replace("CLASS_TEMPLATE", classToFile);*</em>

* }*

* File.WriteAllLines (newPath, lines);*

* // @BMayne’s amazing suggestion, set the name for later reference.*
* EditorPrefs.SetString (“New Class Name”, classToFile);*

* // You probably don’t need to do both of these, but I’m just making sure.*
* // (Experiment with the ones you might or might not need)*
* AssetDatabase.ImportAsset (newPath);*
* AssetDatabase.Refresh ( ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate );*
* }*

* [UnityEditor.Callbacks.DidReloadScripts]*
* private static void ScriptReloaded( )*
* {*
* // If the key doesn’t exist, don’t bother, as we’re not generating stuff.*
* if (!EditorPrefs.HasKey (“New Class Name”))*
{
return;
}

* // If they key does exist and the object doesn’t, it’s just a left over key.*
string name = EditorPrefs.GetString (“New Class Name”);
* GameObject go = GameObject.Find( name );*

* if (go == null)*
* {*
* return;*
* }*

* // Get the new type from the reloaded assembly!*
* // (It won’t work without assembly specification, because this*
* // is an editor script, so the default assembly is “Assembly-CSharp-editor”)*
* Type type = Types.GetType (name, “Assembly-CSharp”);*
* go.AddComponent( type );*

* //Delete the key because we don’t need it anymore!*
* EditorPrefs.DeleteKey(“New Class Name”);*
* }*

}