- Home /
Saving and loading Prefab clones at runtime, C#
I am making a simple map creator/editor for my final year project where i can place prefab clones in 3d space to build a multistory building. I use two types of cubes namely a "WallCube" and a "FloorCube".
I toggle between the cube selection and instantiate them on mouse-click.
I need a way to save the position and the type of the instantiated prefab and load it back at runtime. The save data needs to be in the form of a human readable text file.
I know how to instantiate and destroy the cubes, as well as create, edit and read text files, but I just can't figure out a way to interpret the saved data and use it to re-instantiate the prefabs when loading the save file.
Having an electrical engineering background means that I have a limited knowledge of the subject matter. Any assistance would be highly appreciated.
P.S. I need to save the data in the same directory as the map editor
Answer by TonyLi · Feb 24, 2014 at 02:58 PM
If you put all your clones under a root GameObject, you can just iterate over the children to list them.
The simplest text file format would be something like:
WallCube 0 0 0
WallCube 2 5 3
FloorCube 8 1 7
But I'd recommend using JSON or XML because it's easier to expand and maintain. C# makes XML pretty easy. Roughly (and without testing since I'm not near Unity right now):
public class CubeInfo { // Stores the info about one cube.
public string prefabName;
public Vector3 position;
public Quaternion rotation;
public CubeInfo() {}
public CubeInfo(Transform cube) {
prefabName = cube.name.Replace(" (Clone)", string.Empty);
position = cube.position;
rotation = cube.rotation;
}
}
public class BuildingInfo { // Stores info about all the cubes.
public List<CubeInfo> cubeList;
public BuildingInfo() {}
public BuildingInfo(GameObject rootObject) {
cubeList = new List<CubeInfo>();
foreach (var child in rootObject) {
cubeList.Add(new CubeInfo(child));
}
}
public Instantiate(GameObject rootObject) {
// Rebuild the cubes after loading building info:
foreach (var cubeInfo in cubeList) {
GameObject cube = Instantiate(
Resources.Load(cubeInfo.prefabName),
cubeInfo.position, cubeInfo.rotation);
cube.transform.parent = rootObject.
}
}
}
void Save(GameObject rootObject, string filename) {
BuildingInfo buildingInfo = new BuildingInfo(rootObject);
XmlSerializer serializer = new XmlSerializer(typeof(BuildingInfo));
TextWriter writer = new StreamWriter(filename);
serializer.Serialize(writer, buildingInfo);
writer.Close();
}
void Load(GameObject rootObject, string filename) {
XmlSerializer serializer = new XmlSerializer(typeof(BuildingInfo));
TextReader reader = new StreamReader(filename);
BuildingInfo buildingInfo = serializer.Deserialize(reader) as BuildingInfo;
buildingInfo.Instantiate(rootObject);
}
For clarity, the code above also skips null checking and other good coding practices. I'm typing this directly into the answer box since I'm not near Unity, so it might be a minor miracle if it compiles without syntax errors. But it should give you a general idea of one way to do this.
@TonyLi: Hah, you beat me to it by a $$anonymous$$ute :) One thing I would add is that I assumed that op might need different data for different classes, like, say:
WallCube 0 0
WallCube 2 3
FloorCube 8 flat 7.324
Otherwise, it works nicely for same-class based data.
@TonyLi: I did some $$anonymous$$or changes to your code to make it work.
I'm posting the code just in case anyone like me needs a little help figuring out the problems in the code.
using System.Collections;
using System.Collections.Generic;
//using System.Xml;
using System.Xml.Serialization;
using System.IO;
//using System.Text;
public class SaveLoadSystem : $$anonymous$$onoBehaviour {
public GameObject CubeContainer;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public class CubeInfo
{
public string prefabName;
public Vector3 position;
//public Quaternion rotation;
public CubeInfo()
{
}
public CubeInfo(Transform cube )
{
prefabName = cube.name.Replace("(Clone)", string.Empty);
position = cube.position;
//rotation = cube.rotation;
}
}
public class BuildingInfo
{ // Stores info about all the cubes.
// $$anonymous$$ake a List holding objects of type CubeInfo
public List<CubeInfo> cubeList;
public BuildingInfo()
{
// $$anonymous$$ake a new instance of the List "cubeList"
}
public BuildingInfo(GameObject rootObject)
{
cubeList = new List<CubeInfo>();
foreach (Transform child in rootObject.transform)
{
cubeList.Add (new CubeInfo(child));
//print (child);
}
}
public void reload(GameObject rootObject)
{
// Rebuild the cubes after loading building info:
foreach (var cubeInfo in cubeList)
{
GameObject cube = Instantiate(Resources.Load(cubeInfo.prefabName),cubeInfo.position, Quaternion.identity) as GameObject;
cube.transform.parent = rootObject.transform;
}
}
}
void Save(GameObject rootObject, string filename)
{
BuildingInfo buildingInfo = new BuildingInfo(rootObject);
XmlSerializer serializer = new XmlSerializer(typeof(BuildingInfo));
TextWriter writer = new StreamWriter(filename);
serializer.Serialize(writer, buildingInfo);
writer.Close();
print ("Objects saved into X$$anonymous$$L file\n");
}
void Load(GameObject rootObject, string filename)
{
// while(rootObject.transform.GetChildCount()>0)
// {
// GameObject.Destroy(rootObject.transform.GetChild (0));
// }
XmlSerializer serializer = new XmlSerializer(typeof(BuildingInfo));
TextReader reader = new StreamReader(filename);
BuildingInfo buildingInfo = serializer.Deserialize(reader) as BuildingInfo;
buildingInfo.reload(rootObject);
reader.Close();
print ("Objects loaded from X$$anonymous$$L file\n");
}
void OnGUI()
{
if (GUI.Button (new Rect (30, 60, 150, 30), "Save State"))
{
Save (CubeContainer, "savefile.xml");
}
if (GUI.Button (new Rect (30, 90, 150, 30), "Load State"))
{
Load (CubeContainer, "savefile.xml");
}
}
}
Of course I will be changing it and adding stuff to it like null-checking and over-writing etc. But for now, this should suffice. Cheers! :)
Thanks for posting the fixed code. You might consider sharing it on Unify, too: http://wiki.unity3d.com/index.php/Scripts in case it can help someone else.
Answer by RudyTheDev · Feb 24, 2014 at 03:01 PM
In common terms, Unity's editor is pretty much your level editor most of the time, and scenes are your levels. I assume you want custom editor during run-time, which is essentially a save game.
If you want it human-readable, you will most likely need to make your own save/load parser, re-create all your objects on load, and save/load each variable yourself. There are many different paradigms how saving/loading can be done. Usual recommendation for Unity is serialization (like Unity does internally) that preserves everything serializable about your objects. Of course, this is not at all human-readable if somewhat easier.
Before I go on, what you call "prefab clones" are just GameObjects in the scene (that they are blue means they have an editor-time prefab connection, but they are independent otherwise and become unlinked as soon as you start playing).
The following is personal preference. I usually keep my data bound as little to GameObjects as possible if I plan to save/load myself. This way I control myself everything I save and load. What I keep in GameObject scripts is basically their run-time and visual info that's not saved and various Unity events, like mouse clicks or collisions.
Let's say I have a WallCube class. When I create this class, I also make sure I instantiate a corresponding prefab for it and attach a simple reference script, say WallCubeGameObject. There are two cases how I can create the data class -- directly or by loading from save file. It also has a save routine. So something like:
public class WallCube
{
public WallCubeGameObject ourWallCubeGameObject;
public int ourHeight;
public WallCube(WallCubeGameObject wallPrefab, int givenHeight)
{
// Initialize our variables as given
ourHeight = givenHeight;
// Make our game object
ourWallCubeGameObject = (WallCubeGameObject)GameObject.Instantiate(wallPrefab);
ourWallCubeGameObject.ourWallCube = this; // tell it who we are for back-talk
}
public WallCube(WallCubeGameObject wallPrefab, StreamReader saveFileStreamReader)
{
// Load our variables from save
ourHeight = int.Parse(saveFileStreamReader.ReadLine());
// Make our game object
ourWallCubeGameObject = (WallCubeGameObject)GameObject.Instantiate(wallPrefab);
ourWallCubeGameObject.ourWallCube = this; // tell it who we are for back-talk
}
public void Save(StreamWriter saveFileStreamWriter)
{
// Write down to save file who we are, so later loading knows this
saveFileStreamWriter.WriteLine("WallCube");
// Save our variables to save
saveFileStreamWriter.WriteLine(ourHeight);
}
}
public class WallCubeGameObject : MonoBehaviour
{
public WallCube ourWallCube; // so we know which data is related to this instance
}
Let's say I am saving the entire game world. I would loop through all objects and call Save()
on each. They also write down who they are, e.g. "WallCube". Then, when I an loading them, I would see a string "WallCube" and call WallCube
class constructor. Something like:
identifier = streamReader.ReadLine(); // whatever way you save files, StreamReader is an example
if (identifier == "WallCube") cubes.Add(new WallCube(wallCubePrefab, streamReader));
elseif (identifier == "FloorCube") cubes.Add(new FloorCube(floorCubePrefab, streamReader));
Again, I have to stipulate this is just how I have done things for certain scenarios and highly depends on how you store and manage your data. But may be it gives you an idea of how to approach this.
You could use Reflection, but that, without going into details, is--in my opinion--an overkill.
Your answer
Follow this Question
Related Questions
Cant delete clones of prefab 1 Answer
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Dynamically saving and loading player-created prefabs 0 Answers