When working in Unity, a lot of times making good use of both Awake and Start can be enough of an “execution order” to handle the initialization of your systems. For example, you can use Awake to make sure that local references are hooked up, then use Start to handle any coordinated or delayed initialization that might need to occur. This lesson is really about what you do when those two entry points are not enough.
Unity Patterns
To get started, we will look at some solutions that are specific to MonoBehaviour scripts.
Script Execution Order
Let’s create a sample project to help illustrate some ideas. Start out by creating an empty project – you can use any template, but I went with a standard 2D Core option. Open the sample scene, and create an Empty GameObject, then Create and attach instances of four new classes that you should create:
using UnityEngine; public class DataSystem : MonoBehaviour { void Start() { Debug.Log(this.GetType().Name); } }
using UnityEngine; public class GameSystem : MonoBehaviour { void Start() { Debug.Log(this.GetType().Name); } }
using UnityEngine; public class InputSystem : MonoBehaviour { void Start() { Debug.Log(this.GetType().Name); } }
using UnityEngine; public class NetworkSystem : MonoBehaviour { void Start() { Debug.Log(this.GetType().Name); } }
These four scripts all do the same thing, they use the Start method as an opportunity to initialize themselves. In this simple demo, all that means is that they print their name to the console so we can see the order that they ran. However keep in mind that they REPRESENT the idea of a more complete and therefore complex project.
Play the scene and note the order you see. On one run I saw:
- NetworkSystem
- InputSystem
- GameSystem
- DataSystem
It is notable that this is the opposite of the order I added them to the GameObject. Maybe you thought they would play in the order you added them, but I don’t think Unity makes any promises regarding the order that MonoBehaviour methods of different script instances are invoked. If anyone knows otherwise, please comment below. My guess is that it probably has something to do with serialization – maybe the data is saved in a non-ordered way like a dictionary would be.
If I am correct, then it would make sense why Unity would add a special tool to handle the “Script Execution Order”. You can find this tool from the menu. Choose Edit > Project Settings > Script Execution Order.
You should see a variety of existing systems are already added and have their orders specified. The lower the number, the earlier the order. So for example, the UnityEngine.EventSystems.EventSystem which has an order of `-1000` would “Start” before UnityEngine.UI.ToggleGroup can “Start” since the later has an order of `10`.
At the moment our four systems have a “Default Time” order of `0`. That means they would be triggered after the `EventSystem` and before the `ToggleGroup`. While it is possible to manually assign an order to every one of your classes, I don’t recommend that approach. Rather, I would look for specific systems that really need to fire before other systems, or which need to fire after other systems and only specify new values for those special cases.
Let’s suppose that we want the DataSystem to execute before the GameSystem. That might be because we want to load game data before the game needs to work with it. Unfortunately in our above example, the GameSystem executed first – it would think the user didn’t have any save data! Use the “Script Execution Order” tool to fix it:
- Click the plus button to add a new entry
- Choose DataSystem from the list that pops up
- Assign a negative value to the order: perhaps -10
- Click “Apply” and then close the window
Run the scene again and note the new order that appears:
- DataSystem
- NetworkSystem
- InputSystem
- GameSystem
Perfect! I didn’t care about the order of systems except that I wanted the DataSystem to run first, and now it did. This would be true regardless of the order of the components on a GameObject, or even if the scripts were distributed among multiple GameObjects.
If you want to read more about this tool, see the documentation here.
Default Execution Order
While the above example works fine and is probably easy for beginners to understand and work with, it is not my favorite option. To me, a tool that is “out of sight” is also “out of mind”. In other words, I would probably forget to check whether someone (myself included) had specified an order in that part of the editor since it is so hidden away.
Unity has a second option that I like a little better through something called an “Attribute”. You have probably already been using “Attributes” even if you didn’t know what they were called. For example, there is an attribute so that a private field can be seen in an Inspector:
[SerializeField] private string age;
There is an attribute to make sure that the use of one component will guarantee that another component of a specified type will always exist on the same GameObject:
[RequireComponent(typeof(DataSystem))] public class SomeComponent : MonoBehaviour
In both examples, the “Attribute” bit is the part that appears within the square brackets. They can target methods, classes, etc, but essentially are special objects that can help provide special information or functionality.
Based on the lesson, you probably won’t be surprised to see that Unity provided an “Attribute” that can control the execution order, much like we did above, but handled via code instead of the editor. For the sake of demonstration, let’s suppose I really wanted the “NetworkSystem” to be triggered after all of my other systems. No problem, just add an attribute to its script like so:
[DefaultExecutionOrder(10)] public class NetworkSystem : MonoBehaviour
The “DefaultExecutionOrder” attribute is applied to the class, and accepts a parameter representing the execution order. Just like before, the order is sorted from lowest to highest, so a positive value will cause this script to trigger after our other systems that still exist in the “Default Time”.
Save the script, then build and run once again. This time I saw output that looks like this:
- DataSystem
- InputSystem
- GameSystem
- NetworkSystem
Excellent! The DataSystem still has the early priority thanks to the value specified in the Editor, and now our NetworkSystem has a late priority thanks to the attribute we specified in code.
You can read more about this, and other attributes, from the Scripting API, here.
Custom Patterns
While the above solutions should work for Unity-based workflows, there may be cases where you want to work with standard classes, not MonoBehaviour subclasses. If you are like me, you might want to know how Attributes work so you can make your own! I like the “DefaultExecutionOrder” attribute that Unity provided, but I will want an equivalent that can work for my own scripts. The ability to hard-code an order is certainly flexible, but over time it might become a bit tedious to manage. Rather than having to check the actual value of a scripts order, I think it would also be nice to have some attributes that merely specify an order IN RELATION to another specified type. So for example, I could say that I want a certain script to always be invoked either BEFORE or AFTER another script, without having to know the order of either one.
Priority Attribute
I know that I will want to support attributes of different types: one for a fixed order, and ones for order before or after other types. Regardless, I want to treat them all the same, therefore I will start out by creating an abstract base class that each type can inherit from. Create a new script named “PriorityAttribute” and add the following:
using System; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public abstract class PriorityAttribute : Attribute { public abstract int GetPriority(); }
What we have done here is to create a new type of “Attribute” that can be applied to Class or Struct types. The attribute itself is abstract so you won’t be able to create instances of this yet. It also has an abstract method “GetPriority” that must be implemented by any concrete subclass. Here I am using “Priority” as a synonym for Unity’s “Execution Order”, but wanted a separate name to help reduce any confusion.
Priority Fixed Attribute
Now, let’s create our first real attribute. This one will allow you to specify a priority directly, just like we could do on the unity side. Add the following:
public class PriorityFixedAttribute : PriorityAttribute { private int _value; public PriorityFixedAttribute(int value) { _value = value; } public override int GetPriority() { return _value; } }
The name of this class is “PriorityFixedAttribute” and that name controls how you apply the attribute when you want to use it. The basic structure is to use everything up to the word “Attribute” so it might look something like this:
[PriorityFixed(1)] public class SomeClass
Priority Values
Rather than just use “magic numbers” with my “fixed” priority attributes, I would rather use some named constants. Add the following:
public abstract class Priority { public const int VeryHigh = -100; public const int High = -50; public const int Normal = 0; public const int Low = 50; public const int VeryLow = 100; }
I created an abstract class named Priority, because I don’t want instances to be made. The class is really just serving as a sort of namespace for the constants that I have defined. This allows the following change:
// Without constants [PriorityFixed(-50)] // With constants [PriorityFixed(Priority.High)]
Priority Type Extensions
Now that we have a real working attribute, you might be wondering how you could actually use it. To make it simple, I will create an extension for the System.Type that can return the “priority” of that type. Add the following:
public static class PriorityTypeExtensions { public static int GetPriority(this System.Type type) { foreach (var attribute in type.GetCustomAttributes(true)) { if (attribute is PriorityAttribute priorityAttribute) { return priorityAttribute.GetPriority(); } } return Priority.Normal; } }
This method looks at all of the custom attributes that have been associated with a given Type. For any of those that are a subclass of PriorityAttribute, we can call its implemented GetPriority. In the event that the Type does not have attributes, or at least not one we are looking for, we can simply return a default priority value. Here I return Priority.Normal.
To use the extension, you simply get the “Type” of any instance, then call “GetPriority”, something like the following:
someReference.GetType().GetPriority();
Before And After Priorities
Now let’s add two additional priority attributes so that we can explicitly direct something to execute before or after another Type, but without needing to keep track of the priority value itself. Add the following:
public class PriorityBeforeAttribute : PriorityAttribute { private System.Type _type; public PriorityBeforeAttribute(System.Type type) { _type = type; } public override int GetPriority() { return _type.GetPriority() - 1; } } public class PriorityAfterAttribute : PriorityAttribute { private System.Type _type; public PriorityAfterAttribute(System.Type type) { _type = type; } public override int GetPriority() { return _type.GetPriority() + 1; } }
These two classes are very similar. They both accept a parameter of a System.Type and both return a dynamic priority based on the priority of the type that they reference. To go “Before” another type, we simply provide a priority that is one “less”. To go “After” another type, we simply provide a priority that is one “more”. Remember that this works because priority will be sorted from low to high.
Example uses of these new attributes could look something like this (where “SomeOtherType” would be the name of another class or struct that you wanted to base the new priority upon):
[PriorityBefore(typeof(SomeOtherType))] [PriorityAfter(typeof(SomeOtherType))]
Priority List
In order to better demonstrate the use of prioritized “somethings”, let’s imagine that we have a “collection” of systems. It could be like the systems mentioned at the beginning of this lesson, a data system, game system etc. This new collection could then be enumerated, based on priority, to run some sort of initialization process on those systems. Such a collection could look like this:
using System.Collections.Generic; using System.Collections; public class PriorityList<T> : IEnumerable<T>, IEnumerable { private List<T> _list = new(); private bool _isDirty; public void Add(T item) { _list.Add(item); _isDirty = true; } public void Remove(T item) { _list.Remove(item); } IEnumerator IEnumerable.GetEnumerator() { Sort(); return (_list as IEnumerable).GetEnumerator(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { Sort(); return (_list as IEnumerable<T>).GetEnumerator(); } void Sort() { if (!_isDirty) return; _list.Sort((x, y) => x.GetType().GetPriority().CompareTo(y.GetType().GetPriority())); _isDirty = false; } }
This PriorityList is an enumerable generic collection. Under the hood, it wraps a generic list. So anytime you “Add” or “Remove” or treat it as one of the conformed interfaces, it just passes the commands to the list. However, it also keeps track of a state. Basically, any time that you “Add” an item to the collection, it becomes “dirty” – that means it should be sorted before the next time you enumerate it. In practice, it may only ever need to be sorted one time, depending on how you construct the collection and work with it.
Demo
I’ll end this lesson with a quick demo that wraps everything together. I will create a PriorityList, fill it with a bunch of objects that will have their priority determined in various ways, and then enumerate the collection, showing that it will go by priority order. Create a new script named “PriorityTests” and add the following code.
using UnityEngine; public class PriorityTests : MonoBehaviour { private void Start() { var list = new PriorityList<object>(); list.Add(new UnmarkedPriorityExample()); list.Add(new HighPriorityExample()); list.Add(new LowPritorityExample()); list.Add(new BeforeUnmarkedPriorityExample()); list.Add(new BeforeHighPriorityExample()); list.Add(new AfterUnMarkedPriorityExample()); list.Add(new AfterLowPriorityExample()); list.Add(new AfterAfterLowPriorityExample()); foreach (var item in list) { var type = item.GetType(); Debug.Log($"{type.Name}: {type.GetPriority()}"); } } } public class UnmarkedPriorityExample { } [PriorityFixed(Priority.High)] public class HighPriorityExample { } [PriorityFixed(Priority.Low)] public class LowPritorityExample { } [PriorityBefore(typeof(UnmarkedPriorityExample))] public class BeforeUnmarkedPriorityExample { } [PriorityBefore(typeof(HighPriorityExample))] public class BeforeHighPriorityExample { } [PriorityAfter(typeof(UnmarkedPriorityExample))] public class AfterUnMarkedPriorityExample { } [PriorityAfter(typeof(LowPritorityExample))] public class AfterLowPriorityExample { } [PriorityAfter(typeof(AfterLowPriorityExample))] public class AfterAfterLowPriorityExample { }
Attach the new script to an empty game object in the scene, and then play the scene. Note that we did not “Add” the objects in any intentional order, but they will still be enumerated in the order that was explicitly commanded based on each object’s attribute. This pattern is even powerful enough to be recursive – in other words, a priority attribute can be determined based on a type whose priority attribute is determined by yet another type and so on.
Playing the scene you should see the following output, showing the order of the “systems” and their evaluated priority values:
BeforeHighPriorityExample: -51 HighPriorityExample: -50 BeforeUnmarkedPriorityExample: -1 UnmarkedPriorityExample: 0 AfterUnMarkedPriorityExample: 1 LowPritorityExample: 50 AfterLowPriorityExample: 51 AfterAfterLowPriorityExample: 52
Summary
This lesson was all about the order of execution of different scripts. We covered how to control that order specifically for MonoBehaviours via the Unity Editor, and also by the “DefaultExecutionOrder” Attribute. Then we took a deep dive into how to create and use our very own attributes so they could be used for any object or workflow. In the end we created a demo that showed how a collection of objects could be enumerated based on a custom priority that we had assigned by our own attributes.
If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!