Scriptable Values allow you to use scriptable objects for reactive values, events, and collections instead of normal C# events and singletons.
You also don't need to care about values being saved between sessions as they are cleared before you enter play mode, but this is also customizable!
- Supports fast enter-play mode
- Automatically resets values to default values
- Scriptable values and events for most standard C# and Unity types
- Scriptable objects for values, events, lists, dictionaries, and pools
- Value reference type for easily picking between a constant value and a scriptable object reference
- Automatically collect stack traces to see where your values are set from
- Value and event listeners for easily hooking up events in the editor for when value changes/events are invoked
- Supports addressables for scriptable values and events
Scriptable Values supports all Unity versions from Unity 2021.3 and onward. It may support older versions but they are currently untested.
If you have the OpenUPM CLI tool installed, you can add the package with this command:
openupm add se.hertzole.scriptable-values
Otherwise, follow these instructions:
- Open Edit/Project Settings/Package Manager
- Add a new Scoped Registry (or edit the existing OpenUPM entry)
Name:package.openupm.com
URL:https://package.openupm.com
Scope:se.hertzole.scriptable-values
- Click
Save
(orApply
) - Open Window/Package Manager
- Click
+
- Select
Add package by name...
orAdd package from git URL...
- Paste
se.hertzole.scriptable-values
into the name field - Click
Add
- Open up the Unity package manager
- Click on the plus icon in the top left and "Add package from git URL"
- Paste in
https://github.com/Hertzole/scriptable-values.git#package
You can also usehttps://github.com/Hertzole/scriptable-values.git#dev-package
if you want the latest (but unstable!) changes.
Also check out my other package Unity Toolbox that can generate subscribe methods for scriptable values and events!
Scriptable Values allow you to have a single value across multiple objects and listen to its changing events.
There are scriptable values for all primitive C# values and most standard Unity types that you can use out of the box, but it's not difficult to create your own.
public class PlayerHealth : MonoBehaviour
{
public ScriptableInt health;
public void TakeDamage(int damage)
{
health.Value -= damage;
}
}
public class HealthUI : MonoBehaviour
{
public ScriptableInt health;
public Text healthText;
private void OnEnable()
{
health.OnValueChanged += OnHealthChanged;
}
private void OnDisable()
{
health.OnValueChanged -= OnHealthChanged;
}
private void OnHealthChanged(int oldValue, int newValue)
{
healthText.text = "Health: " + newValue.ToString();
}
}
Creating your own scriptable value:
[CreateAssetMenu(fileName = "New Scriptable Vector3", menuName = "My Values/Scriptable Vector3")]
public class ScriptableVector3 : ScriptableValue<Vector3>
{
// Nothing more, it's as simple as that!
}
Scriptable Events aim to replace normal C# events and the requirement to know the object they come from. With scriptable events, an event can come from anywhere but you are still able to know where if needed.
There are scriptable events for all primitive C# values and most standard Unity types that you can use out of the box, but it's not difficult to create your own.
public class PlayerHealth : MonoBehaviour
{
public ScriptableIntEvent onHealthChanged;
private int currentHealth = 100;
public void TakeDamage(int damage)
{
currentHealth -= damage;
onHealthChanged.Invoke(this, currentHealth);
}
}
public class HealthUI : MonoBehaviour
{
public ScriptableIntEvent onHealthChanged;
public Text healthText;
private void OnEnable()
{
onHealthChanged.OnInvoked += OnHealthChanged;
}
private void OnDisable()
{
onHealthChanged.OnInvoked -= OnHealthChanged;
}
private void OnHealthChanged(object sender, int args)
{
healthText.text = "Health: " + args.ToString();
}
}
Creating your own scriptable event:
[CreateAssetMenu(fileName = "New Scriptable Vector3 Event", menuName = "My Events/Scriptable Vector3")]
public class ScriptableVector3Event : ScriptableEvent<Vector3>
{
// Nothing more, it's as simple as that!
}
There are two scriptable collections that you can inherit from, list and dictionary, to create collections that you can use across your objects.
There's also a premade ScriptableGameObjectList
that you can use to store game objects.
Basic list usage:
[CreateAssetMenu(fileName = "New Scriptable String List", menuName = "My Lists/Scriptable String List")]
public class ScriptableStringList : ScriptableList<string> {}
public class MessageHandler : MonoBehaviour
{
public ScriptableStringList messageList;
public void AddMessage(string message)
{
messageList.Add(message);
}
}
public class MessageUI : MonoBehaviour
{
public ScriptableStringList messageList;
public Text textPrefab;
private void OnEnable()
{
messageList.OnAdded += OnMessageAdded;
}
private void OnDisable()
{
messageList.OnAdded -= OnMessageAdded;
}
private void OnMessageAdded(string newMessage)
{
Text text = Instantiate(textPrefab);
text.text = newMessage;
}
}
Basic dictionary usage:
[CreateAssetMenu(fileName = "New Scriptable Player Dictionary", menuName = "My Dictionaries/Scriptable Player Dictionary")]
public class ScriptablePlayerDictionary : ScriptableDictionary<int, GameObject> {}
public class PlayerID : MonoBehaviour
{
public int myId;
public ScriptablePlayerDictionary playersDictionary;
private void Awake()
{
playersDictionary.Add(myId, gameObject);
}
}
public class PlayersUI : MonoBehaviour
{
public ScriptablePlayerDictionary playersDictionary;
public Text textPrefab;
private void OnEnable()
{
playersDictionary.OnAdded += OnPlayerAdded;
}
private void OnDisable()
{
playersDictionary.OnAdded -= OnPlayerAdded;
}
private void OnPlayerAdded(int key, GameObject value)
{
Text text = Instantiate(textPrefab);
text.text = $"Player {key}: {value}";
}
}
Scriptable Pool is a type of scriptable object that you can use for pooling objects. It can be a normal C# class, a component, game object, or even scriptable objects.
There's a premade ScriptableGameObjectPool
that you can use to pool game objects.
public class EnemySpawner : MonoBehaviour
{
public ScriptableGameObjectPool enemyPool;
public void SpawnEnemy()
{
GameObject enemy = enemyPool.Get();
enemy.transform.position = Vector3.zero;
}
}
public class EnemyHealth : MonoBehaviour
{
public ScriptableGameObjectPool enemyPool;
public void Kill()
{
enemyPool.Return(this.gameObject);
}
}
Creating your own class pool:
[CreateAssetMenu(fileName = "New Scriptable String Builder Pool", menuName = "My Pools/String Builder Pool")]
public class ScriptableStringBuilderPool : ScriptablePool<StringBuilder>
{
// Called when you need to create a new object.
protected override StringBuilder CreateObject()
{
return new StringBuilder();
}
// Called when a object may need to be destroyed, like when clearing the pool.
protected override void DestroyObject(StringBuilder item)
{
// Can't destroy a string builder...
}
}
Creating your own component pool:
[CreateAssetMenu(fileName = "New Scriptable Enemy Pool", menuName = "My Pools/Enemy Pool")]
public class ScriptableEnemyPool : ScriptableComponentPool<Enemy>
{
// Nothing more, it's as simple as that!
}
Creating your own scriptable object pool:
[CreateAssetMenu(fileName = "New Scriptable Int Value Pool", menuName = "My Pools/Int Value Pool")]
public class ScriptableIntValuePool : ScriptableObjectPool<ScriptableInt>
{
// Nothing more, it's as simple as that!
}
Value reference is a special type that allows you to pick in the inspector if you want a constant value in the inspector or a scriptable value reference (and even a addressable reference if you have addressables installed!). Meanwhile, in your code, you only interact with a single Value
property and you don't need to care what type it is.
public class PauseManager : MonoBehaviour
{
public BoolReference isPaused; // You can use a built-in type.
public ValueReference<float> timescale; // You can also just use the generic type.
public void TogglePause()
{
isPaused.Value = !isPaused.Value;
timescale.Value = isPaused.Value ? 0f : 1f;
}
}