Quick guide to Steam Cloud saves
Devlog


Disclaimer: The official Steamworks documentation on Steam Cloud is your best guide on how to actually implement and set up cloud saving for your game. This is an overview on what you can and probably should do and is meant to help get a clear idea on how to approach cloud saving before having to go through the documentation, which can get a bit technical if you're not sure what you're looking for.

I've recently made the move from mobile games to Steam. My first indie game, The Adventures of Elena Temple, is about to be released on March 15th. Working with Steam has been pretty great so far, but while setting up everything related to cloud saving, I often found myself wishing for a high level guide to help with what you can do and what is recommended you should do on the subject. Sure, I managed just fine in the end using the official documentation, but now that everything is a lot clearer for me on the subject, I write this guide in the hope that other developers starting out with Steam can benefit from it.

Unity

My game is made in Unity, so I'll give various examples on how to set up cloud saving for Unity games, but the changes for other game engines should be easy to figure out.

The most important thing to remember when implementing your saves system in Unity is that you should avoid using PlayerPrefs if you're planning on using Steam auto-cloud. The data saved in PlayerPrefs for Windows is in a registry - HKCU\Software\[company name]\[product name] - and you can't upload that to the cloud. So an old school approach is recommended here: save your progress in actual save files, preferably at the location given by Application.persistentDataPath - for Windows, this should be in the AppData/LocalLow folder of the Windows user, under a folder named after the developer and another with the app name - these depend on what you've set up in Player Settings in the Unity Editor. I don't want to get into the details of saving files with Unity, use FileStream, BinaryReader, BinaryWriter or whatever you feel is the best approach for you, the important thing is to have a save file on disk when you're done.

The above applies to other engines too, you need to output a save file, with the difference that the data path would likely be different, so you need to find out what that is.

Auto-cloud

For The Adventures of Elena Temple I've used Steam's auto-cloud saves. Yes, you can have a lot more flexibility with the Cloud API, but I didn't need it. What I did require was for Steam to upload to the cloud my progress and settings save files and auto-cloud was a perfect fit for that purpose. In case you're totally new to this subject, Steam auto-cloud lets you configure paths to where your save files are, then uploads those files from the user's machine to his cloud storage when the user closes the game. Before a new play session, Steam checks the cloud to see if any save file has a newer version there, probably uploaded from a different machine than the one the user is currently on. If there is, it downloads it to the current computer and boots the game, letting the user access his latest progress no matter on which machine it was achieved.

Setting up auto-cloud is pretty easy, especially if your game is only compatible with one OS, let's say Windows. You need to set up a byte quota per user, which means how much you think the total amount of space your save files could take on disk. Obviously, put a slightly bigger value there. If you estimated around 10 MB, it's probably safer to put somewhere between 30-50 MB, just to be perfectly sure. Then you need to set the number of files per user, which, in theory, should mean how many save files there are for your game. In my case, it was two. But setting that value to 2 lead to a bunch of head-scratching moments later on, when I had to change something in the save paths and the newer files weren't uploaded to the cloud. Why? Well, because the older files were there and me changing the paths didn't mean the older files would be removed from the cloud. So when the newer files knocked at the cloud's door, the bouncer opened and said 'Sorry, we're all full!' In conclusion, if you need just 2 files, maybe set the value to 10, just be sure.


Next up is setting the actual path to your save files. In my case, with Unity and Windows, the path root was WinAppDataLocalLow - this is a predefined macro from Steamworks, there are a bunch of them to choose the one that matches your save location. The subdirectory should be [company name]/[product name] - replace these values with whatever you've set in Player Settings if you're using Unity or with whatever subfolder your game outputs to. It's recommended that all your save files use the same extension, like .sav or .dat or something, so you can set the pattern to *.sav or *.dat, meaning all files with that extension. And, finally, set the OS to Windows, save, publish the changes and you should have auto-cloud saves working on your developer account. If you want to enable them for everyone, make sure to uncheck cloud saves for developers only. Easy so far, right?

Per Steam user saves

With the above, cloud saves work for the current Steam user. But what happens if a different user logs into Steam on the same machine? Most likely, if the new user runs the game, it will automatically continue from the same spot the previous user left off. Then the save file uploads to the cloud as soon as the game is closed. Not ideal if we want to support several users on the same machine, each with different progress and if we don't want to go through the trouble of implement a users system inside our game.


The simple solution is to save the game's progress in a subfolder named with the current Steam user id. We get that from the Steam API, with the GetSteamID() method - more info on that can be found in the official documentation. After we made sure that saves are now outputted in the specified subfolder at the default path, we must also change the subdirectory in Steamworks, by appending /{64BitSteamID} at the end of the previous subdirectory - for Unity it would become [company name]/[product name]/{64BitSteamID}. This means that the auto-cloud system uploads the files from the subfolder named with the current Steam user's id to that user's cloud. Different users will not only have different save files on disk, they will also have only their unique save files on the cloud. Neat!

Cross-platform

What if your game supports multiple operating systems? The Adventures of Elena Temple is compatible with Windows and Mac and I wanted to make sure the save files are correctly synced through the cloud if I played on Windows, then later continued playing on Mac. To make that happen, you need to use root overrides. Which sounds scary, but they're really simple. It just means that while the save path we set above remains theoretically the same, if the Steam client is ran on another OS, it replaces part of that path with what we tell it to override it with. But before it can do that, we must change the OS setting from our save path to All OSes. This is to tell the auto-cloud system to use this path no matter the OS, but with the correct overrides we're going to set next. As a note, if we don't set an override for an OS, it means that it will try to use the default save path, which is why we won't set any override for Windows, because we know the save path already works on Windows.


After we create a root override, we set the original root to whatever we set as the root for the default path, in our case WinAppDataLocalLow. We set the new OS to what we need, for example Mac. The new root should be where your persistent data path is for the new OS, depending on your engine. For Unity and Mac, the new root should be MacAppSupport. For Linux, it's LinuxHome. In the add/replace path field we must set the subdirectory for the new OS, in our case unity.[company name].[product name]. If you set up different save files per Steam user, make sure to append /{64BitSteamID}. Then make sure to check the replace path box, as we want this subdirectory to replace the default one, not added at its end. Save, publish and, what do you know, your saves should now properly be synced between different operating systems!

Demo saves

This may not apply to you, but I think it's recommended to take it into consideration. For The Adventures of Elena Temple, I wanted the demo progress to migrate to the full version in case the user chose to upgrade. On disk things are relatively simple, just use the same paths between the two versions. But, in this case, you need to make sure that saves are compatible with both apps, from demo to full and, to be sure, from full to demo. If you think why would anyone play a demo after the full version, then you haven't been around in app development for long enough and you're still trying to judge human actions using logic. If something can be done, just assume that someone will do it, doesn't matter if it makes any sense or not.

On Steamworks, demos are different apps. Which means they have a different app id, different cloud settings and so on. To make sure that demo saves on the cloud work on the full app if the user upgrades, we need to set everything up with the exact same values we did for the full app. The only difference is that there is a field called shared cloud app id, which we left with its default value of 0 for the full app. For the demo, we need to set that field to the app id of the full game, the one inside the parentheses next to the app name in Steamworks. Setting this means that the demo uses the same cloud storage as the full game.

Wrap-up

With the settings above, your game now has cross-platform, per steam user cloud saves and the saves from the demo nicely migrate to the full game if the user purchases it. What more could you want? Here's a nice bonus screenshot with the Steam achievements for The Adventures of Elena Temple, as a treat for reading this far!


Don't forget, if you're a registered developer, the Steamworks documentation is your best source of in-depth information regarding cloud saves on Steam. Thank you for reading!