Hi,
Figured I’d post this in case others might find it useful.
It always frustrated me that I had to set up the ScriptExecutionOrder by hand, especially when using my own personal library of useful scripts. For a while I used a script i’d found online but you had to add the classes to it by hand. So I finally got round to writing my own that leveraged c# attributes to automatically set the order for me.
The script has had basic testing, but as ever its a ‘use at your own risk’ deal and you should probably make a back up of the project before using it for the first time.
There are two scripts, ScriptExecutionAssignment is an editor script and as such MUST be placed within an Editor folder in your project. The other is ScriptExecutionAttribute which can be placed anywhere, but must NOT be in an editor folder.
Then simply add [ScriptExecutionAttribute( XXX )] to your scripts you want to control the script execution order for, replacing the XXX with an integer ( -9999 to 9999 ).
// ScriptExecutionAssignment
// By NoiseCrime 05.06.2016
//
//
// Editor class that will automatically set the scriptExecutionOrder based on using a ScriptExecutionAttribute.
// The class is called automatically everytime Unity recompiles the project - see [InitializeOnLoad]
//
// This script MUST be placed into an Editor Folder in your Unity Project.
// Requires Script names to match class name.
// Likely only works with a single class defined per file.
// Unfortunately the script will also execute everytime you run the project in the editor ( play )
//
// ENABLE_ASSIGNMENT: If NOT defined this will prevent execution of the script.
// ENABLE_LOGGING: If defined will log ( sorted by order ) to the console all found classes using the ScriptExecutionAttribute and their script order.
#define ENABLE_ASSIGNMENT
#define ENABLE_LOGGING
using UnityEngine;
using UnityEditor;
using System;
using System.Linq;
using System.Collections.Generic;
[InitializeOnLoad]
public class ScriptExecutionAssignment : Editor
{
static ScriptExecutionAssignment()
{
#if ENABLE_ASSIGNMENT
if( EditorApplication.isCompiling || EditorApplication.isPaused || EditorApplication.isPlaying) return;
#if ENABLE_LOGGING
Dictionary<string, int> executionOrderLogging = new Dictionary<string, int>();
#endif
EditorApplication.LockReloadAssemblies();
// Loop through monoscripts
foreach (MonoScript monoScript in MonoImporter.GetAllRuntimeMonoScripts())
{
Type monoClass = monoScript.GetClass();
if( monoClass != null)
{
Attribute att = Attribute.GetCustomAttribute(monoClass, typeof(ScriptExecutionAttribute));
if( att != null)
{
int executionIndex = ((ScriptExecutionAttribute)att).executionIndex;
#if ENABLE_LOGGING
executionOrderLogging.Add( monoScript.name, executionIndex);
#endif
// It is important not to set the execution order if the value is already correct!
if (MonoImporter.GetExecutionOrder(monoScript) != executionIndex)
{
MonoImporter.SetExecutionOrder(monoScript, executionIndex);
#if ENABLE_LOGGING
Debug.LogWarning("The script " + monoScript.name + " was not set to correct Script Execution Order - it has been fixed and set to " + executionIndex);
#endif
}
}
}
}
EditorApplication.UnlockReloadAssemblies();
#if ENABLE_LOGGING
// Simple logging to console with the dictionary of found classes sorted by execution order.
if(executionOrderLogging.Count > 0)
{
string logging = "ScriptExecutionAttribute: Found Types\n";
foreach (KeyValuePair<string,int> item in executionOrderLogging.OrderBy(key => key.Value))
logging += "ScriptExecutionAttribute: " + item.Value + " Type: " + item.Key + "\n";
Debug.Log(logging);
}
#endif
#else
Debug.LogWarning("ScriptExecutionAssignment is disabled - check script define.");
#endif
}
}
// ScriptExecutionAttribute
// By NoiseCrime 05.06.2016
//
// Support Attribute class that works with the editor script 'ScriptExecutionAssignment' to automatically populate the ScriptExecutionOrder.
// Simply add [ScriptExecutionAttribute( value )] prior to your class declaration, where value indicates the script order you want ( -9999 to +9999 )
// This script must NOT be placed in an Editor Folder.
using System;
[AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ScriptExecutionAttribute : Attribute
{
public readonly int executionIndex;
public ScriptExecutionAttribute( int index )
{
this.executionIndex = index;
}
}
an example
using UnityEngine;
[ScriptExecutionAttribute( -9702 )]
public class ApplicationStartUp : MonoBehaviour
{
void Awake()
{
.....
}
}
5 Likes
Hey,
So since people are still using this code I figured I’d post the latest version.
This updated version has
- Removed the requirement for setting a define in the code to enable/disable the script. Instead that behavior is now achieved through the custom Unity menu toggle option. This means you can easily and quickly toggle the script on or off.
- Other miscellaneous improvements and fixes.
If the ‘ScriptExecutionAssignment Automatic’ menu option is enabled then on every recompile the script will run and automatically sort the execution assignment of scripts with the attribute. This state is saved to the per project editor preferences.
Alternatively you can leave ‘ScriptExecutionAssignment Automatic’ disabled and instead select the ‘ScriptExecutionAssignment Refresh’ to immediately run and automatically sort the execution assignment of scripts with the attribute
The editor script - MUST BE PLACED WITHIN AN EDITOR FOLDER!
// Author: NoiseCrime 05.06.2017
// Location: In Editor Folder
#define ENABLE_LOGGING
using UnityEngine;
using UnityEditor;
using NoiseCrimeStudios.Core;
using System;
using System.Linq;
using System.Collections.Generic;
namespace NoiseCrimeStudios.Editor.Settings
{
/// <summary folder="Editor">
/// Editor class that will automatically set the scriptExecutionOrder based on using a ScriptExecutionAttribute.
/// The class is called automatically everytime Unity recompiles the project - see [InitializeOnLoad]
/// </summary>
/// <remarks>
/// This script MUST be placed into an Editor Folder in your Unity Project.
/// Requires Script names to match class name.
/// Likely only works with a single class defined per file.
///
/// ENABLE_LOGGING: Log to console all classes found using the ScriptExecutionAttributeand their script order.
/// </remarks>
[InitializeOnLoad]
static class ScriptExecutionAssignment
{
public static bool m_EnabledState = false;
// Define where settings appear in the Unity Menus
const string m_MenuItemAutomatic = "Window/NoiseCrimeStudios/EditorSettings/ScriptExecutionAssignment Automatic";
const string m_MenuItemImmediate = "Window/NoiseCrimeStudios/EditorSettings/ScriptExecutionAssignment Refresh";
// Cache Enabled state to Unity EditorPrefs
const string m_EnabledPrefsKey = "com.noiseCrimeStudios.ScriptExecutionAssignment.Enabled";
static ScriptExecutionAssignment()
{
// Cache EnabledState to avoid constant reads of prefs file
SetEnabledState( EditorPrefs.GetBool( m_EnabledPrefsKey, false ) );
// Only allow ScriptExecutionAssignment if enabled and not conflicting with Unity
if ( !m_EnabledState ) return;
Debug.LogWarningFormat( "NoiseCrimeStudios EditorTools: ScriptExecutionAssignment Constructor: Active ( IsEnabled: {0} )", m_EnabledState );
RefreshScriptExecutionAssignment();
}
static void RefreshScriptExecutionAssignment()
{
// Only allow ScriptExecutionAssignment if not conflicting with Unity
if ( EditorApplication.isCompiling || EditorApplication.isPaused || EditorApplication.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode ) return;
#if ENABLE_LOGGING
int index = 0;
Dictionary<string, int> executionOrderLogging = new Dictionary<string, int>();
#endif
try
{
// LockAssemlies
EditorApplication.LockReloadAssemblies();
// Loop through monoscripts
foreach ( MonoScript monoScript in MonoImporter.GetAllRuntimeMonoScripts() )
{
Type monoClass = monoScript.GetClass();
if ( monoClass != null )
{
// Attribute att = Attribute.GetCustomAttribute(monoClass, typeof(ScriptExecutionAttribute));
ScriptExecutionAttribute attribute = Attribute.GetCustomAttribute(monoClass, typeof(ScriptExecutionAttribute)) as ScriptExecutionAttribute;
if ( attribute != null )
{
int executionIndex = attribute.executionIndex;
string monoScriptName = monoClass.FullName;
#if ENABLE_LOGGING
if ( executionOrderLogging.ContainsKey( monoScriptName ) )
monoScriptName = string.Format( "{0} [{1}]", monoClass.FullName, index++ );
executionOrderLogging.Add( monoScriptName, executionIndex );
#endif
// It is important not to set the execution order if the value is already correct!
if ( MonoImporter.GetExecutionOrder( monoScript ) != executionIndex )
{
MonoImporter.SetExecutionOrder( monoScript, executionIndex );
#if ENABLE_LOGGING
Debug.LogWarning( "The script " + monoScriptName + " was not set to correct Script Execution Order - it has been fixed and set to " + executionIndex );
#endif
}
}
}
}
}
catch
{
throw;
}
finally
{
// Unlock Assemblies
EditorApplication.UnlockReloadAssemblies();
}
#if ENABLE_LOGGING
// Simple logging to console with the dictionary of found classes sorted by execution order.
if ( executionOrderLogging.Count > 0 )
{
string logging = "ScriptExecutionAttribute: Found Types\n";
foreach ( KeyValuePair<string, int> item in executionOrderLogging.OrderBy( key => key.Value ) )
logging += "ScriptExecutionAttribute: " + item.Value + " Type: " + item.Key + "\n";
Debug.Log( logging );
}
#endif
}
#region Public Methods
public static void SetEnabledState( bool val )
{
EditorPrefs.SetBool( m_EnabledPrefsKey, val );
m_EnabledState = val;
}
#endregion
#region MenumItems
[MenuItem( m_MenuItemAutomatic, false )]
static void ToggleOption()
{
SetEnabledState(!m_EnabledState);
}
[MenuItem( m_MenuItemAutomatic, true )]
public static bool ValidateToggleOption()
{
Menu.SetChecked( m_MenuItemAutomatic, m_EnabledState );
return true;
}
[MenuItem( m_MenuItemImmediate, false )]
static void Refresh()
{
RefreshScriptExecutionAssignment();
}
[MenuItem( m_MenuItemImmediate, true )]
public static bool ValidateRefresh()
{
// Only allow ScriptExecutionAssignment if not conflicting with Unity
if ( EditorApplication.isCompiling || EditorApplication.isPaused || EditorApplication.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode ) return false;
return true;
}
#endregion
}
}
The Attribute Script - MUST BE PLACED ANYWHERE EXCEPT AN EDITOR FOLDER
// Author: NoiseCrime 05.06.2017
// Location: Not In Editor Folder
using System;
namespace NoiseCrimeStudios.Core
{
/// <summary>
/// Support Attribute class that works with the editor script 'ScriptExecutionAssignment' to automatically populate the ScriptExecutionOrder.
/// Simply add [ScriptExecutionAttribute( value )] prior to your class declaration, where value indicates the script order you want ( -9999 to +9999 )
/// This script must NOT be placed in an Editor Folder.
/// </summary>
[AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false )]
public sealed class ScriptExecutionAttribute : Attribute
{
public readonly int executionIndex;
public ScriptExecutionAttribute( int index )
{
this.executionIndex = index;
}
}
}