I’m looking for a solution to have a function run in a ScriptableObject only when the editor enters play mode or when an application is started. In this way, it would be very similar to the MonoBehaviour.Start function.
I’ve tried using the OnEnable, OnValidate, and Awake functions. However, all of these run outside of play time and Application.isPlaying returns false within them when in play time.
Just so you know, these two totally different things:
are absolutely nothing at all like this thing:
So of those three things (enter play mode, application started, and MonoBehaviour.Start() ), I’m not really sure what you’re looking or here.
On the premise it is the .Start() answer, then just make a tiny Monobehaviour that runs your own MyStart() method on every ScriptableObject instance you want.
You could even hook the creation of this object up to a RuntimeInitializeOnLoad attribute and it can completely disappear from your sight and your concern.
Here is some timing diagram help:
Some super-simple Singleton examples to take and modify:
Simple Unity3D Singleton (no predefined data):
Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:
These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance.
Alternately you could start one up with a RuntimeInitializeOnLoad attribute.
Ah-ha! Finally after all these years we can point and laugh at Kurt for gasp… making a mistake! I’m part of a secret organization that has been waiting for this event since time immemorial. The tapestries foretold of this long ago but I personally never thought I’d live to see it myself.
lol Okay, just having some fun obviously. But I am a bit surprised that kurt didn’t know about this one. You absolutely can tell when playmode is entered and exited in the editor and it’s a very common pattern for exactly this sort of thing when dealing with ScriptableObject assets. Unity themselves uses it all over the place in most of their modules.
EditorApplication.playmodeStateChanged is the event you can subscribe to in order to determine what state you are currently in. A common pattern is to register a listener to it within a ScriptableObject’s OnEnable and unregister to in the OnDisable method. The reason for this is that when ScriptableObject assets are first created in the editor OnEnable is called and the object is essentially loaded into memory until recompilation and domain reloading happens or the editor app is shutdown. You’ll of course want to guard all of this in #if UNITY_EDITOR condiational compilation blocks so that it only applies to the editor environment. It might not also be a bad idea to add a bool flag within that block to check if you’ve already registered the listener or not as ScriptableObjects are a bit famously weird when it comes to the order and timing of OnEnable and OnDisable being called in the editor.
I think wanting, or designing, your scriptable objects to require to be initialised in some form is definitely not their strong suit.
It goes without saying that scriptable objects are best suited for bags of data or as pluggable behaviour (particularly when stateless).
If I need some kind of behaviour that needs to be initialised, I’ll design this in pure C# code, and make a scriptable object that acts as a factory for said data.
So I’m assuming this is sort of what @Sluggy1 is suggesting:
using UnityEngine;
public abstract class StartableScriptableObject : ScriptableObject
{
#if UNITY_EDITOR
protected virtual void OnEnable()
{
if (Application.isPlaying)
{
this.Start();
}
else
{
UnityEditor.EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
}
}
protected virtual void OnDisable()
{
UnityEditor.EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
}
private void EditorApplication_playModeStateChanged(UnityEditor.PlayModeStateChange e)
{
switch (e)
{
case UnityEditor.PlayModeStateChange.EnteredPlayMode:
this.Start();
break;
}
}
#else
protected virtual void OnEnable()
{
this.Start();
}
//this needs to be here incase someone override OnDisable and it needs to compile in both places
protected virtual void OnDisable() {}
#endif
protected abstract void Start();
}
Also in regards to what OP is asking for… the part that I’m confused on is why doesn’t just checking if ‘Applicaiton.isPlaying’ during OnEnable not working?
The reason I say this is because from what I’ve seen is that if I have an SOs that needed to be loaded on start of a scene in the editor. Unity will automatically unload those that are currently loaded, and reload them again.
Basically I just created a ScriptableObject and a simple MonoBehaviour that could reference that SO. Then in the OnEnable and OnDisable methods of the SO I added a debug log signaling each. And when I press play this is what prints out:
(you can ignore the ‘GameLoop’ line, that’s something that always runs in my project on play enter)
Note that the SO got disabled and re-enabled on entering playmode, and then this dummy ‘Start’ I created above ran.
Effectively meaning we likely don’t need to do all this rigamaroll and could just say:
void OnEnable()
{
if (!Applicaiton.isPlaying) return;
//do stuff that should only happen when the game is playing and the SO was "enabled"
}
…
That is of course… if I understood OPs question.
Cause I’m with Kurt… these things aren’t the same thing.
I’m just operating under the idea of if someone who doesn’t realize they’re not the same thing MIGHT think they would behave like. But even then, I’m still lost as to what OP wants since OnEnable already does it.
…
Of course for me… I avoid writing logic in my ScriptableObjects. I treat them as data containers, and leave the logic to the things that consume the ScriptableObject.
I do tend to litter them with statics and getter-helpers though, but stateless ones at least from the perspective of the contained data.
F’rinstance I do often throw in a static LoadAll and a static GetByName, or perhaps an instance GetTotalArea or GetEnemyCount and that sort of thing, just to centralize all shenanigans related to that SO.
But those are stateless from the perspective of business logic, even if (again for instance) the LoadAll actually intentionally caches them all into a collection optimized for their retrieval needs.
EDIT: I actually sometimes do put static state in when the thing is so important that there will never be a time both things will be active, such as a GravityMode or PlayerEngineControlMode, in which case I often provide a .Current getter and a .SelectByName() setter.
Yeah, helper methods is one thing, especially static ones.
Even a method that when called modifies its state, like a ‘ChangeUnderlyingState’ method that is called, or just a plane old property.
I just mean more like… I wouldn’t expect the SO to perform the logic of the game, but rather return a result that could be used by the game.
For example here is an ‘EventActivatorMask’ that I use a lot:
[CreateAssetMenu(fileName = "EventActivatorMask", menuName = "Spacepuppy/EventActivatorMask")]
public class EventActivatorMask : ScriptableObject, IEventActivatorMask
{
#region Fields
[SerializeField]
private bool _testRoot;
[SerializeField]
private LayerMask _layerMask = -1;
[SerializeField()]
[ReorderableArray]
[TagSelector()]
private string[] _tags;
[SerializeField]
[Tooltip("An optional eval string that will be operated as part of the mask, the GameObject of the activator will be passed along as $.")]
private string _evalStatement;
#endregion
#region Properties
public LayerMask LayerMask
{
get { return _layerMask; }
set { _layerMask = value; }
}
public string[] Tags
{
get { return _tags; }
set { _tags = value; }
}
public string EvalStatement
{
get { return _evalStatement; }
set { _evalStatement = value; }
}
#endregion
#region Methods
public bool Intersects(GameObject go)
{
if (go == null) return false;
if (_testRoot) go = go.FindRoot();
bool result = _layerMask.Intersects(go) && (_tags == null || _tags.Length == 0 || go.HasTag(_tags));
if (result && !string.IsNullOrEmpty(_evalStatement)) result = com.spacepuppy.Dynamic.Evaluator.EvalBool(_evalStatement, go);
return result;
}
public bool Intersects(Component comp)
{
if (comp == null) return false;
return Intersects(comp.gameObject);
}
#endregion
#region IEventActivatorMask Interface
public bool Intersects(Object obj)
{
return this.Intersects(GameObjectUtil.GetGameObjectFromSource(obj));
}
#endregion
}
With these I can have generic trigger boxes that can filter based on these masks during their logic.
The ScriptableObject in this case really only defines the terms by which things get filtered. It’s the script like ‘t_OnTriggerOccupied’ that will perform the real logic of the game only asking the mask if the target object is valid or not:
public sealed class t_OnTriggerOccupied : SPComponent, ICompoundTriggerEnterHandler, ICompoundTriggerExitHandler, IOccupiedTrigger
{
#region Fields
[SerializeField]
private EventActivatorMaskRef _mask = new EventActivatorMaskRef();
[SerializeField]
private bool _reduceOccupantsToEntityRoot = false;
[SerializeField]
[SPEvent.Config("occupying object (GameObject)")]
private SPEvent _onTriggerOccupied = new SPEvent("OnTriggerOccupied");
[SerializeField]
[SPEvent.Config("occupying object (GameObject)")]
private SPEvent _onTriggerLastExited = new SPEvent("OnTriggerLastExited");
[System.NonSerialized]
private HashSet<GameObject> _activeObjects = new HashSet<GameObject>();
[System.NonSerialized]
private bool _usesCompoundTrigger;
#endregion
#region CONSTRUCTOR
protected override void Start()
{
base.Start();
this.ResolveCompoundTrigger();
}
protected override void OnEnable()
{
base.OnEnable();
this.ResolveCompoundTrigger();
}
#endregion
#region Properties
public SPEvent OnTriggerOccupied
{
get { return _onTriggerOccupied; }
}
public SPEvent OnTriggerLastExited
{
get { return _onTriggerLastExited; }
}
public IEventActivatorMask Mask
{
get { return _mask.Value; }
set { _mask.Value = value; }
}
public bool ReduceOccupantsToEntityRoot
{
get => _reduceOccupantsToEntityRoot;
set => _reduceOccupantsToEntityRoot = value;
}
public bool IsOccupied
{
get
{
this.CleanActive();
return _activeObjects.Count > 0;
}
}
[ShowNonSerializedProperty("Uses Compound Trigger", ShowAtEditorTime = true, ShowOutsideRuntimeValuesFoldout = true)]
public bool UsesCompoundTrigger
{
get
{
#if UNITY_EDITOR
if (!Application.isPlaying) return this.HasComponent<ICompoundTrigger>() || !(this.HasComponent<Collider>() || this.HasComponent<Rigidbody>());
#endif
return _usesCompoundTrigger;
}
}
#endregion
#region Methods
public void ResolveCompoundTrigger()
{
//we assume CompoundTrigger if we have one OR if we don't have anything that can signal OnTriggerEnter attached to us.
_usesCompoundTrigger = this.HasComponent<ICompoundTrigger>() || !(this.HasComponent<Collider>() || this.HasComponent<Rigidbody>());
}
private void AddObject(GameObject obj)
{
if (_mask.Value != null && !_mask.Value.Intersects(obj)) return;
if (_activeObjects.Count == 0)
{
_activeObjects.Add(obj);
_onTriggerOccupied.ActivateTrigger(this, _reduceOccupantsToEntityRoot ? obj.FindRoot() : obj);
}
else
{
this.CleanActive();
_activeObjects.Add(obj);
}
}
private void RemoveObject(GameObject obj)
{
if ((_activeObjects.Remove(obj) || this.CleanActive() > 0)
&& _activeObjects.Count == 0)
{
_onTriggerLastExited.ActivateTrigger(this, _reduceOccupantsToEntityRoot ? obj.FindRoot() : obj);
}
}
//clean up any potentially lost colliders since Unity doesn't signal OnTriggerExit if a collider is destroyed/disabled.
private int CleanActive()
{
return _activeObjects.RemoveWhere(o => !ObjUtil.IsObjectAlive(o) || !o.activeInHierarchy);
}
#endregion
#region Messages
void OnTriggerEnter(Collider other)
{
if (!this.isActiveAndEnabled || _usesCompoundTrigger || other == null) return;
this.AddObject(other.gameObject);
}
void OnTriggerExit(Collider other)
{
if (!this.isActiveAndEnabled || _usesCompoundTrigger || other == null) return;
this.RemoveObject(other.gameObject);
}
void ICompoundTriggerEnterHandler.OnCompoundTriggerEnter(ICompoundTrigger trigger, Collider other)
{
if (!this.isActiveAndEnabled || other == null) return;
this.AddObject(other.gameObject);
}
void ICompoundTriggerExitHandler.OnCompoundTriggerExit(ICompoundTrigger trigger, Collider other)
{
if (!this.isActiveAndEnabled || other == null) return;
this.RemoveObject(other.gameObject);
}
#endregion
#region IObservableTrigger Interface
BaseSPEvent[] IObservableTrigger.GetEvents()
{
return new BaseSPEvent[] { _onTriggerOccupied, _onTriggerLastExited };
}
BaseSPEvent IOccupiedTrigger.EnterEvent
{
get { return _onTriggerOccupied; }
}
BaseSPEvent IOccupiedTrigger.ExitEvent
{
get { return _onTriggerLastExited; }
}
#endregion
}
The ScriptableObject mask can be told to have its state changed via the properties on it. But the SO isn’t really doing the trigger enter logic, or anything like that. It’s just a state that can be read and asked things about it.
Basically like any asset. A texture, a physics material, etc. They have methods on them that do things… but they do them on request, they don’t actively logic it themselves, they’re instead read or manipulated by the real logic.
But like earlier today someone was posting an AI thing they were doing and they ran into a problem because they were storing the state of their AI logic in the ScriptableObject. Which I would have never done. I might use an SO to describe the AI behaviour, and even configure it, but then would have a ‘state’ object owned by the ‘brain’ that would be passed to the ‘think’ function/coroutine to be acted upon. Thusly making the ScriptableObject more of a data container rather than a thing that does stuff. It instead defines how stuff is done.
Sure I suggested they clone the SO… but that was just because I didn’t want to take the effort to explain how to decouple that design. https://discussions.unity.com/t/924570
@lordofduct This might be ones of those cases of where ‘ScriptableObjects are weird’ applies. I’ve tried relying on just waiting for OnEnable and OnDisable in the past and it absolutely bit me hard. So hard in fact that I ended up having to close and re-open the editor because I had objects getting stuck in partially initialized states.
My solution was to use something like this:
private void OnEnable()
{
//due to how scriptable objects work in the editor we need this guard
//to ensure we are always inializing when in playmode and ONLY when in playmode
if(Application.isPlaying)
Initialize();
#if UNITY_EDITOR
//this will be called once when this asset is created for the first time or after a domain reload in the editor
EditorApplication.playModeStateChanged += HandlePlayModeChanged;
#endif
}
private void OnDisable()
{
if(Application.isPlaying)
Deinitialize();
#if UNITY_EDITOR
//this will be called once in the editor just before a domain reload
EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
#endif
}
#if UNITY_EDITOR
void HandlePlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
Initialize();
else if(state == PlayModeStateChange.ExitingPlayMode)
Deinitialize();
}
#endif
I’ll admit that it’s an absolute eyesore and I hate it but it was the solution I came up with after trying to simply use OnEnable and OnDisable and realizing they don’t work reliably in the editor.
EDIT: Just realized that your example was almost exactly the same as mine. I should really refrain from trying to comprehend posts after getting home from a 12 hours shift lol