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. The writing onto the text file is giving me problems.
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;nnpublic class " + input_text + " : MonoBehaviourn{nn}";
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.Log
s run be them before or after the WriteAllText
. Also, the code won’t recognize the active Gameobject at all and hence won’t attach the script to it.
What could be the problem, and how can I fix it?
Think about the process of manually adding a component. After creating a script file, you need to wait for Unity to complete compilation and reload the assemblies before you can add this component through the Add Component menu. Now you need to wait for this process programmatically.
OnEnable
is a callback event that informs you the assemblies have been reloaded, so you can save the script name (in EditorPrefs
) after writting the script file, then attach the script to the game object until the next invocation of OnEnable
.
File.WriteAllText(path, template);
EditorPrefs.SetString("LASTADDEDSCRIPT", input_text);
private void OnEnable()
{
....
var s = EditorPrefs.GetString("LASTADDEDSCRIPT");
if(!string.IsNullOrEmpty(s))
{
EditorPrefs.DeleteKey("LASTADDEDSCRIPT");
attachScriptToGameobject(s);
Close();
}
}
Something need to be noted:
-
The type name passed to
GetType
should be assembly-qualified, thus you need to append the assembly name. (e.g.Assembly-CSharp
)System.Type.GetType(input_text + ",Assembly-CSharp")
-
The selected object can be changed during this process, you need to find other ways to retain it. (e.g. GlobalObjectId)
-
There are also other ways to learn about assemblies reload event, such as AssemblyReloadEvents, InitializeOnLoadAttribute, etc.
I haven’t explicitly tried it, but you probably want to use AssetDatabase.ImportAsset with the ImportAssetOptions.ForceSynchronousImport.
This should synchronously import the newly created asset. Furthermore you probably want to use LoadAssetAtPath with your asset path. Keep in mind that asset path always starts with "Assets/"
and always uses forward slashes. LoadAssetAtPath
will actually return a MonoScript instance. A MonoScript
is actually a TextAsset
but has additional methods. The one relevant for you is the GetClass method which returns the System.Type
reference of the class that is represented by this script asset.
Also note that editor scripts should not use AddComponent. For editor scripts there’s the Undo class which offers all sorts of methods like AddComponent to carry out editor actions. This class ensures that those changes are properly registrated in the Undo system.
1