Help me to get rid of repetitive code
Good day! This is a data persistence task. I have some data I want to conserve across scenes, as well as between application executions. While I understand how to achieve this, I cannot resolve a problem that arises when I have multiple GameObjects that are essentially separate data containers. How would I go around with creating multiple classes with DIFFERENT class members while keeping THE SAME functionality without duplicating code? I will elaborate with some code.
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
public class PlayerData : MonoBehaviour
{
#region data
public float fval;
public string msg;
#endregion
public static PlayerData data;
//always keep one instance on a gameobject across scenes
void Awake()
{
if (data == null)
{
DontDestroyOnLoad(gameObject);
data = this;
}
else if (data != null)
{
Destroy(gameObject);
}
}
public void Save()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = File.Create(Application.persistentDataPath + "info.dat");
//initialize object for serialization
_Data dataobj = new global::_Data();
//fill object with data
dataobj.fval = this.fval;
dataobj.msg = this.msg;
//write
bf.Serialize(fs, dataobj);
}
public void Load()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = File.Open(Application.persistentDataPath + "info.dat", FileMode.Open);
//initialize object for deserialization
_Data dataobj = new global::_Data();
//read into object
dataobj = (_Data)bf.Deserialize(fs);
//populate class members
this.fval = dataobj.fval;
this.msg = dataobj.msg;
}
}
[Serializable]
class _Data
{
public float fval;
public string msg;
}
I had attempts on creating a base class and inheriting from it, or creating an interface, however I encountered some problems along the way. For example I am not sure how to bind the reference of the class to the base class, so the base class takes control of the gameobject persistence (awake() logic) across scenes. With the interface idea, i think could end up with the same problem and I am not entirely sure that I can have MonoBehaviour methods in it. I am probably missing something very simple here.
I'm not quite following what the intentions are with your code (there's nothing suggesting what your hierarchy is going to look like - and the data fields in the Data class are repeated in the PlayerData), or what the issue is.
But with regard to this bit: " I am not sure how to bind the reference of the class to the base class, so the base class takes control of the gameobject persistence (awake() logic) across scenes", the simple answer to that is that the base class takes control of everything, except when a derived class overrides.
But it's probably useful to point out that when working with these kinds of things, one often uses factory-type classes or static methods to manage things like IO.
For example, you can save the details of an object just using polymorphism; since you're saving something, it's type is known. You'd get the derived type to first save its type identifier, and then its details. But when creating a new object based on that saved data, you can't because you don't know the type yet.
There, you might use a base class static loading function ins$$anonymous$$d (or a static method in a separate factory class), which can first load a type identifier and then have a switch on that, deter$$anonymous$$ing which of the derived types to create. Once the new object is created, you can use polymorphism to load the details.
Thanks for your reply. I should have clarified more on that earlier. The idea is to have separate "data containers" where I would like to keep data across all scenes and in between executions. In unity editor, it would just be a single gameobject that would never be destroyed, or duplicated (see awake() method). So one set of data would correspond to PlayerData. (health, speed, other attributes). Another set of data would be concerning an Enemy entity and etc. I would like to keep them separate as far as loading/saving is concerned. For example, in one scene I would just choose to load player data, and do nothing with enemy data, etc.
I could just make the same script for enemy data, put that on its own gameobject with its own member data, however, you could probably tell that some of the code would be repeated. Like the awake() method. I just wanted to know if there is a more elegant solution without code repetition. So maybe have a base class that does the gameobject handling (making sure its never duplicated or destroyed. Sort of like a singleton idea). and have other data classes (with their own unique sets of data) inherit from that base class behaviour.
Your reply is a bit heavy on the technical ter$$anonymous$$ology. I'll try to dissect this info bit by bit. It would be helpful to see some example code however.
as far as the _Data class goes, I just set it up for serialization, so its packed nicely with correct padding, etc. It is just a temporary holder. You can refer to this video to understand the data persistence strategy I am using: https://www.youtube.com/watch?v=J6FfcJpbPXE&t=4m10s
Answer by Bunny83 · Nov 12, 2017 at 02:56 PM
First of all using BinaryFormatter is not really a good choice. Yes it's often adviced to be a simple solution. However the binary format is very strict and very verbose. So it's rather large for what is actually stored and it will easily break when you change anything on your class (adding, removing or renaming fields).
However if you want to stick with it you can simply create a generic baseclass like this:
public abstract class BinarySerializedMonoBehaviour<TSelf, TData> : MonoBehaviour where TSelf : BinarySerializedMonoBehaviour<TSelf, TData> where TData : class, new()
{
public static TSelf instance;
public abstract string fileName { get; }
protected abstract void OnSerializeData(TData dataobj);
protected abstract void OnDeserializeData(TData dataobj);
protected virtual void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this as TSelf;
}
else if (instance != null)
{
Destroy(gameObject);
}
}
public void Save()
{
BinaryFormatter bf = new BinaryFormatter();
using (FileStream fs = File.Create(Application.persistentDataPath + fileName))
{
TData dataobj = new TData();
OnSerializeData(dataobj);
bf.Serialize(fs, dataobj);
}
}
public void Load()
{
BinaryFormatter bf = new BinaryFormatter();
using (FileStream fs = File.Open(Application.persistentDataPath + fileName, FileMode.Open))
{
TData dataobj = (TData)bf.Deserialize(fs);
OnDeserializeData(dataobj);
}
}
}
Every of your singleton classes that should be saved simply derive from this class like this:
public class TestClass : BinarySerializedMonoBehaviour<TestClass, _TestClassData>
{
public override string fileName { get { return "TestClass.info"; } }
public int someIntValue;
public string someStr;
protected override void OnSerializeData(_TestClassData data)
{
data.someIntValue = someIntValue;
data.someStr = someStr;
}
protected override void OnDeserializeData(_TestClassData data)
{
someIntValue = data.someIntValue;
someStr = data.someStr;
}
}
public class _TestClassData
{
public int someIntValue;
public string someStr;
}
So all you have to do for each class is the class specific stuff. So the specific data class that should be serialized and the copying of the data from / to that class as well as the filename that should be used.
Though if you want to have multiple "saves" you might want to add an additional path parameter to the Save and Load methods.
In the end there wasn't much of "repetitive code".
Thanks for taking your time. Very clear and exactly what I was looking for.
Just for a future reference, you have said: "First of all using BinaryFormatter is not really a good choice". What is a better choice? I want to read up on that.
Your answer
Follow this Question
Related Questions
Inheritance Question 1 Answer
Serialize Lists with multiple inherited classes or interfaces ? 1 Answer
[SOLVED] JSON Serialization of Derived Classes 2 Answers
Polymorphism on member attribute 1 Answer
Can't instantiate new class C# 0 Answers