Unity3D: Saving and Loading
So this one's driven me up the wall enough, and I wanted to find out if anyone has any better ways of handling saving and loading.
Right now, we're using a system where everything related to a player's profile, career, equipment, inventory, etc etc are all rolled up into a Player class, and I've borrowed a component from this asset that can serialize any C# object into JSON, and deserialize it back properly. So far it's behaving really well - it can handle Dictionaries and Interfaces unlike XMLSerializer, and I also managed to get an implementation of SharpZIP working, so the output is compressed before being written to disk.
I'm aware of at least 2 other options for saving and loading: The one is PlayerPrefs, which would work great on OSX, but on Windows you're limited by the registry size (plus the registry isn't really meant for that sort of data, right?), and the other is having the data pushed to an API endpoint, but that comes with all sorts of complications of its own.
Is there another approach to saving/loading that might be a little simpler?
Right now, we're using a system where everything related to a player's profile, career, equipment, inventory, etc etc are all rolled up into a Player class, and I've borrowed a component from this asset that can serialize any C# object into JSON, and deserialize it back properly. So far it's behaving really well - it can handle Dictionaries and Interfaces unlike XMLSerializer, and I also managed to get an implementation of SharpZIP working, so the output is compressed before being written to disk.
I'm aware of at least 2 other options for saving and loading: The one is PlayerPrefs, which would work great on OSX, but on Windows you're limited by the registry size (plus the registry isn't really meant for that sort of data, right?), and the other is having the data pushed to an API endpoint, but that comes with all sorts of complications of its own.
Is there another approach to saving/loading that might be a little simpler?
Comments
http://wiki.unity3d.com/index.php/ArrayPrefs2
I like it cause I can do cross platform without worrying about anything really. You can always just write a little in between script (API, sure) that takes your data you want to save and splits it up into 1mb chunks for the registry and back again.
For me I haven't run into any problems with playerprefs, and in the beginning when I did want to do user made content for montez I had a sub folder in which I would just drop the text files and read from them using basic text manipulation.
Maybe check out this : http://answers.unity3d.com/questions/21666/creatingsaving-prefabs-during-runtime.html
What exactly do you want to save ?
What platforms are you looking at ?
If my save structure is small isn't going to change (or if I don't care about backwards save compatibility) then I prefer to just convert the class base64 and write it to PlayerPrefs. If I care about human-readability I'll use JSON.
This is the exact approach I would use, and have done in the past. The only difference is we added a layer to encrypt the files with a per user key. This was because within 24 hours of first release, a manually modified save that only used a common key was available for download that gave the player effectively infinite resources. Not great for IAPs :) Oh, and no, the common key wasn't in the code as plain text or as a single blob.
@mattbenic It drove me mad before, when I thought I had to build my own object serializer, with code to store and retrieve every single field on my Player class. Then I found that JSON serializer library which does all the reflection and stuff for me, but the classes I'm using are dragging a lot of other crap into my game, and I feel like I'm constantly missing something really obvious here. Saving complex objects cannot possibly be a new use case, and I keep thinking there's a better way to do it in Unity.
I like the idea of adding an encryption layer - how did you go about the per-user key? At first thought, I could generate a GUID and store it using PlayerPrefs, then use a hardcoded string inside my game to salt it, but that would lock the profile to the computer, and syncing files via Steam Cloud would be impossible.
I don't understand what you mean by dragging a lot of other crap into your game. Our Save/Load system was reduced to a few generic methods exposed on a utility object (SaveObjectToFile(), LoadObjectToFile()) and no specifics were ever introduced. We actually switched from saving/loading XML to rather using json in under a day without touching any game code. As long as you're keeping things properly separated, it shouldn't affect your game.
IIRC one big thing we did also do was introduce a system for asyncronous saving, but again, that was hidden behind a simple save api so game code never really got polluted beyond calling on a save method at the appropriate points. It's quite possible/likely steam already does save encryption.
I would expect it's possible to get some kind of user ID from steam's APIs? I've never used them. If you build your system so that the source of the key is separated from the actual saving/encryption you should be able to support whatever user system you want, each one just provides a different string to base the encryption key on.
If I remember right, our encryption would either be based on device id, or unique user ID _IF_ the user had agreed to provide email and password and we had created an account for them on our web services. Device ID obviously isn't portable, but I can't remember if we did iCloud integration since it was so new then :)
I think I'll take another crack at the serializer later on, see if I can strip it down some more so I don't have dangling methods. For now it works, and I'll probably just have to be happy with it.
Deep in the bowels of those scripts are methods for saving to/from JSON. I pulled them out, along with every other class and namespace that Mono complained about not being able to find. So right now I've got all of this crap: http://i.imgur.com/keLTYKd.png - and I'm sure I can delete even more of them, when I actually follow the path of what the JSONSerializer does.
So to be clear, I didn't write my own, but I might end up dropping all of that and starting over, when I know enough about serializable attributes and all the rest. My profile relies heavily on Dictionaries and Interfaces, two things which are the nemesis of XMLSerializer, the only basic .NET serializer available to me. It's possible that I need to re-think the way I've written a lot of that code, so that I can actually have it written to disk as XML using the native .NET functionality. So far I've approached this project with the same mindset I use for desktop software, and it may be the wrong paradigm. That occurred to me a few minutes after I posted that last reply :) Steam has a unique ID per player, so I could theoretically grab that and use it as a base for my encryption, and that way, profiles encrypted on one machine (or platform even) could be decrypted on another, provided you're authenticating to Steam with the right username/password - which you need to do anyway in order to retrieve your save files from the steam cloud.
I forget what we used previously, but in my current project we use JsonFx. I don't think I'd flatout recommend it (it's on my todo list to evaluate alternatives) because I've had to do a fair amount of modification to it.
If the issue is not the serialization itself, but rather all those extra scripts, then why not find a dll library? I second this. I also use JsonFx and haven't run into any major issues with it. Apparently JSON.net is a more robust solution though.