Generating Scripts From Menu Items

I am trying to make a menu item to generate scripts. I want this item to show in the “Add Component” menu for game objects. The goal is for the menu item to ask for a string “input_text” and generate a script with that name. Then, the script is attached to the game object it was called on if the function was called from the “Add Component” menu.

using System.IO;
using UnityEditor;
using UnityEngine;

public class ScriptBundle : EditorWindow{
    private string input_text = "";
    
    [MenuItem("Component/Custom Items/Script and Editor")]
    public static void ShowWindow(){
        ScriptBundle win = GetWindow<ScriptBundle>("Script + Editor Generator");
        win.minSize = new Vector2(400,100);
        win.maxSize = new Vector2(400,100);
        win.position = new Rect(Screen.width/2, Screen.height/2, 400, 100);
    }

    private void OnEnable(){
        this.minSize = new Vector2(400,100);
        this.maxSize = new Vector2(400,100);
        this.position = new Rect(Screen.width/2, Screen.height/2, 400, 100);
    }

    private void OnGUI(){
        GUILayout.Label("Generate a script and its inspector editor script", EditorStyles.boldLabel);
        input_text = EditorGUILayout.TextField("Script Name: ", input_text);

        GUILayout.Space(10);

        if (GUILayout.Button("Generate")){
            Debug.Log(input_text);
            
            GenerateScript(input_text);
            attachScriptToGameobject(input_text);

            Close();
        }
    }

    private void GenerateScript(string input_text){
        Debug.Log("Generate");
        
        string path = $"Assets/{input_text}.cs";

        if (File.Exists(path)){
            Debug.LogError("A script with this name already exists.");
        }

        string template = "using UnityEngine;\n\npublic class " + input_text + " : MonoBehaviour\n{\n\n}";

		try{
			//File.WriteAllText(path,template);
		}catch(Exception ex){
			Debug.Log($"Faild to write: {ex}")
		}

        AssetDatabase.Refresh();
    }

    private void attachScriptToGameobject(string input_text){
        Gameobject selected = Selection.activeGameObject;
		Debug.log(selected.transform.name);
		
		if (selected != null){
			selected.AddComponent(System.Type.GetType(input_text));
			Debug.Log("Script Attached");
		}
    }
}

Notice how the line for writing the template to the script is commented. This line causes issues when it is executed. None of the Debug.Logs run be them before or after the WriteAllText. Also, the code won’t recognize the activeGameobject at all and hence won’t attach the script to it.

What could be the problem, and how can I fix it?

There’s an undocumented class called ProjectWindowUtil that can make script files off of a proper template: UnityCsReference/Editor/Mono/ProjectBrowser/ProjectWindowUtil.cs at master · Unity-Technologies/UnityCsReference · GitHub

That said I don’t think you can recreate “New Script” add component menu as it uses internal stuff: UnityCsReference/Editor/Mono/Inspector/Core/AddComponent/NewScriptDropdownItem.cs at 611307378c56a1b2f90eb1018332166cbe3c9c03 · Unity-Technologies/UnityCsReference · GitHub

You would have to wait until after a domain reload then apply the component. Not sure the best way to do that off the top of my head.

Creating the script shouldn’t be a problem, it works fine in my testing.

You have two compile errors in your script (in attachScriptToGameobject: Gamebject instead of GameObject, Debug.log instead of Debug.Log). Maybe the code didn’t get properly recompiled and reloaded while you were testing and you were using an old version of it (from the last time it successfully compiled)?

But there’s a fundamental issue: After writing the script file, Unity first needs to compile it and then do a domain reload to load the new assemblies containing the new script. If you try to find the script type immediately after creating the file, the type won’t exist yet and it will fail.

You have to wait for the domain reload to complete, and only then you can get the type and attach the script. Since you’re in an editor window, you can wait for OnEnable to be called again after the domain reload. Any public or private serializable field will be carried through the reload but any non-serializable or static fields will be reset.

Also, Type.GetType often won’t be able to find types without an assembly-qualified name. A better approach is to get the importer for the script, call MonoImporter.GetScript to the the MonoScript instance, and then MonoScript.GetClass to get the type. This will ensure the type matches what Unity uses.

1 Like

Have you tried clicking “Add Component” and then navigate to “New Script”? Because that’s a thing, you don’t need to make a custom script to do that. :wink:
At least it was in my Unity 6 project yesterday.

And if anything, use UI Toolkit for editor UI, not the archaic, verbose, error prone, issue-laden IMGUI.

Wrong! You wrote a single file with System.IO and therefore you should call this instead:

AssetDatabase.ImportAsset(path);

Refreshing the entire AssetDatabase is a potentially costly operation and should be avoided whenever you can, which is 99.9% of all cases!