-
Notifications
You must be signed in to change notification settings - Fork 0
Program_SaveLoad
In order to save and load the game, we need to build a pipeline between data source and game objects. Generally speaking, the pipeline is composed of three parts:
- [ Data source ] <--> [ Data hub ] <--> [ Game object ]
Game data is usually stored in an external file. But we can retrieve data from various sources. It might be a binary or XML file, an internal dictionary, or a random number which is generated on the fly.
The data hub does two things:
- Read data from or write data to the source.
- Collect data from or send data to the object.
Now let's dive a little deeper into the code. In Fungus Cave, XML and binary data goes through two slightly different pipelines:
- [ XML file <-->
SaveLoadFile
] <--> [ XData ] <--> [ GameObjectX ] - [ Binary file <-->
SaveLoadFile
] <--> [SaveLoadGame
] <--> [ GameObjectY ]
First let's talk about saving and loading XML files.
As mentioned above, a XML file goes through this pipeline.
- [ XML file <-->
SaveLoadFile
] <--> [ XData ] <--> [ GameObjectX ]
SaveLoadFile has two public methods to handle XML files:
XElement LoadXML(string path)
void SaveXML(XElement data, string path)
This class interacts with files directly and outputs or requires a complete data object. By complete, I mean LoadXML()
reads the whole XML file and outputs everything unabridged.
The XData
class extracts a portion of data from the return value of LoadXML()
. Some XData
class implements ISaveLoadXML
and/or IGetData
:
ISaveLoadXML
simply wraps LoadXML(string path)
and SaveXML(XElement data, string path)
. They interact with a specific file.
public interface ISaveLoadXML
{
void Load();
void Save();
}
IGetData
, which is defined in GameData, handles data extraction.
public interface IGetData
{
XElement GetData<T, U>(T t, U u);
int GetIntData<T, U>(T t, U u);
string GetStringData<T, U>(T t, U u);
}
My XML files usualy has two levels:
<ActorTag>
<HP></HP>
<Name></Name>
</ActorTag>
So in this case t
refers to ActorTag
and u
refers to HP
or Name
.
ActorData is one of the XData
classes. It reads data from Data/actorData.xml
.
GameObjectX
is the final stop in our XML data trip. It calls XData
's methods to get data. For example,
Person.HP.MaxHP = ActorData.GetIntData("Person", "HP");
The three segements, file, data hub and game object are independent of each other. We always need the game object, Person
, but we can set MaxHP
in different ways:
- Set it directly: MaxHP = 10.
- Use
XData
to read an XML file. - Use
XData
to get the value from an internal dictionary.
As mentioned above, a binary file goes through this pipeline.
- [ Binary file <-->
SaveLoadFile
] <--> [SaveLoadGame
] <--> [ GameObjectY ]
SaveLoadFile has two public methods to handle binary files:
IDataTemplate[] LoadBinary(string path)
void SaveBinary(IDataTemplate[] data, string path)
Binary data is stored in an array. It must implements IDataTemplate
:
public interface IDataTemplate
{
DataTemplateTag DTTag { get; }
}
Every element in the array has a unique DTTag
. The tag is critical to loading data. More on this below.
We do not need to serialize a complete Person
object, because his MaxHP
, Name
and some other data can be obtained from XML files. Instead, we can create a smaller class that stores this Person
's Salary
. Yes, not all people are born equal:
[Serializable]
public class DTPerson : IDataTemplate
{
public int Salary;
DataTemplateTag DTTag { get { return DataTemplateTag.Person; } }
}
SaveLoadGame is XData
's counterpart:
- It collects data from objects all over the game and then calls
SaveLoadFile.SaveBinary()
. - It calls
SaveLoadFile.LoadBinary()
and then sends the data to objects.
But how does the collecting and sending work? Inside SaveLoadGame
, there are two events:
public event EventHandler<LoadEventArgs> LoadingGame;
public event EventHandler<SaveEventArgs> SavingGame;
The event args are defined like this:
public class LoadEventArgs : EventArgs
{
public IDataTemplate[] GameData;
public LoadEventArgs(IDataTemplate[] gameData)
{
GameData = gameData;
}
}
public class SaveEventArgs : EventArgs
{
public Stack<IDataTemplate> GameData;
public SaveEventArgs(Stack<IDataTemplate> gameData)
{
GameData = gameData;
}
}
Objects that need to be saved and loaded subscribe these two events and implement ISaveLoadBinary
, which is defined in SaveLoadFile:
public interface ISaveLoadBinary
{
void Load(IDataTemplate data);
void Save(Stack<IDataTemplate> data);
}
Let's take Person
for an example.
// Subscribe the saving event.
SaveLoadGame.SavingGame += Person_SavingGame;
private void Person_SavingGame(object sender, SaveEventArgs e)
{
SaveBinary(e.GameData);
}
// Implement ISaveLoadBinary.SaveBinary().
public void SaveBinary(Stack<IDataTemplate> data)
{
DTPerson save = new DTPerson
{
Salary = this.salary
};
data.Push(data);
}
When loading the game, we need to find out this object's data based on DTTag
.
// Subscribe the loading event.
SaveLoadGame.LoadingGame += Person_LoadingGame;
private void Person_LoadingGame(object sender, LoadEventArgs e)
{
LoadBinary(e.GameData);
}
// Implement ISaveLoadBinary.LoadBinary().
public void LoadBinary(IDataTemplate[] data)
{
foreach (IDataTemplate idt in data)
{
if (idt.DTTag == DataTemplateTag.Person)
{
DTPerson value = idt as DTPerson;
this.salary = value.Salary;
return;
}
}
}
There is one last thing worth mentioning. An object's loaded data might be overwritten by Unity's Start()
method. Refer to this article for more information.
Home | Latest | Game List | Design | Programming