This project provides a framework for implementing a data-oriented design in Unity. It uses ScriptableObjects as a robust alternative to the Singleton pattern for managing shared state and data, ensuring a clear separation between data and logic.
DataAssetSo: AScriptableObjectthat acts as a container for a list of data objects.DataObject: The abstract base class for all data entries. The system uses[SerializeReference]to store different data types in one collection.ReactiveValue<T>: ADataObjectfor a single value (e.g.,int,bool) that invokes anOnValueChangedevent when its data changes.ReactiveList<T>: ADataObjectfor a list of items, providing events for collection changes (OnItemAdded,OnItemRemoved, etc.).
✨ Powerful & Extensible Editor
- A complete custom editor is provided to facilitate the creation and management of data assets.
- The system automatically detects and integrates any new data class. Simply inherit from
ReactiveValue<T>orReactiveList<T>, and it will immediately appear as an option in the editor. - A set of basic reactive types (
Int,String,Bool,Float, etc.) and collections (IntList,StringList) are already included to get you started.
🔧 Robust Tooling & Validation
- Usage Scanning: Find every reference to a data key across your entire project, including scripts, prefabs, and scenes.
- Dependency Validation: Use the
[RequireDataKeys]attribute on aDataAssetSofield. The inspector will warn you at edit-time if the referenced asset is missing any of the specified keys. - Automatic Key Generation: To avoid error-prone "magic strings", the editor includes a tool to generate a static C# class containing all your data keys as
const string. This ensures type-safe access in your code.
Create an asset via the Assets > Create > ScriptableObjects > DataAssetSo menu.
Select the DataAssetSo file to open the custom inspector. Use the "Add New Data" section to add DataObject entries.
Already included reactive types are located in "Base" within the dropdown wich contains a "Primitive" and a "Collection" subfolder. Each custom reactive type will have its own dropdown category depending on the namespace. So for example, this custom type :
namespace Custom
{
[Serializable]
public class ReactiveVector3 : ReactiveValue<Vector3>
{
public ReactiveVector3()
{
}
public ReactiveVector3(string dataName, Vector3 initialValue) : base(dataName, initialValue)
{
}
}
}Will be located in a "Custom" subfolder.
Each DataObject within a DataAssetSo is accessible by its name, as it is bound to a dictionary for fast lookups when the asset is enabled.
Reference the DataAssetSo in a component and use GetData<T>() to access data.
using DataAsset.Base.Primitive;
using UnityEngine;
public sealed class PlayerManager : MonoBehaviour
{
public DataAssetSo playerData; // Reference to the DataAsset containing player data
private ReactiveFloat playerHealth; // ReactiveFloat to track player health
private void OnEnable()
{
// Grab the ReactiveFloat from the DataAsset
playerHealth = playerData.GetData<ReactiveFloat>("health");
// As the value of reactive data is wrapped in a class,
// we need to access the Value property to get the actual float value
if (Mathf.Abs(playerHealth.Value) < 0.0001f)
{
playerHealth.Value = 100f;
}
// Subscribe to the OnValueChanged event of player health
playerHealth.OnValueChanged += HandlePlayerHealthChanged;
// This event is triggered whenever any data object within the DataAsset changes
playerData.OnAnyDataChanged += HandlePlayerChanged;
}
private void OnDisable()
{
playerHealth.OnValueChanged -= HandlePlayerHealthChanged;
}
private void HandlePlayerHealthChanged(float value)
{
Debug.Log($"Player health changed to: {value}");
}
private void HandlePlayerChanged(DataObject data)
{
Debug.Log($"Data changed: {data.dataName}");
}
}To create a custom data type, simply create a new class that inherits from ReactiveValue<T> or ReactiveList<T>. The system will automatically detect and integrate it.
using System;
using DataAsset.Core;
using UnityEngine;
namespace Custom
{
[Serializable]
public class ReactiveVector3 : ReactiveValue<Vector3>
{
// This constructor is required for Unity's serialization system.
public ReactiveVector3()
{
}
public ReactiveVector3(string dataName, Vector3 initialValue) : base(dataName, initialValue)
{
}
}
}To ensure that a DataAssetSo is correctly configured for a specific consumer, you can use the [RequireDataKeys] attribute. It verifies at edit-time that the referenced asset contains all the specified keys, displaying a clear warning in the inspector if any are missing.
For example, if this [RequireDataKeys] is set with a PlayerScore and a PlayerHealth
using DataAsset.Core;
using DataAsset.Core.Attributes;
using UnityEngine;
public sealed class PlayerManager : MonoBehaviour
{
[RequireDataKeys("PlayerScore", "PlayerHealth")]
public DataAssetSo playerData;
private void OnEnable()
{
if (playerData == null)
{
Debug.LogError("Player data asset is not assigned.", this);
return;
}
playerData.OnAnyDataChanged += HandlePlayerDataChanged;
}
private void OnDisable()
{
if (playerData != null)
{
playerData.OnAnyDataChanged -= HandlePlayerDataChanged;
}
}
private void HandlePlayerDataChanged(DataObject data)
{
// Handle the player data change event here
}
}This will generate this warning :
Until this is implemented :
To avoid errors from string-based lookups ("magic strings"), the system includes a tool to generate a static C# class from your DataAssetSo. This class contains all your data keys as const string fields, providing type-safe access and enabling IDE auto-completion.
This is located under Tools/Data Asset/Generate All Keys.
using DataAsset.Base.Primitive;
using DataAsset.Core;
using UnityEngine;
public sealed class PlayerManager : MonoBehaviour
{
public DataAssetSo playerData;
private ReactiveFloat playerHealth;
private void OnEnable()
{
// Magic strings
playerHealth = playerData.GetData<ReactiveFloat>("PlayerHealth");
// Using generated keys
playerHealth = playerData.GetData<ReactiveFloat>(Player_Keys.PlayerHealth);
}
}The editor includes a powerful utility to find every usage of a data key across your entire project. It scans scripts, prefabs, and scenes to build a comprehensive list of references. You can click on any entry in the list to navigate directly to the corresponding location in your project. This is invaluable for debugging and safe refactoring.
For example :
Scan results are cached in the Editor. It is recommended to re-run the scan if you make significant changes to your scripts to ensure the usage data is up-to-date.
- Unity 2021.3 LTS or newer.
- EditorCoroutines 1.0.0.
This project is licensed under the MIT License. See the LICENSE file for details.






