Is this Game State design advisable?

There seems to be quite a bit of misinformation out there, so I thought I’d start my own thread to try to nail this down.

I have been researching best practices on how to handle generic game states that I’ll call RoundSetup, RoundStart, and RoundEnd. My goal is to implement a design that is reusable in a variety of games, so the design should not be coupled to GameObjects in any particular scene.

Criteria:

  • Decoupled from other objects in scene
  • Allow waiting for dependent processes on other GameObjects to complete, then change states
  • Allow for another GameObject to change the state

The design I have in mind is something like this, where the blue boxes represent criteria 2 and 3 above:

My questions:

  • Is a design like this advisable?
  • If so, could someone point me in the right direction to go about implementing this design?

Based on my research, my hunch is that the implementation will have something to do with a finite state machine, but accomplishing that in code is hazy to me. I realize that the Animator is a state machine though, and I have some experience with that.

Oh, and one of the many implementations I’ve looked at is in the Unity TANKS! Tutorial: Game Managers - Unity Learn

My issue with this approach is that the GameManager is coupled closely with other GameObjects in the scene…

Sure the design so far seems feasible and logical. But this is only like the first step of the design… I wouldn’t really even call it a design yet (in a software sense), it’s more of a concept so far.

As for the right direction…

Actually, lets rewind, there’s one part in your idea that I’m not understanding. You don’t like how it’s coupled closely to GameObjects in the scene… in what way do you mean you want that decoupled?

I’ll share this:

Here I have the basic foundation of my SPSceneManager (it’s sort of newish to my framework, and not complete).

With it you kind of get a few of the steps in your design.

How this works is that the SPSceneManager deals with loading and unloading scenes. Scenes are encapsulated in a Scene object to give them identity (this idea came about before unity released their new SceneManager… and I only barely refactored to it).

You then create ISceneLoadOptions (there’s default implementations, or you can write custom ones), which are what you hand to the SPSceneManager to load new scenes. These load options are used because the scene hasn’t loaded yet… it’s basically just a callback object that allows you to deal with the inter-scene events. Note how it has events for:

OnBeforeSceneLoaded
OnSceneLoaded (like your RoundSetup)
OnSceneStarted (like your RoundStart)

When it loads the scene, a scene also has a ISceneBehaviour. This is a script that runs for the duration of the scene, and is signaled to when the scene starts, and when the scene ends. Here is where I write my scene logic (like a GameManager, but for specific scenes). It’ll usually contain the logic for pausing, exiting the scene, etc. It’s often what actually ends up calling back to the SPSceneManager to tell it to transition to the next scene.

Thanks for the reply, lod.

Well, in the Tanks tutorial the GameManager requires references to TankManager, CameraControl, a Text component, and a Tank prefab. To me, it seems a better solution would involve the idea that the GameManager would not care whether those objects are in the scene—instead, the GameManager would change the state of the game and those objects would respond accordingly (if at all). Does that make sense?

(On a related note, I am familiar with C# events and use them quite a bit in other game logic; they might have a place here. In this case I’m just stumped on how to put all the pieces together in a way that makes the game state system reusable and easily expandable.)

I’ll take a look at the resources you posted.

Unfortunately this appears to be beyond my level of understanding. :frowning:

I’ll keep at it, but right now my main takeaway from this is that implementing something analogous to ISceneBehaviour might be necessary. Am I in the ballpark? (Or maybe I’m somewhere in the ballpark’s parking lot—ha!)

Note, that code is not documented as of yet… like I said, it’s newish… it’s really in flux a lot as I hammer out the kinks in the design (note how several classes are commented out completely as I change things around).

Here’s the thing, abstracting a design this far back is HARD, and it takes time and experience to come to it.

Honestly… it’s more iterative than anything.

So like, that SPSceneManager was developed over several games as I noticed things I repeatedly kept needing to do in each game (and I’m still doing…). I wanted a hook here, and a hook there, and a blah blah blah. So the design kept growing around my games as I noticed the common generality from game to game. A common generality I wouldn’t have known without making several games… I don’t know what my workflow is until I’m working it.

But there in lies another problem… it’s MY workflow. Something like my framework is hinged around a workflow that I like. Other people may not like it, and have their own workflow in mind instead. Which is why my framework isn’t exactly adopted by tons of people…

You might even wonder why the heck I have it publicly available like I do… and honestly it’s just there for people to pick apart. Example code. When I was learning, I liked digging through other people’s code just to get a feel for how they design things, and the common standards they use. I wanted to see if I could identify those damn ‘design patterns’ everyone kept talking about.

So…

Slow down a little. Take a deep breath. And start working your workflow. Start making your game and write code that sort of fits into the needs you’ve outlined with your ‘design’. Adhoc it where you have to, glue it together with ducttape and superglue. And get a game made…

then iterate your design

Do it again, try to reuse the code from the previous project, and you’ll start to notice the weak points. It’ll be the parts that don’t fit into your new project nicely without ripping the code apart.

A framework like this should be easily coded against, without needing to code inside it.

You should be able to inherit from classses and override methods, or implement interfaces, or call functions, to get it integrated into your next project. But if you find yourself having to re-enter your frameworks code and fiddle with the inner bits… that’s bad. That’s a weak point. Figure out how to refactor that into the interface of your framework so that next time you don’t have to fiddle inside it like that.

1 Like

tl;dr

your design hinges on your workflow

a workflow that is both too complicated for us to explain concisely in a forum post, and too unique to you and the way you work that we would only be describing how WE would do it, not how you should

so, just do it, try to build it, and see where it breaks

I appreciate the words of advice. And not too long; did read. :wink:

I guess after making a couple of games with spaghetti code, I have started to be able to recognize code with that smell. So now I am trying to figure out a way to implement what seems to be a possible solution (in my OP), but need to be pointed in the right direction (and helped to avoid the spaghetti systems floating around out there). Plus, I sense that a little book-learnin’ in this area would serve me well now and in the future—I just need some guidance along the way.

Maybe I’m further away than I think I am, but I’m not yet ready to hack something together that has implications for much of my project… been there, done that. If I find that I simply cannot proceed with reasonable effort, I’ll give in at that point.

I’ll keep at it and post my progress, but some more guidance at this point might save me a ton of time!

Well, where are you at knowledge wise?

What sort of code have you written in your previous games to deal with the above problem you’re trying to solve. Maybe show us, we can then give you some direction based on that. Knowing where you are currently goes a long way in pointing you where to head next.

The two scripts here are good representations of where I’m at: https://unity3d.com/learn/tutorials/projects/tanks-tutorial/game-managers?playlist=20081

In other words, I fully understand the concepts/design here and this is probably what I would do if I didn’t push my understanding at this point. But I think I can also see their shortcomings and I’d like to try to learn a better way.

Going back to my original diagram, to give you an idea of where I think I need help, I’ll take a quick stab at what I think I need to do:

  • GameManager

  • Tracks what state the game is in

  • Broadcasts that the game state has changed

  • (Broadcasts likely to be C# events — very comfortable with these)

  • Has a bit of other game data, like the number of rounds played

  • (Should be a singleton? — relatively new to me)

  • Other GameObjects

  • Subscribe to game state change events

  • Do stuff

  • Ability to tell GameManager to change to a specific state

  • (Likely calling a GameManager method to do this)

  • Ability to meet some conditions to signal to the GameManager to change game states

  • Other GameObjects also have conditions

  • When all conditions of all relevant GameObjects are met, GameManager will then change the game state

  • (Not sure how to implement this)

That’s a quick sketch. Constructive criticism okay, or happy to explain more.

A lot of that looks good.

Singleton -
this can be very useful, as long as you understand its short falls. Really a Singleton is a lot like a static class, only that it has object identity (it can be referenced as an object, useful for functions that need to take a reference to something). So just like a static class, it means you have a ‘global state’, which is considered a giant no-no to some people.

Thing is, in game design… we cut corners sometimes and forego into no-no land, because to not is a lot more leg work.

And the thing is, if your requirements are a single point of authority, and you must maintain that single point… then doing something else is just as prone to problems as well.

And for something like a GameManager, where there is really only ONE game ever… it’s a completely legitimate design approach.

So yeah, Singleton away with it. Heck, I use a Singleton for my SPSceneManager as well.

Dispatching events -
C# events are decent for this as well.

The Singleton for the GameManager makes it an easy way to reference it and register for said events.

Only downside is that you can’t really register for the event until Awake or Start, which means depending ordering, you might fail to register for the event before it’s actually raised. So make sure you plan for this… you can’t let the GameManager raise the event until you know for certain that the scene is loaded and ready to receive the event.

Ability to meet some conditions to signal to the GameManager to change game states

This one here I’m not sure about.

So I’m assuming this is for between ‘SETUP’ and ‘START’. A sort of ‘loading’ time.

And the thing is that loading could take a while, several frames, if it has to do things like asychronously load data from disk or the web or something.

This part of the design can be a bit tough.

So in my SPSceneManager you’ll notice I have some code that does this:
https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/Scenes/SPSceneManager.cs

In LoadScene I do this:

        public IProgressingYieldInstruction LoadScene(ISceneLoadOptions options)
        {
            if (options == null) throw new System.ArgumentNullException("options");

//here I'm just canceling out any previous loads... this is incase someone tries to load a new scene mid load
            if (_loadingOp != null)
            {
                _loadingOp.Cancel();
                if (!_loadingOp.NextSceneStarted) _currentSceneBehaviour = null;
                _loadingOp = null;
            }

//start the load process
            _lastSceneBehaviour = _currentSceneBehaviour;
            _currentSceneBehaviour = null;
            _loadingOp = new WaitForSceneLoaded();
            _loadingOp.Start(this, options, _lastSceneBehaviour);
            return _loadingOp;
        }

I create a process called ‘WaitForSceneLoaded’, and it’s a nested class in the SPSceneManager, and it does this:

private class WaitForSceneLoaded : RadicalYieldInstruction, IProgressingYieldInstruction
        {

            #region Fields

            private SPSceneManager _manager;
            private ISceneLoadOptions _options;
            private ISceneBehaviour _lastScene;

            private IProgressingYieldInstruction _loadOp;
            private bool _started;

            private RadicalCoroutine _routine;

            #endregion

            #region Properties

            public SPSceneManager Manager { get { return _manager; } }

            public ISceneLoadOptions LoadOptions { get { return _options; } }

            public ISceneBehaviour LastScene { get {return _lastScene; } }

            public bool NextSceneStarted { get { return _started; } }

            #endregion

            #region Methods

            public void Start(SPSceneManager manager, ISceneLoadOptions options, ISceneBehaviour lastScene)
            {
//set the state variables needed during loading
                _manager = manager;
                _options = options;
                _lastScene = lastScene;
//run the coroutine that loads stuff
                _routine = manager.StartRadicalCoroutine(this.DoLoad()); //GameLoopEntry.Hook.StartRadicalCoroutine(this.DoLoad(), RadicalCoroutineDisableMode.Default);
            }

            public void Cancel()
            {
                if (_routine != null)
                {
                    _routine.Cancel();
                    _routine = null;
                }

//this is because this can be used async, signals are for thread merging
                this.SetSignal();
            }

            private System.Collections.IEnumerator DoLoad()
            {
//allow the last scene to do any extra clean up it may need to do before loading the next scene
                if(_lastScene != null)
                {
                    //end last scene
                    var endInstruction = _lastScene.EndScene();
                    if (endInstruction != null) yield return endInstruction;
                    if(_manager._lastSceneBehaviour == _lastScene) _manager._lastSceneBehaviour = null;
                }

//the event args for communicating back and for between this and handlers
                var args = new SceneLoadingEventArgs(_manager, _options);
                object[] instructions;

                //signal about to load
                _options.OnBeforeSceneLoaded(_manager, args);
                _manager.OnBeforeSceneLoaded(args);
//here we get all the yield args from the handlers, so that we can wait until gameobjects in the scene are done
                if (args.ShouldStall(out instructions)) yield return new WaitForAllComplete(GameLoopEntry.Hook, instructions);

                //do load
                var scene = _options.GetScene(_manager);
                _loadOp = (scene != null) ? scene.LoadAsync() : RadicalYieldInstruction.Null;
                yield return _loadOp;

                //get scene behaviour
                var sceneBehaviour = _options.LoadCustomSceneBehaviour(_manager);
                if(sceneBehaviour == null)
                {
                    var go = new GameObject("SceneBehaviour");
                    go.transform.parent = _manager.transform;
                    go.transform.localPosition = Vector3.zero;
                    sceneBehaviour = go.AddComponent<SceneBehaviour>();
                }
                SceneBehaviour.SceneLoadedInstance = sceneBehaviour;
                _manager._currentSceneBehaviour = sceneBehaviour;

                //signal loaded
                _options.OnSceneLoaded(_manager, args);
                _manager.OnSceneLoaded(args);
                if (args.ShouldStall(out instructions)) yield return new WaitForAllComplete(GameLoopEntry.Hook, instructions);
                else yield return null; //wait one last frame to actually begin the scene

                //signal scene begun
                _started = true;
                var beginInstruction = sceneBehaviour.BeginScene();
                _options.OnSceneStarted(_manager, args);
                _manager.OnSceneStarted(args);
                if (args.ShouldStall(out instructions))
                {
                    var waitAll = new WaitForAllComplete(GameLoopEntry.Hook, instructions);
                    if (beginInstruction != null)
                        waitAll.Add(beginInstruction);
                    beginInstruction = waitAll;
                }
                if (beginInstruction != null) yield return beginInstruction;
                _manager._loadingOp = null;
                this.SetSignal();
            }

            #endregion

            #region IProgressAsyncOperation Interface

            public float Progress
            {
                get
                {
                    if (this.IsComplete)
                        return 1f;
                    else if (_loadOp == null)
                        return 0f;
                    else
                        return _loadOp.Progress * 0.99f;
                }
            }

            #endregion

        }

So really, this is a Coroutine… it’s a coroutine inside a class so that I can store some state information as well.

But when I call Start on it in LoadScene, really it’s just starting this coroutine and working its way through it.

The coroutine is the ‘DoLoad’ method inside ‘WaitForSceneLoaded’. It calls through to the ‘SceneLoadOptions’ so that it can do work (remember this is the object that actually tells the manager what/how to load). As each event occurs, it passes out ‘SceneLoadingEventArgs’ (standard C# event design here), which the handlers of said event can call the method ‘RequestManagerToStall’ on:
https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/Scenes/SceneLoadingEventArgs.cs

If no one calls that message, the manager moves on to the next step in the loading process. Otherwise it yields until any yield instructions that the handlers stalled on are complete.

So basically if any handlers of one of these events needs to load with a WWW or something, it starts a coroutine in the event handler, and it calls ‘RequestManagerToStall’ passing in the coroutine as the instruction to stall on. The manager will then stall for as long as those objects wait.

void OnBeforeSceneLoaded(object sender, SceneLoadingEventArgs e)
{
    e.RequestManagerToStall(this.StartCoroutine(this.DoLoad()));
}

void DoLoad()
{
    //load stuff from the internet
}

It does this for every intermediate load event.

OnBeforeSceneLoaded
OnSceneLoaded
OnSceneStarted

Now, of course you may be looking at my code and be wondering how the hell of all that greek of mine is doing that.

And this is because a few reasons…

  1. my code is heavily using my framework of tools, I have a lot of tools that do work for me, my way. If your not familiar with it, it might look weird.

  2. It’s heavily abstracted. I’m attempting to deal with every which unknown… honestly, when used, really only one of those events is really ever used for any logic. I just have them all there just in case I need to hook in somewhere weird.

1 Like

For your events system you can get some inspiration from Unity itself. About how unity handles its ubiquitous functions like Awake, Start, Update, OnDestroy, … etc. You could have your very own functions and have then registered and invoked by your game manager through reflexion. This way, your GameObjects and your game manager would be completely decoupled.

Your GameManager should be the one who is in control of the game and it should be responsible for handling its own states, the other game objects should not directly change the state of the GameManager. Though the GameManager would only receives data from other elements of the games and be able to query the data that it needs.

About singleton, that’s useful when you need an object to be unique and to be often referenced by other objects, which is the case for managers. In Unity I like to implement singletons has follow:

using UnityEngine;

public class MySingleton : MonoBehaviour
{
    static MySingleton _instance = null;
    public static MySingleton instance
    {
        get
        {
            if (_instance == null)
                _instance = GameObject.FindObjectOfType<MySingleton>();
            return _instance;
        }
    }

    public void DoSomething()
    {
        // Do something...
    }
}

Then I can use it from anywhere in the project like this:

MySingleton.instance.DoSomething();

About keeping track of your game states, there is indeed Finite State Machine (FSM) to handle state transitions. As you mentioned in your OP, it can become a real pain to manage an FSM in code. Even with a graphical tool, such as the Animator or Playmaker, a complex FSM (more than 5 states) becomes very fragile and easily breaks when modified.

In my personal experience, since I have discovered Behaviour Tree, FSM is a tool of the past. I wish I had discovered BT earlier, it could have save me a lot of hassle. I am very enthusiastic about BT. I’ve made a scripting tool based on this concept. Have a look here:
http://www.pandabehaviour.com/

You could use this for handling your game states. For illustration, @Kiwasi made a space invader clone to test out Panda BT (I was impressed how fast he got the grip on it), and he comes up with the following tree as game manager (original post):

tree "Root"
    Sequence
        FreezeGame
        Wait 120
        StartGame
        Race
            GameWon
            GameLost
        FreezeGame
        Wait 120
        RestartLevel

Though, you are not familiar with this type of script, I’m sure you’ve an idea of what this game manager does.

2 Likes

Thanks for the feedback, guys. I’ll dig into this tomorrow and report back.

I think the best way to do something like this is to use what’s called the “Observer” pattern: Observer Design Pattern

For example:

public interface IHasGameState
{
    bool isBusy;
    void EnterState(GameStateManager.State state);
}

public class GameStateManager : MonoBehaviour
{
    public enum State { RoundSetup, RoundStart, RoundEnd }
    private List<IHasGameState> stateObjects;

    public void Register(IHasGameState obj)    { stateObjects.Add(obj); }
    public void Unregister(IHasGameState obj) { stateObjects.Remove(obj); }

    public void ChangeState(State newState) {
        foreach(var obj in stateObjects) obj.EnterState(newState);
        StartCoroutine(WaitForStateReady());
    }

    public IEnumerator WaitForStateReady()
    {
        while(stateObjects.Any(o => o.isBusy)) yield return null;
        Debug.Log("Everything is ready now!");
    }
}

public class SomeGameComponent : MonoBehaviour, IHasGameState
{
    public GameStateManager manager;
    public bool isBusy;
   
    public void OnEnable() { manager.Register(this); }
    public void OnDisable() { manager.Unregister(this); }

    public void EnterState(GameStateManager.State state)
    {
        if(state == State.RoundSetup) isBusy = false;  //Don't need to do anything in this state
        else if(state == State.RoundStart) StartCoroutine(DoSomething());
    }

    public IEnumerator DoSomething()
    {
        isBusy = true;
        while(doingABunchOfStuff)
        {
               DoSomeStuff();
               yield return null;
         }
         isBusy = false;
     }
}

Using the Observer pattern lets you keep everything decoupled; the game state manager doesn’t need to know what it’s managing, and the objects don’t need to know about each other. If you wanted, you could make the GameStateManager a singleton like mentioned above, or you could find it some other way like using GameObject.Find or just putting a reference to it in a prefab in case you want different versions of it.

1 Like

Thanks for taking the time for all these posts. I’ve read all of them and they’re helping me—thanks. More feedback to come, but for now…

I’ve been working on the approach last mentioned, but I’m getting this error on the following code: Interfaces cannot contain fields or constants

It looks like I need a workaround, but since I’m not all that familiar with interfaces I’m unsure of the best approach given the usage in GameStateManager. Any tips?

Oops, right, you need to make it either a method or a property. Try:

bool IsBusy {get;}

and then in classes that implement the interface change “public bool isBusy;” to

public bool IsBusy {get; private set;}

I should add that the end of my code (the method DoSomething) won’t actually compile if you just copy and paste it; that’s meant to be an example of where you’d put your code that actually does something. :wink:

I’m on top of that one haha. Actually feeling pretty comfortable with the rest of it. Updates soon.

‘GameStateManager.stateObjects’ is never assigned to, and will always have its default value ‘null’

Anything that can be done about that or am I doomed to see that warning forever?

This is what I get for just writing code in a post rather than checking it in the actual editor first. :stuck_out_tongue: That should be:

private List<IHasGameState> stateObjects = new List<IHasGameState>();

Or you could give it an Awake or Start method and assign it a new List there. Either would work.