Figuring out a Data Storage Technique: Scriptable Objects and Monobehaviors

Hi all,

I’m a pretty new Unity user who’s been working on a little project that, for the sake of simplicity, has the player control a party of units like a turn-based rpg. My dilemma has been trying to understand how to juggle data I want saved along with data I want at runtime. Here’s a simple example with what I have currently:

  • Units can be recruited to the party, their base data is stored in a scriptable object (attack, health, and sprite, along with some other stuff). This data is only meant to be used when they are recruited, as I expect most of it to change.
  • I have a script called Fighter that is a Monobehavior that reads the scriptable object data and sets these as base stats. It can be attached to game objects and work with a companion sprite rendering script (packaged as a prefab).

That’s it, which works well if I want to only work with base units. But I want to be able to 1) keep my party + each unit’s data in a list that is a part of my Player object 2) Buff these units with gear and items, and be able to keep track of changes from scene to scene. 3) Inevitably, there will be a save system.

I can’t keep a list of the scriptable objects because I don’t want that data to change, or rather, I don’t want to change the stats of ALL units of that type. I can’t keep a list of game objects, because my game objects are made at runtime. My next guess was to use a regular C# object, but now there’s alot of scripts to juggle just for one unit and I’m starting to get dizzy. I guess the problem is simple, but I don’t have a great Unity or CS background so I’d like to understand abit more. Any help and explanations would be greatly appreciated, thanks!

EDIT: perhaps this should go in the “Getting Started” section, apologies if this section is incorrect.

I suggest saving the data you need to JSON.
You can maybe use enums or strings or something to define which troops use which scriptable object instead of storing the whole object.
For stats, the easiest is to simply save all stats. A bit harder would be to only store the stats override and if there is no stat saved them default to scriptable object. I suggest the first one however, since it doesn’t take a lot of data.

There also are many save managers on the asset store (and probably GitHub).

“Sake of simplicity” and “player control a party of units like a turn based RPG” really don’t belong in the same sentence.

I know you don’t want to hear that, but that doesn’t make it not true. What you are contemplating is in no possible even remotest way a “beginner” project. This project would probably be about your 10th game or so, after you have made flappy birds, angry birds, pong, space invaders, etc. to get the lay of the game development land.

These things (stats systems, party combat, inventories, shop systems, character customization, etc) are fairly tricky hairy beasts, definitely deep in advanced coding territory.

They contain elements of:

  • a database of items that you may possibly possess / equip
  • a database of the items that you actually possess / equip currently
  • perhaps another database of your “storage” area at home base?
  • persistence of this information to storage between game runs
  • presentation of the inventory to the user (may have to scale and grow, overlay parts, clothing, etc)
  • interaction with items in the inventory or on the character or in the home base storage area
  • interaction with the world to get items in and out
  • dependence on asset definition (images, etc.) for presentation

Just the design choices of an inventory system can have a lot of complicating confounding issues, such as:

  • can you have multiple items? Is there a limit?
  • if there is an item limit, what is it? Total count? Weight? Size? Something else?
  • are those items shown individually or do they stack?
  • are coins / gems stacked but other stuff isn’t stacked?
  • do items have detailed data shown (durability, rarity, damage, etc.)?
  • can users combine items to make new items? How? Limits? Results? Messages of success/failure?
  • can users substantially modify items with other things like spells, gems, sockets, etc.?
  • does a worn-out item (shovel) become something else (like a stick) when the item wears out fully?
  • etc.

Your best bet is probably to write down exactly what you want feature-wise. It may be useful to get very familiar with an existing game so you have an actual example of each feature in action.

Once you have decided a baseline design, fully work through two or three different inventory tutorials on Youtube, perhaps even for the game example you have chosen above.

Breaking down a large problem such as inventory:

https://discussions.unity.com/t/826141/4

If you want to see most of the steps involved, make a “micro inventory” in your game, something whereby the player can have (or not have) a single item, and display that item in the UI, and let the user select that item and do things with it (take, drop, use, wear, eat, sell, buy, etc.).

Everything you learn doing that “micro inventory” of one item will apply when you have any larger more complex inventory, and it will give you a feel for what you are dealing with.

As for the common problem of loading / saving, here’s some info:

Load/Save steps:

https://discussions.unity.com/t/799896/4

An excellent discussion of loading/saving in Unity3D by Xarbrough:

https://discussions.unity.com/t/870022/6

Loading/Saving ScriptableObjects by a proxy identifier such as name:

https://discussions.unity.com/t/892140/8

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:

https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

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.

https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide

Thanks for the responses! I had thought JSON would only be used for save data or something, but it seems like it might work for what I want, I’ll try all these implementations.

I do agree, it’s probably not a good first project, but I’ve been motivated to make it and I’ve learned a ton just doing this passion project. Perhaps using a turn-based rpg as an example was abit misleading; I don’t intend to do any open world stuff, loot drops, things like that. It’s really just an auto-battler that goes from fight scene to shop scene to a buff scene, and back again. I’ve set up databases and some decent enough systems, and I think learning through this one project has been ideal for my learning style. Very early on it’s forced me to consider scope, expandability, and not just cobble bad solutions (best I can, at least, and even if I fail I learn alot). 100% respect the perspective though, and I’ll definitely check out all the great resources you linked, thanks alot Kurt!

From reading your post I think you are more so looking for how to persist data between scene loads? In that case you just need something that hangs around through scene-loads carrying the data you need.

One option is an additively loaded scene that hangs around and holds this information, or it could just be a static class (of which you need to be mindful of it’s lifetime differences).

I’ve used the first option myself in a project. Just a game object in its own scene, with a component on it, that holds a reference to a scriptable object. Said SO is the beginning of a web of references that represents all the runtime save data. And since I have it all in one place, it’s straight forward to take this data, turn it into serialisable save data and write that out (ie: a save game).

Alrighty, I got a solution to work which wasn’t too bad, I’ll write it up for anyone curious:

I conveniently already had a database of all my Scriptable Objects and a method for getting each via a unique id (forgot I even did this but it payed off to plan ahead). So I basically implemented DevDunks idea:

The script with all the stats and SO reference is a monobehaviour, so I just made a TransferData class that acts as a middleman and stores any stats I want to save along with the SO ID. These are all just ints and strings. Convert that TransferData object into a JSON, keep that wherever. To transfer back to a game object, just instantiate a prefab which contains the monobehaviour, set the SO reference ID and any stats I wanted to move. Any non-serializable stuff gets handled by that SO, the changing stats and stuff gets handled by the JSON. Seems to work great. No save solution yet, keeping those references around for when that happens.

Some JSON implementations (such as Newtonsoft JSON .NET) let you provide custom converters.

Just a few days ago I made one to serialize a class that contains instances of a ScriptableObject-derived class we have.

It uses the fact that we also have a unique way to identify our ScriptableObject instances (in this case, the name; we keep them all in one subdirectory so name conflicts are not possible), and then the converter class simply serializes the SO name as a string on Serialize(), and reads the string and Resource.Load()s the SO on Deserialize().

Works a treat!

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-6-0