Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
I've got into quite a lot of detail of how my save/load system works, which is specific to a point and click adventure game, but the same concept can be applied to any game
I took a break from these posts for the holidays, so I thought we’d jump right back in with quite a code-heavy one! I’ll be summarizing how the save/load system works in Arrival in Hell.
The first thing I needed to do was make commonly used ‘complex’ object serializable (you’ll see why later). So I made a couple of little classes for Quaternion and Vector3 since both are used extensively in the game.
[Serializable] public class Vector3Serializable { public float x; public float y; public float z; public Vector3Serializable(Vector3 vector3) { x = vector3.x; y = vector3.y; z = vector3.z; } public Vector3 toVector3() { return new Vector3(x, y, z); } } [Serializable] public class QuaternionSerializable { public float x; public float y; public float z; public float w; public QuaternionSerializable(Quaternion quaternion) { x = quaternion.x; y = quaternion.y; z = quaternion.z; w = quaternion.w; } public Quaternion toQuaternion() { return new Quaternion(x, y, z, w); } }
These classes allow me to quickly convert to and from a serializable and non-serializable version of the same data. I also did this for my custom type InventoryItem; it is the only custom complex type I use.
Next I needed to create a SaveData object (also serializable) which can store the entire state of the game. This includes player position, player rotation, flags (turned on and off as you interact with IneractiveObjects), positions and rotations of every Interactive Object, the current InventoryItems in your inventory and finally a snapshot of where you are with any dialogue trees (unlocked conversations etc).
[Serializable] public class SaveData { public Vector3Serializable playerPosition; public QuaternionSerializable playerRotation; public Dictionary flags; public Dictionary positions; public Dictionary rotations; public List inventory; public string dialogue; }
Whenever you hit save, the first step is to populate this object. Here is how I went it for each property:
Player position and rotation are pretty straight forward, only thing to remember is to convert to the serializable objects since Vector3 and Quaternion are not serializable
Flags are already contained in a Dictionary object with Bool values and String keys, so this is already serializable and can just be copied over to the save object
When the game starts, all interactive objects register themselves with the save system. Every time you save the game, the interactive objects are looped through, and their position and rotation recorded in serializable objects
Inventory is looped through and items stored as serializable objects
The excellent Dialogue system we are using actually has a method for outputting it’s state as a String which can later be read back; simply call PersistentDataManager.GetSaveData()
So now we have a complete save game object, the next step is to convert the object to a save-able format, then save that to disk. These 4 lines of code do the job:
BinaryFormatter bf = new BinaryFormatter (); FileStream file = File.Create (path); bf.Serialize (file, data); file.Close ();
In order to load we simply do the reverse. Load the file from the system, parse it into a SaveData object, then take those data objects and write them to the correct places instead of reading. In order to have multiple save game files, I simply use a different filename for each of the 6 save ‘slots’ available in the gameFor more detail on how all of this works check out the Unity documentation as well as this great tutorial.
One last thing worth mentioning is that with each save game I take a screenshot with every save to be displayed as a thumbnail for each save file. The best way I found to do this without a big lag spike was to read the pixels on screen, scale them down, and then save it. I also do it in a Coroutine which waits for the end of the frame after a close, otherwise all the thumbnails are just a screenshot of the save menu!
IEnumerator TakeScreenshot(int slot) { yield return new WaitForEndOfFrame(); Texture2D texture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false); texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0); texture.Apply(); TextureScale.Bilinear(texture, (int)ThumbSize.x, (int)ThumbSize.y); System.IO.File.WriteAllBytes(Application.persistentDataPath + "/saveGame" + slot + ".png", texture.EncodeToPNG()); if (saveSlots[slot]) { saveSlots[slot].Refresh(); } }
The finished load screen
So there it is. It’s very simple and requires a lot of manual data pulling and pushing, but it works fine for this game. If I had to do it again perhaps I’d create a ‘Saveable’ component which can be attached to any game object, which would automate the process slightly.
Next week I’ll talk about the image effects I’m using.
Read more about:
Featured BlogsYou May Also Like