Unity3D: Saving and Loading

edited in Questions and Answers
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?

Comments

  • This is probably not "the best way" to do things but I just use this script in my stuff :
    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 ?
  • @wogan: Your method sounds like the standard approach. PlayerPrefs works nicely because it's part of the engine and works across all platforms (no dealing with filepaths!). Obviously if your saves are large and you're worried about registry size then it may be better to write to a file.

    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.
  • @wogan you haven't said why this is driving you mad?

    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.
  • @Squidcor How would you base64 an object, though? Don't you still need to serialize it into text, then convert it?

    @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.
  • Depending on the Json serializer, you should be able to attribute particular fields for serialization and keep extra cruft out of your game. But really having dedicated objects that are designed only for data that should be saved are IMO best. Those objects can of course be fields on your classes that use them, and if you don't want obj.SaveData.PlayerName type code all over the place some wrapper properties sort that out.

    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.
    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.
    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 :)
    Thanked by 1Chippit
  • This is probably not "the best way" to do things but I just use this script in my stuff :
    http://wiki.unity3d.com/index.php/ArrayPrefs2
    ArrayPrefs2 looks interesting, however it's still using the registry on Windows. I'm cautious about using stuff like that, since the registry itself has a maximum size, and trying to write large amounts of text to it could cause actual system crashes. I wouldn't want to be responsible for that :P
    What exactly do you want to save ?
    What platforms are you looking at ?
    It's a bit difficult to explain, but basically a single Player in this game will have some basic attributes (name, color, credit balance, etc), and a whole lot of complex objects attached (inventory, hardware, software), with each of those having properties of its own. Imagine the sort of work required to save, say, a Diablo 3 character - it's around that level, with inventory, stuff equipped, locations in the world, waypoints, etc.

    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.
  • mattbenic said:
    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.
    Ah, I said that in my first post. I found this asset, which is a beast of a save system: https://www.assetstore.unity3d.com/en/#!/content/3675

    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.
    mattbenic said:
    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.
    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.
  • edited
    It may also just be that you want to try a different serializer, there are a couple out there and many have unity friendly versions or forks. Most offer a decent level of functionality and you should be able to get away with whichever one has the best API for you.
    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.
  • wogan said:
    @Squidcor How would you base64 an object, though? Don't you still need to serialize it into text, then convert it?
    Serialize it with a BinaryFormatter and then convert the bytes with Convert.ToBase64String.
    wogan said:
    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.
    Are you aware of the ISerializable interface? This is how you usually exert some control over how your classes are serialized.

    If the issue is not the serialization itself, but rather all those extra scripts, then why not find a dll library?
    mattbenic said:
    But really having dedicated objects that are designed only for data that should be saved are IMO best.
    I second this.
    mattbenic said:
    I forget what we used previously, but in my current project we use JsonFx.
    I also use JsonFx and haven't run into any major issues with it. Apparently JSON.net is a more robust solution though.

  • I also use JsonFx and haven't run into any major issues with it. Apparently JSON.net is a more robust solution though.
    I should have qualified my criticism with the fact that we're on a really old version of JsonFx. One of the "alternatives" I would have looked at was the latest JsonFx, I suspect they would have fixed a lot of the things we did.
Sign In or Register to comment.