Posted October 07, 2023 by JeremiahFieldhaven
#design
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:
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:
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.