I just had a look at the code of the AddComponentWindow which is responsible for creating the new scripts. It has a nested class called “NewScriptElement” which handles the specifics about the script creation and actually draws the edit box for the script name and the drop down for the language selection. It actually has a private variable called “m_Directory” which is treated as a partial path after “/Assets/”. However it’s initialized with an empty string and there’s no code that actually changes that variable.
So they might had in mind to provide a way to specify a path where the new script should be created, but they never implemented any kind of GUI / interface to set that directory.
Reflection saves the day, However it’s more ugly than i thought. The problem is that the AddComponentWindow is recreated each time it’s opened. Also it’s internal tree of “Elements” which contains the “NewScriptElement” instance is also recreated each time the window is opened. Since it’s a drop-down window you can’t click anywhere outside the window since that would close the window. So the only solution is to actually “poll” if the window is currently open and if so we “inject” our desired path.
I’ve just written an editor script which does that. Of course the script has to be placed in an editor folder. To use it, just click “Tools/Default script path settings” in the main menu to bring up the settings menu window. The window provides you three things:
- A toggle button where you can enable / disable the background scanning for the add component window
- A text field where you can enter the relative path where you want your new scripts to be saved to. That path uses forward slashes. You shouldn’t add leading or trailing slashes. So if you want to use “/Assets/Scripts/” as folder just type “Scripts” (without the quotes) into the text field.
- An indicator which shows you whether the background check has successfully set the directory or not.
The window must be open to be able to work. However it can be docked in any view and even be hidden somewhere as long as it’s open. The window also saves the state of the toggle button as well as the path you enter in the EditorPrefs so it’s automatically restored when Unity recompiles or when you reopen Unity.
edit The new version of this script doesn’t require to have the window open. The window is now just a “settings” menu where you can specify the path and enable / disable the background check.
//NewScriptDefaultPathWindow.cs
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Linq;
using System.Reflection;
[InitializeOnLoad]
public class NewScriptDefaultPathWindow : EditorWindow
{
#region Reflection magic
static System.Type AddComponentWindowType = null;
static System.Type NewScriptElement = null;
static FieldInfo s_AddComponentWindow = null;
static FieldInfo m_Tree = null;
static FieldInfo m_Directory = null;
static EditorWindow m_CurrentWindow = null;
static object m_CurrentElement = null;
static NewScriptDefaultPathWindow()
{
var types = typeof(EditorWindow).Assembly.GetTypes();
AddComponentWindowType = types.Where(t => t.Name == "AddComponentWindow").FirstOrDefault();
s_AddComponentWindow = AddComponentWindowType.GetField("s_AddComponentWindow",BindingFlags.NonPublic | BindingFlags.Static);
m_Tree = AddComponentWindowType.GetField("m_Tree",BindingFlags.NonPublic | BindingFlags.Instance);
var nestedTypes = AddComponentWindowType.GetNestedTypes(BindingFlags.NonPublic);
NewScriptElement = nestedTypes.Where(t => t.Name == "NewScriptElement").FirstOrDefault();
m_Directory = NewScriptElement.GetField("m_Directory", BindingFlags.NonPublic | BindingFlags.Instance);
EditorApplication.update += BackgroundCheck;
}
static EditorWindow GetAddComponentWindow()
{
var inst = (EditorWindow)s_AddComponentWindow.GetValue(null);
return inst;
}
static object GetNewScriptElement()
{
var window = GetAddComponentWindow();
if (window == null)
{
m_CurrentWindow = null;
m_CurrentElement = null;
return null;
}
if (window == m_CurrentWindow)
{
return m_CurrentElement;
}
m_CurrentWindow = window;
System.Array a = (System.Array)m_Tree.GetValue(window);
var list = a.OfType<object>().ToArray();
for(int i = 0; i < list.Length; i++)
{
if (list*.GetType() == NewScriptElement)*
{
m_CurrentElement = list*;
return m_CurrentElement;
_}_
_}_
return null;
_}_
static string Directory
_{_
get
_{_
var element = GetNewScriptElement();
if (element == null)
return “”;
return (string)m_Directory.GetValue(element);
_}_
set
_{_
var element = GetNewScriptElement();
if (element == null)
return;
m_Directory.SetValue(element, value);
_}_
_}_
_#endregion Reflection magic*_
[MenuItem(“Tools/Default script path settings”)]
* static void Init ()*
{
var win = CreateInstance();
win.ShowUtility();
* }*
static string dir = “”;
static string currentDir = “”;
static bool enableBackgroundCheck = false;
static int counter = 0;
static NewScriptDefaultPathWindow instance;
static bool initialized = false;
static void LoadSettings()
{
dir = EditorPrefs.GetString(“NewScriptDefaultPath”, “”);
enableBackgroundCheck = EditorPrefs.GetBool(“NewScriptDefaultPath_Enabled”, false);
}
static void BackgroundCheck()
{
if (!initialized)
{
initialized = true;
LoadSettings();
}
if (enableBackgroundCheck)
{
// check only once a second
if (++counter > 100)
{
counter = 0;
Directory = dir;
if (instance != null)
{
string tmp = Directory;
if (tmp != currentDir)
{
currentDir = tmp;
instance.Repaint();
}
}
}
}
}
void OnEnable()
{
LoadSettings();
instance = this;
titleContent = new GUIContent(“Edit default script path”);
}
void OnDisable()
{
instance = null;
}
Color GetColor(bool aActive)
{
if (aActive)
return Color.green;
return Color.red;
}
* void OnGUI ()*
{
Color oldColor = GUI.color;
GUI.changed = false;
enableBackgroundCheck = GUILayout.Toggle(enableBackgroundCheck, “enable background check”, “button”);
GUILayout.BeginHorizontal();
GUILayout.Label(“Default script save path:”, GUILayout.Width(150));
dir = GUILayout.TextField(dir);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("Background check is ");
GUI.color = GetColor(enableBackgroundCheck);
GUILayout.Label((enableBackgroundCheck ? “enabled” : “disabled”), “box”);
GUI.color = oldColor;
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
if (GUI.changed)
{
EditorPrefs.SetString(“NewScriptDefaultPath”, dir);
EditorPrefs.SetBool(“NewScriptDefaultPath_Enabled”, enableBackgroundCheck);
}
if (enableBackgroundCheck && dir != “”)
{
GUI.color = GetColor(currentDir == dir);
if (currentDir == dir)
GUILayout.Label(“Directory successfully set”, “box”);
else
GUILayout.Label(" - Window currently not open - ", “box”);
GUI.color = oldColor;
}
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if(GUILayout.Button(“Close”))
{
Close();
}
GUILayout.Space(15);
GUILayout.EndHorizontal();
GUILayout.Space(15);
* }*
}
Note: It’s just a quick and dirty solution. We can be happy that they implemented the m_Directory field and actually use it internally. Without that it would be near to impossible to change anything since the path would be hardcoded. The only way would be to decompile the UnityEditor.dll, edit the desired parts and recompile it completely.
Also Note: This class uses reflection to get access to several internal classes and fields. Internal things can change at any time in the future whenever you update Unity. So it might no longer work in a distant future.