Holidays are over, time to get back to work.

Turns out that making a good reusable menu system is rather difficult. Real problem, of course, doesn’t lie in making UI elements that look good together, event though it is an important task as well. Real problem lies in understanding, what do you want your menu to be able to do, and then, writing proper reusable code for it. In my case, I wanted to be able to do three not-so-separate things: create and/or load player profiles, have persistent settings for audio and video, and have an automatically generated “Theme/Level Selection” menu.

As a result, instead of actually working on the UI, I ended up mostly adding new data management code and refactoring the existing one. Before we dig into this mess, let’s look at the data structures themselves. In this project, I currently have two active classes – PlayerData and LevelData. PlayerData contains, well, data about the player – name, settings, highscores, unlocked levels and achievements e.c.t. LevelData contains read-only information about individual levels that is required for gameplay, e.g. constants that define how scoring work.

As soon as I started working on the code that is supposed to control sound volume, I realised that everything I’ve made so far is a mess. What I wanted to do was make a system that loads information from the current player profile and applies it to the game, as well as gets every settings that the player changes and stores it in his profile, and then applies it in the next cycle. Initial solution was to use floats for sound volume, fetch them and apply them to UI and mixer controls. Immediately it showed to be a bad way of doing things.

Using this method meant that I’d have to manually assign everything, and write unique lines of code for every setting and every UI element. If I wanted to add, let’s say, a separate volume control for sound effects, I’d have to rewrite every single line of code. That, obviously, was not the way to go. But my current data structure didn’t allow for any other way of doing things, so I had to improvise.

First thought was “Oh hey just use generics, that’s easy!”. It was a very tempting idea, use a function that can handle any data type as input. Problem was, I had to use, essentially, a cycle, to access different class properties. They are not linked together in any way, even though they might have similar names and types. After trying a few different methods, including writing a custom IEnumerator, I gave up on that idea. And I still had to write separate code for UI elements, because there was no obvious way to streamline fetching and controlling them. That path was a no-no.

Eventually, I made an AudioSettings class that has three properties – name, volume and isEnabled. These can be set at init, associating a name with a mixer with the same name, and, at the same time, with a UI element with the same name – more on that later. A player class has a List of , which allows me to iterate through all settings with a single line of code, using a player data reference and a list of setting names, which is defined in advance. This way, if I want to add a new volume setting, I can just add a copy of existing UI element, a new mixer and a new AudioSetting to the list in the player class, give them all the same name and BOOM – everything works like a charm.

This is all possible because Unity can find game objects by name, and can also expose – make publicly accessible – mixer control variables. They both can use the same name without conflicting, so it is possible to use a single constant to reference everything associated with a certain setting, or, for that matter, anything you want to automate. Of course, that implies that your UI elements are made from the same prefab, or at least can be controlled in a similar way. For example, my code finds the slider component and the toggle component in children of a VolumeController group of objects, and works with that. There are more ways to do this, each fit for it’s own situation.

Next problem was even more fun that the sound control. How do I create/load players? It sounds as simple as it can be, but we already know that “looks simple” doesn’t mean anything except that it’s, without a doubt, not simple at all. That’s where poorly written initialization code came into play. What I had before was “I’ll just shove everything here at once and hope it works, I might even load some things twice, just to be sure”. That, obviously, didn’t work particularly well. It was a mess.

First of all, for some reason, I decided that it’s a good idea to mix up things in level data and player data. For many reasons, a better way is to keep read-only data in level data and put everything that can be changed into player data. This way, things like level data and achievement data only contain information about those things, while the player data has fields that tell the game whether this player unlocked that achievement or level.

I started by changing the PlayerData class to incorporate lists, containing player progress. I also added a flag that tells whether this player was the last on to play the game, and also acts a pointer to the current player. That meant I had to rewrite the interface, with which every other part of code interacted with GlobalData. Instead of having everything publicly available, I turned everything into private and created get and, where necessary, set, methods to allow other functions to interact with GlobalData. This way, instead of writing things like

GlobalData.Instance.loadNext=GlobalData.Instance.allLevelData[GlobalData.Instance.allPlayerData[GlobalData.Instance.activePlayer].selectedLevel].name;

I can just do

GlobalData.Instance.SetActiveLevel(GlobalData.Instance.GetActivePlayer().activeLevel)

which is noticeably easier to read and write. This also forbids getting direct access to anything that is not intended to be changed in current context, or at all.

After doing all that, I realized that there isn’t much left to do. As soon as everything was condensed into a PlayerData, I just had to make a CreatePlayer() function and use it accordingly. Now I could add new players, or select existing ones with one click. Everything concerning levels was safe behind a get {} method, and the same could be done with achievements and other things if needed. With that in mind, I quickly created a login menu and decided to see if things worked, which they didn’t.

It was time to figure out a proper way to load stuff, now that everything depended on PlayerData to initialize, and PlayerData partially depended on LevelData. After playing around for a bit, I decided to put actual initialization into the code for a Login Menu. Every possible function already existed in GlobalData, and those which didn’t could be easily created without interfering with other code. End result was compact and promising. If there was no player at all, it set every setting to default and only allowed the player to create a new one. Code for that was already in place in GlobalData. If there was an active player, it grabbed settings from him and then allowed the player to either continue, create new, or load a different profile. This way, GlobalData only loaded thing from files on awake, and everything else was done by a login menu, thus avoiding any conflicts.

Things that still require refactoring are: the Game Manager, which is non-existent, the Achievement Observer, which is also non-existent, a proper Login Menu, with a fancy list of players, and many other things. Sadly, the Level Select Menu also broke because of the refactor, but it is an easy fix, hopefully. Other than that, the game is already playable and it is possible to just sell it as is, if provided with good-looking 3D assets and sound, of course.