In this game, the player will seach the dungeon to feed the bunny boss within time limit. During this journey, player will face a series of challengnes, mazes, and hostile creatures. The ultimate goal is running toward freedom.
Role: Developer
Team size: 10
Engine: Unity
Duration: 4 weeks
Languages used: C#
Game will automatically save the status each time the player feeds the bunny, which also triggers a new day. If the player fails to survive the new day, game will reset to previous day to let you try again with respawning foods.
During the game play, player has to solve a series of puzzles to unlock new zones for exploration. Opened zones will remain opening during reloads.
The system will save player's status and inventories, including:
how many days has passed
how many collectables the player hold
Also, the system will reload every puzzle that player has worked out. In this game, puzzles are solved by moving crates onto correct places to create new route or push putton. The whole mechanism is physics-driven, i.e. the door will shut if the crate is moved from the button location. The saving system reads all crates' world position and save them in file. Upon reading, the crates will be placed to correct locations. The number of movable crates in game is fixed and strictly one-on-one mapping.
public void SaveGame()
{
Dictionary<string, string> m_dataToSave = gameData.WriteToDict();
// false means the file will be overwritten.
// public StreamWriter(string path, bool append, Encoding encoding);
StreamWriter sw = new StreamWriter(savePath, false, Encoding.ASCII);
foreach (KeyValuePair<string, string> kvp in m_dataToSave)
{
sw.WriteLine(kvp.Key + "=" + kvp.Value);
}
foreach (GameObject button in m_buttonOBJS)
{
bool isButtonPressed = button.GetComponent<PuzzleButton>().IsPressed;
if (isButtonPressed)
{
//m_doorsOpened.Add(button);
// only write pressed button's position
sw.WriteLine("buttonPressed=" + button.transform.position.x + "," + button.transform.position.y + "," + button.transform.position.z);
}
}
foreach (GameObject checkCrate in m_crateOBJS)
{
// write to file
// note: it offsets crate's location to make sure it won't stuck at ground
// could be removed if this is deemed unnecessary.
sw.WriteLine("crate=" + checkCrate.transform.position.x + "," + (checkCrate.transform.position.y + 2.0f) + "," + checkCrate.transform.position.z);
}
sw.Close();
Debug.Log("Game Saved.");
}
public void ReadFromSave()
{
// TODO: add execption handlers in this function
Dictionary<string, string> m_inputStatus = new Dictionary<string, string>();
List<Vector3> m_crateRespawnLocation = new List<Vector3>();
List<float> m_buttonDistance = new List<float>();
foreach (string line in File.ReadLines(savePath))
{
if (line[0] == ';')
{
// allow comments starting with semicolon.
// note: comments won't be written back to save files
continue;
}
string[] fields = line.Split("=");
if (fields[0] == "crate")
{
// read crate location
string[] position = fields[1].Split(",");
Vector3 crateLocation = new Vector3(float.Parse(position[0]), float.Parse(position[1]), float.Parse(position[2]));
m_crateRespawnLocation.Add(crateLocation);
}
else
{
if (fields[0] == "buttonPressed")
{
// restore button
string[] position = fields[1].Split(",");
Vector3 buttonLocation = new Vector3(float.Parse(position[0]), float.Parse(position[1]), float.Parse(position[2]));
m_buttonDistance.Clear();
foreach (GameObject button in m_buttonOBJS)
{
m_buttonDistance.Add((button.transform.position - buttonLocation).magnitude);
}
int minIndex = m_buttonDistance.IndexOf(m_buttonDistance.Min());
PuzzleButton p_button = m_buttonOBJS[minIndex].GetComponent<PuzzleButton>();
p_button.IsPressed = true;
StartCoroutine(p_button.SquishButton());
}
else
{
m_inputStatus[fields[0]] = fields[1]; // read it and cover default value
}
}
}
// reset crate location
// make sure no illegal access
int noOfCrates = Mathf.Min(m_crateRespawnLocation.Count, m_crateOBJS.Count);
for (int i = 0; i < noOfCrates; i++)
{
m_crateOBJS[i].transform.position = m_crateRespawnLocation[i];
}
gameData.ParseFromDict(m_inputStatus);
}
Discussion
Binary optimization can be used on save files; we don't have to read/write human-readable floating-point numbers and strings, and only binary representations will suffice. However, we did not implement this feature for debugging purposes since the I/O around save files are NOT intensive.
One of our teammates dropped out of school during this project, which left the UI part very bug-prone. We have to introduce a lot of extra checkings to make sure the save files are loaded when we want and deleted when we asked.