Design - from startup to the main menu


While slowly working at getting acqainted with alternative engines, I’ve decided that it probably makes a some sense to document the structure of Project Shinar as I’d implemented it within Unity. This is partly so that I can actually disentangle what I did, and partly to help with the next implementation.

Fair warning that this and other posts that may follow will be somewhat rambly!

The core of the game is, fittingly, the Master Control Program (the MCP). Pretty much every game has a game manager somewhere inside it, and in the case of Project Shinar, that’s the MCP. This is a singleton class that marks itself as DontDestroyOnLoad() on Awake(), and that happens pretty much as soon as the main menu scene is loaded when the game starts:

namespace Shinar
{
    /// <summary>The MCP coordinates the overall behaviour of the game,
    /// including managing the current scene, updating cameras, and
    /// providing a means for different subsystems of the game to talk
    /// to each other.
    /// </summary>
    public class MasterControlProgram : MonoBehaviour
    {
        // ... stuff here ...
    
        private void Awake()
        {
            Debug.Log("MCP is awake!");
            Debug.Log("MCP version is " + Version.GetVersionString());

            // If the MCP has already been created, delete the new imposter MCP.
            if(Instance != null && Instance != this) {
                Destroy(this.gameObject);

            } else {
                // This is the first MCP, so make it the only one there can be
                Instance = this;
                DontDestroyOnLoad(gameObject);

                // Let the initialiser handle actual setup
                Init(CurrentState);
            }
        }
        
        // ... and more stuff here ...
    }
}

From this point on, the MCP runs the show: it manages the overall state of the game, transitions between different scenes, and it provides an accessor so that objects throughout the game can contact the MCP and call methods in the various manager objects as needed. During initialisation the MCP creates several manager objects to handle aspects of the game:

  • WorldManager - the world manager coordinates the actions of the WorldLoader, based on the sort of scene the MCP has loaded.
  • WorldLoader - handles all aspects of loading JSON level descriptions, building level contents, keeping track of the in-memory representation of the world state, and talking to PlayerManager to handle the player’s state. The logic to build levels is also heavily reused to load the backgrounds for menu screens: effectively the main menu, zone selection menu, and level select menu are just special forms of level, with almost all the same systems as a playable level. This allows the backgrounds to have robots and NPCs moving around and affecting the background while the player interacts with the menus.
  • CameraManager - provides methods to locate the main camera and skybox camera in each scene, and keep track of the bounding box used to constrain the main camera if it is moveable. Each scene has its own main and skybox cameras because different scenes have different requirements on camera behaviour, so CameraManager is used to look for the cameras each time a scene is loaded by the MCP.
  • PlayerManager - keeps track of the current state of the player once a save game slot is selected on the main menu. It handles reading and writing save game data.
  • MusicManager - has an array of audio sources, and uses them to smoothly transition between audio clips at scene changes (with the eventual plan being to allow the game to switch between slightly different variations of music depending on which robot is currently active, too)

The MCP also has the master list of LevelComponents. Every object that can be created as part of level loading is a LevelComponent, and the MCP has the list of known LevelComponents that it can pass to the WorldLoader to use as a lookup table when converting JSON level descriptions into spawned game objects.

As you can guess, there’s a flurry of things happen during startup, but essentially the process is:

  1. Game startup and Unity splash screen stuff
  2. Main menu scene loads, and the MCP Awake() is called
  3. MCP intialisation begins
  4. MCP creates the CameraManager
  5. MCP creates the WorldManager
  6. WorldManager creates the WorldLoader and tells it to load the zone file, cache level metadata, and convert the MCP’s LevelComponent list into a hash keyed off component names to make level building more efficient.
  7. MCP creates the PlayerManager
  8. MCP sets the game state to “Main menu” which triggers the CameraManager to locate the cameras in the scene, and WorldManager to tell WorldLoader to load a randomly chosen main menu background level and instantiate all the level components defined in it.

But wait, there’s more! All of this happens in MCP’s Awake(), but there are more moving parts involved - you might have noticed that there’s nothing there about starting the music, for example.

Each scene has its own dedicated manager in a separate object (don’t tell the MCP…) - the main menu has the cunningly named MainMenuManager, the level selection scene has the LevelSelectManager, the play level scene has PlayLevelManager and so on (naming things is hard). These handle scene-specific stuff, including things like asking the MCP to change the music. The scene-specific managers also ask the MCP to change the game state and load a different scene in response to appropriate events.

Concentrating on the main menu for now, in the MainMenuManager’s Start() method, it asks the MCP to start playing the main menu music, and at this point the FadeBlock overlay in the UI canvas, which starts out black, starts the process of increasing its alpha channel - becoming more transparent.

As soon as the FadeBlock is completely transparent it tells the MainMenuManager that it has completed the fade, which then triggers the process of showing the main menu UI via several animations. At this point the MainMenuManager also sends a signal to any robots that might have been instantiated in the menu background level to start following any orders they were given in the level file - this is how the robots can move around and do things while the player is interacting with the menus. They’re really just using the exact same order queueing system the player uses when playing a level, except the orders are pulled in from the level file rather than defined by the player.

So, this is essentially the process to go from startup to a presenting the player with the main menu. Next is the process when the player selects a save slot, or switches to the level designer.

Leave a comment

Log in with itch.io to leave a comment.