As long as you keep straight in your mind the difference between authored values and overridden values and transiently-boosted values in your game, it doesn’t really matter how you implement it. For instance:
a base sword does 1d6 damage. This would be specified in a ScriptableObject
if you enchant it with a scroll, it does 1d6+1 damage (the base sword remains unchanged)
if you quaff a potion of strength, now the total is 1d6+2
The enchant would be stored perhaps as a buffing attached to the base sword.
The strength effect would be stored as a transient boost to the player.
Beyond that, all the normal Load/Save steps just work (see below).
Here’s more on ScriptableObject usage in RPGs:
Usage as a shared common data container:
Load/Save steps:
An excellent discussion of loading/saving in Unity3D by Xarbrough:
Loading/Saving ScriptableObjects by a proxy identifier such as name:
When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls new to make one, it cannot make the native engine portion of the object.
Instead you must first create the MonoBehaviour using AddComponent() on a GameObject instance, or use ScriptableObject.CreateInstance() to make your SO, then use the appropriate JSON “populate object” call to fill in its public fields.
If you want to use PlayerPrefs to save your game, it’s always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:
Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.