Saturday, March 19, 2016

Production Post 6 : Data!

A couple weeks ago, the team and I went to implement some little thing. I don't recall what this thing was, but it did uncover a whole slew of issues that urgently needed resolving. We traced the issue back to GameManager, but to fix it we would have to change a lot about how we are saving and loading data. The team and I worked for a few hours to try and hack what was already there together, but it just wasn't working and we all went home at around 1AM. Side note, walking out of the lab with a fundamentally broken game is, like, really disheartening.

So I went home, got some sleep, and death-marched back to campus the next day hell bent on making this work.

Step one was to make an entirely new class: DataManager. DataManager would store all the World and Level data the game needed to operate. All objects, manager, and scripts that needed this data would run through this class. This was no small task: at the time, there was practically no management of this data: there were places that stored data that meant different things than the other places. The code was littered with pseudo saving and loading that sort of did the actual save file writing and loading pretty randomly. The previous SaveLoadManager was this tacked on thing that the game sometimes wrote to, but most of the time did not. It was a nightmare, but not unconquerable.

First thing I did was write make two structs. These structs went through a few iterations, especially with how we managed the completed nodes, but the final structs became:


There are two instances of these structs in DataManager, and EVERYWHERE in the code the previously referenced random half-assed versions of these could now just call a long series of getters and setters defined in DataManager to modify these values. Another neat thing I did to further make DataManager a pleasant black box: in all the setter functions, in addition to just setting the data, I also make a call to the Save() function. This means that absolutely nothing in the code has to deal with saving and loading, DataManager would just do it.

Previously, there were a few calls throughout the code to the Load() function. This now served absolutely no purpose, since the most recent data is always accessible from DataManager: no file-reading required. So, the ONLY place in the code where Load() is called is in DataManager's Awake() function, which only calls once at the start of the application. Beautiful!

Which brings me to the next big change: replacing the non-functioning, platform-dependent data file with Unity's built in PlayerPrefs. PlayerPrefs is essentially a managed data file with a simple Dictionary-style lookup system. It was exactly what we needed. The Save function is as follows:































Simple enough, and totally under the hood, taken care of, and nobody needs to worry about saving or loading. A huge improvement already!

But, PlayerPrefs can only save three data types: ints, floats, and strings (along with nice Dictionary-style lookup keys). So, as you may have noticed, the only data that isn't one of those data types is the List of Lists of integers which stores the CompletedNodes for each chapter. Saving this as a string was the obvious choice, and two functions had to be written to handle converting this list of lists of ints into a string: one to convert from string to List<List<int>>, and one to convert from List<List<int>> to string.

The main part of this was adding two unique identified characters: a '$' to indicate the end of a saved node ID, and a ';' to indicate the end of a chapter's list of node IDs.

Below, I'll post the (nicely commented, as usually) functions:

List<List<int>> to string for saving

string to List<List<int>> for loading
Reasonably elegant, I think, but ya know it doesn't really matter because, yet again, it's completely a black box. The only thing myself and my lovely programming team have to use from DataManager are the getters and setters to access and set data.

In addition to writing DataManager, I had to scroll through every single script in the entire project and make it all work with DataManager. It made a lot of stuff more elegant, and, more importantly, functional, which is something it was previously lacking. The DataManager opens a lot of doors there were previously either closed or really difficult and time-consuming to find.

And that's pretty much all of how DataManager works. So far, it's made the OverWorld possible, and sped up development time on pretty much anything that has to know about World or Level data.

No comments:

Post a Comment