ScriptableObject changes not persisted

I have a MonoBehaviour that references a ScriptableObject, ActionBarSlot. Changing the action bar slot through the UI during runtime doesn’t persist the change through a Unity re-opening. However, changing the action bar slot by manually dragging a talent onto it works well, both during runtime as well as non-runtime.

ActionBarSlot is referenced from ActionBarSlotUI (MonoBehaviour).
Riposte is referenced from TalentTreeNode (MonoBehaviour).
The talent is passed along the monobehavious unchanged, not casted to anything anywhere, through a drag event. Everything seems to line up perfectly and the ActionBarSlot gets the new talent. It just doesn’t persist for some reason.

What am I missing?

public class Talent : ScriptableObject
{
    public string Description;
    public int Level;
    public int MaxLevel;
    public Sprite Icon;
    public List<Talent> Dependencies = new List<Talent>();
}

[CreateAssetMenu(menuName = "Game/Talents/Riposte")]
public class Riposte : Talent, IAction
{
    public int DamagePerLevel;
    public float Cooldown { get; set; }

    public Sprite GetIcon
    {
        get { return Icon; }
    }

    public void Perform(IActor caster)
    {
        if (caster == null)
        {
            throw new ArgumentNullException("caster");
        }

        Debug.Log("In Riposte, riposting...");
    }
}

public class ActionBarSlot : ScriptableObject
{
    private Item item;
    public Talent talent;
}
1 Like

Bump.

Changes during play mode are deliberately not persistent.

2 Likes

Changes during playmode to ScriptableObject assets (files in your Project view) are persistent. For example:

[CreateAssetMenu]
public MySO : ScriptableObject {
    public int myNumber;
}

Let’s say you create an asset of this ScriptableObject type (MySO).

During playmode, if you inspect that asset in the Project view and change myNumber, it will remain changed when you stop playing.

However, if you’ve instantiated (cloned) a copy of the asset, changes to the clone will not persist, and will not be copied back to the original asset. For example:

var clone = Instantiate(mySO);
clone.myNumber = 42; // Value be lost when stopping playmode.

I’m guessing that you’ve written a custom editor for your ScriptableObject asset, and that it’s not marking the asset dirty. In this case, Unity doesn’t know to save the changes to disk. When you close Unity, those changes are lost. Ideally, your custom editor will use serializedObject.Update(), EditorGUI[Layout].PropertyField, and serializedObject.ApplyModifiedProperties(). These will properly mark the asset dirty. Or don’t use a custom editor at all, and use [Header], [Tooltip], etc., attributes for your serialized fields to get the look you want if possible.

2 Likes

The reference that’s not persisting is the reference in the MonoBehaviour, no?

As @LaneFox said, changes to object references on MonoBehaviours does not persist during play, by design. So if you drag-and-drop in a new ActionBarSlot to your MonoBehaviour during play, it’ll revert when you leave play.

This isn’t different from say changing the speed of an NPC at play - it won’t stay changed when you leave. You’re switching an object reference rather than a float, but the result is still the same.

Changing the ActionBarSlot itself will persist, because it’s an asset. Changes to a prefab in the project overview will persist, as it’s also an asset.

Bit too tired (or stupid) to be sure what you mean.

Yeah, which is why I have begun to fall in love with Assets, for better or worse. In this case, I am not dropping in a new ActionBarSlot, merely changing a reference field in one of them, pointing to another Asset.

This is assuming the speed is not an Asset , right?

That is just it though, I believe I AM making a change to the specific asset, not changing it.

The current structure of it all is (under development while learning):
Talent : ScriptableObject (works as base class for various talents, maybe that is the issue?)
ActionBarSlot : ScriptableObject (has a Talent field which is supposed to be changed during runtime AND persisted)
ActionBarSlotUI : MonoBehaviour (has an ActionBarSlot field, reference itself never changes)
TalentTreeNode : MonoBehaviour (has a Talent field which is assigned during non-runtime)

I implement the standard bunch of Unity IXXXDragHandler’s to a TalentTreeNode. I then drop a node on an ActionBarSlotUI, at which point the ActionBarSlotUI gets the reference to the Talent and passes it along to the Talent field in the ActionBarSlot that it has a reference to. The only thing that change references during these operations is the Talent field in the ActionBarSlot, and given that it is an Asset I thought it would persist those changes. It is especially curious to me that, if I manually drag a talent, subtalents included, to the Talent field of an ActionBarSlot during runtime it WILL persist that change. It just does not work when I drag them using the ingame UI.

Right. I do not instantiate any asset. Pre runtime I drag the assets (talents) onto talent tree nodes (have a reference to Talent asset). During runtime I drag talent tree nodes to action bar slots, setting a talent field to reference the talent in the talent tree node. Since all I am doing is changing the reference in the action bar slot slot (which is an asset) to point to another asset (talent) I would like to think that the change should be persisted. Especially since, during runtime, dragging the very same talent from the project view and dropping it on the talent field for the action bar slot asset DO persist the change. Just does not work when dropping it during the ingame UI. And oh, I never (as far I know) instantiate any asset of any kind during runtime, just changing fields in them. Hope I am not proven wrong!

No custom editor created (yet).

Was under the impression that changes to Assets during gameplay DO persist?

Lane was talking about objects that get serialized when reloading assemblies (e.g., when switching playmode or recompiling) such as instances of assets, scene objects, etc. You’re correct that asset files themselves DO persist.

What do you mean by “ingame UI”? It sounds like it works when you use the Unity editor’s Inspector view. The Inspector properly marks assets dirty when you make changes. Perhaps your ingame UI isn’t marking the assets dirty.

By “ingame UI” I mean the UI used when playing the game (vs the Unity UI you use when developing the game).

Am I supposed to mark the assets as dirty through code? Thought was handled automagically whenever a change to the asset occurred? As far as I know, the only way to explicitely mark an asset as dirty is through Editor classes?

Oh.

You’re changing the instantiated version of the ScriptableObject through an in-game UI? I (and probably @TonyLi ) were assuming you had selected the Scriptable in the hierarchy and edited it in the inspector.

That won’t persist. That would allow you to change the state on disk of the ScriptableObject in a build. Can’t do that. As you’ve realized, making the scriptable save requires you to set it dirty, which is an editor-only feature (as it’s got to do with the editor).

If you want the user to be able to persist changes through an in-game UI, you’ll have to save that data to file in some way.

3 Likes

…such as JsonUtility. You can create a new instance of your ScriptableObject and use JsonUtility.FromJsonOverwrite. It will serialize most primitive data (ints, strings, arrays, etc.), but how it handles references will depend on how those references are implemented.

(And sorry about the confusion earlier. As Baste suggested, I thought you were talking about making changes to your ScriptableObject asset in the editor using some kind of ingame UI, for example to tweak gameplay values. This would work, but only in the editor.)

1 Like

Oh, ok. I see what you are saying. That is… unfortunate. Was hoping Assets would allow me save data in an easy way. Never much liked the way Unity deals with serialization, something pretty every game needs.

A huge thanks to you for taking the time to help me out.

I know what you’re talking about. But one of the nice things about Unity is its extensibility. There are other serialization options out there, too.

For those who google this. You can make SO persistent by calling .SetDirty() on them and then calling AssetDatabase.SaveAssets()

but remember, you can’t call AssetDatabase in build.

1 Like

It would have been way too easy to just give developers the possibility of saving persistant data in scriptable Objects. Way too easy. Instead Unity has to keep it real, and leave that option out.

‘But scriptable objects aren’t meant to be used that way’

Says who. Leave people the option to use tools in the way they see fit. Saving data persistantly in SO’s would have been INCREDIBLY convenient. Way too convenient, as I stated. No convenience allowed!!

You’re missing some “small” problems around this: your scriptable objects are in big compressed libraries when you are installing your game. Unity would have to develop some sort of saving system for you, but since they do not know your game, they really can’t do it on a satisfying way, so they (and we, who know what is really happening) say that you can develop your saving system, it’s really not that complicated. Gather your data what you want to save and write it in a file in a format you want.

You are probably right, and if I was content with a ‘dumb’ approach it would be easy to solve. But trying to create an elegant mantainable and scaleable solution is a whole different beast. I’m 100% certain that everything you mentioned would be a minor inconvenience if there was the will to allow for SO’s to store data persistantly. They could still be and remain compressed and yada yada for everyone that doesn’t need that functionality.

I can’t remember where I saw it now, but I’m pretty sure I remember seeing that the option to enable saving of scriptableobject asset runtime changes when desired was something that they were looking to introduce at some point.