Skip to content

Creating Custom Types

Creating custom types is a great way to extend the functionality of the package. This will focus on creating a base “runtime scirptable object” that all the existing types are based on. If you want to create a new scriptable value, event, etc, see their respective pages for more information.

Your new scriptable object should inherit from RuntimeScriptableObject. This is the base class that handles play mode detection and property tracking.

Creating a Runtime Scriptable Object
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
// Add your properties and methods here.
}

There are two methods you can override to hook into the initialization of the scriptable object: OnPreStart and OnStart. OnPreStart is called before OnStart.

When a scriptable object is loaded or instantiated at runtime, OnPreStart and OnStart will be called immediately after the object is loaded. This means it’s safe to subscribe to any events directly after loading the object.

OnPreStart will be called on all loaded scriptable objects before OnStart. Likewise, OnStart will be called on all loaded scriptable objects after OnPreStart.

OnPreStart is used for preparing the object itself, like cleaning up old data or resetting values. You should not subscribe to any other scriptable objects in this method! This is because the other scriptable objects may not have been initialized yet.

OnStart can be used for subscribing to other scriptable objects or initializing data that depends on other objects. Other scriptable objects should have been initialized by now.

OnStart Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
public int currentValue;
public int defaultValue;
protected override void OnPreStart()
{
// This will be called before OnStart.
currentValue = defaultValue;
}
protected override void OnStart()
{
// This will be called when the game starts, after OnPreStart.
}
}

There are two methods you can override to hook into when the scriptable object is disabled: OnPreDisabled and OnDisabled. OnPreDisabled is called before OnDisabled.

When exiting play mode, OnPreDisabled and OnDisabled will be called immediately before the editor switches back to edit mode. This means it’s safe to unsubscribe from any events directly after exiting play mode.

OnPreDisabled will be called on all loaded scriptable objects before OnDisabled when they are destroyed at the same time. Likewise, OnDisabled will be called on all loaded scriptable objects after OnPreDisabled.

OnPreDisabled is used for unsubscribing from other scriptable objects or cleaning up data that depends on other objects. Other scriptable objects should still be initialized at this point.

OnDisabled can be used for cleaning up the object itself, like resetting values or releasing resources. You should not unsubscribe from any other scriptable objects in this method! This is because the other scriptable objects may have already been disabled.

OnDisabled Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
public event Action OnValueChanged;
protected override void OnPreDisabled()
{
// This will be called before OnDisabled.
}
protected override void OnDisabled()
{
// This will be called when the object is disabled.
OnValueChanged = null; // Clear all subscribers.
}
}

All runtime scriptable objects implement INotifyPropertyChanging and INotifyPropertyChanged. That means you can cast these objects to their respective interface and use the PropertyChanging and PropertyChanged events to get notified when properties change. This is useful for when you want to update the UI or other objects when a property changes.

Starting with Unity 2023.2 and the introduction of the runtime binding system for UI Toolkit, all runtime scriptable objects also implement IDataSourceViewHashProvider and INotifyBindablePropertyChanged to support the new binding system.

It’s highly recommended to notify the property change events when you change a property to ensure a consistent workflow.

Property Tracking Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
private int currentValue;
public int CurrentValue
{
get { return currentValue; }
set
{
// SetField automatically sets the field and
// notifies the property change events.
SetField(ref currentValue, value);
}
}
}

If you instead want to manually invoke the property change events, you can using the following methods:

Manually Invoking Property Change Events
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
private int currentValue;
public int CurrentValue
{
get { return currentValue; }
set
{
NotifyPropertyChanging(nameof(CurrentValue));
currentValue = value;
NotifyPropertyChanged(nameof(CurrentValue));
}
}
}

If you’re using Unity 2023.2 or later, you should also override the IDataSourceViewHashProvider.GetViewHashCode method.

This is used to generate a hash code for the object, which is used by the new binding system to determine if the object has changed. This is useful for performance reasons, as it allows the binding system to only update the UI when the object has actually changed.

View Hash Code Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
public int currentValue;
protected override long GetViewHashCode()
{
// This is not a guide on how to write hash codes, but
// rather a simple example of how to implement it.
unchecked
{
long hash = base.GetViewHashCode();
hash = (hash * 397) ^ currentValue.GetHashCode();
return hash;
}
}
}

Stack traces can be used to keep track of where your scriptable object is being used. This can be really useful for debugging purposes. You may have noticed by now in the editor that there’s a big list for stack traces. To actually register a stack trace, you just need to call the AddStackTrace method.

Stack Trace Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
public int currentValue;
public void DoSomething()
{
// This will register the stack trace.
AddStackTrace();
}
}

You can also skip frames in the stack trace. This is useful for when you want to skip the current method and only register the stack trace for the caller.

Skip Frames Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
public class NewScriptableObject : RuntimeScriptableObject
{
public int currentValue;
public void DoSomething()
{
// This will register the stack trace and
// skip the current method.
AddStackTrace(1);
}
}

Stack traces are automatically cleared when entering play mode. This is to ensure that you only see the stack traces for the current play session. But you can clear them whenever you want using the ResetStackTraces method.

If you don’t want to use stack traces at all for your type and hide the list in the editor, you can apply the HideStackTraces attribute to your class. This will only hide the list in the editor, but stack traces can still be registered and used in code.

Hide Stack Traces Example
using Hertzole.ScriptableValues;
using UnityEngine;
[CreateAssetMenu]
[HideStackTraces]
public class NewScriptableObject : RuntimeScriptableObject
{
public int currentValue;
public void DoSomething()
{
// This will register the stack trace, but
// the list will be hidden in the editor.
AddStackTrace();
}
}