Tuesday, March 29, 2016

Unity Project Organization: MasterControl and SceneControl

As I said in the previous post the way I design my scenes revolves around the MasterControl and SceneControl classes.

The MasterControl is the omnipresent 'god object'; it will persist through every scene in the game.

A SceneControl object is the controller for a single scene.  This is where you'll find the logic for setup/teardown of the scene, as well as data that needs to be centralized or persisted during the scene.



While the logic for each scene is of course different, every scene control object derives from SceneControlBase.  Being the base for every class, it has about what you'd expect:  a protected reference to MasterControl.Instance as a constantly useful shortcut,  some scene switching boilerplate for networked games (otherwise commented out), and finally a startup check.

The final one I consider the most important.  A tip I read (which I think I reference in an earlier post) was 'keep your game playable from every scene'.  Previously I'd realized that it was really a hassle to be working on scene X, then have to find my Startup scene, load it, run it, then test scene X.   Wouldn't it be nice to just run and test scene X?

Well, if you're doing a Flappy Birds clone or the like, maybe you could.  But in anything more complicated there's a lot of state and setup that needs to be handled.

So the next best solution is to check for things that should be there (the MasterControl object) and if it isn't, automatically load the startup scene.

From BaseSceneControl:

        
protected virtual void Awake()
            {
            //Messenger.AddListener("PlayerControlChanged", OnPlayerUnitChanged); 
            //Messenger.AddListener("EntityCreatedFromNetwork", OnEntityCreatedFromNetwork); 

            StartCoroutine(CheckForStartUp());
            }


 private IEnumerator CheckForStartUp()
            {
            //This is only used if we're in the editor and
            //trying to start from a scene that isn't the
            //base startup scene.  It's a lot easier working
            //in scenes and having it autoload the startup
            // (as opposed to having to manually switch to
            // the startup scene, then run the game).

            yield return null;

            GameObject mc = GameObject.Find("MasterControl");
            if (mc == null)
                {
                Debug.Log("Force loading Startup scene.");

                SceneManager.LoadScene("Startup");
                }
            }


Why the coroutine?  Well when I upgraded to... well I don't remember the version.  5.2?  5.3?  Whichever, -the one where they made the changes to add the SceneManager and multiscene loading and editing, forcing a scene load in the Awake stage caused frequent crashes.  It wasn't every time, something less than fifty percent of the time.  That's obviously unacceptable, of course, and I was never able to figure out why it was crashing the entire system, editor and all.

However deferring the check and load till the end of frame did get rid of that particular problem.  It does, however, cause errors to appear in the log, as many object look for items that don't exist (i.e. MasterControl, etc.) and throw exceptions when they don't find them.

But those are transient as we then immediately load out of the scene into Startup and the game begins normally.

And since this is just for in-editor convenience, it isn't a big deal (in fact, though, I hunted down all the errors and made them fail nicely since I was tired of seeing errors in the log every time I started the game from certain scenes).

I'd say this is a huge win for just the convenience factor alone.


1 comment: