Search Unity

How do you deal with Game States?

Discussion in 'General Discussion' started by Nanni, Sep 9, 2019.

  1. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    Hello!

    I've been struggling with Game States for a few months between different projects and thought of asking here. I seem to always overcomplicate this, but at the same time if I try doing it in a simple way I feel like I won't have much control and it get messy very soon.

    How do you deal with it? I know it's a very broad question, so I'll try to explain my difficulties and what I tried.

    • Sometimes, specially during game jams, I use enums for each State in a GameManager script and it gets the job done. I write a ChangeState method, which receives newState as a parameter, calls an ExitState method, updates the currentState variable to the newState, then calls an EnterState method. ExitState and EnterState both have a switch(currentState) and a case for each GameState with the code I want to execute. The problem I have with this is that for a larger game (not big, but larger than a game jam one) it will become a mess to have a switch case for each state, specially since I keep trying to make EVERYTHING a state and will probably add more as the development progresses - SplashScreen, LoadingMenu, MainMenu, OptionsMenu, ConfirmQuitGame, LoadingMatch, MatchSetup, Match, EndMatch, GameOver, Results, and so on...

    • I tried applying what I learned from the PluggableAI tutorial, but instead of using the States for AI, I used for the Game States. So, each State was a ScriptableObject and they each had their own actions (like LoadLevelGeometry, SpawnPlayers, FadeOut, ...) that I could rearrange. THe problems I had with this were mainly due to programming inexperience - it was extremely hard for me to give the actions the parameters and references they needed to work with and also some actions needed to happen over time or wait for them to be finished before going to the next action, which I couldn't do with the ScriptableObjects.

    • Now, I'm trying to make a GameManager GO with a GameManager script that has references to individual scripts that inherit from GameStateBase. There will be a script for each GameState and all of them will be components in the GameManager GO. GameStateBase has EnterState, ExitState and ExecuteState methods. GameManager will have a currentState variable with refeference one of the GameState scripts and will call the methods inside them, overridden from the GameStateBase. My problem with this is - where do I write the "actions", the steps that each state needs to go through, their routines? In the GameManager class and the GameStates will call them? Or inside each state, which can be a problem, because the same step might need to happen in different states?

    I'd like to know if someone can point me to a better way, suggest someting to improve what I tried or explain a different way that was used for a finished project, please. Every time I try to start a game, I get stuck trying to make a better, I don't know, architecture (?).

    I can show codes of my approaches if it is of any help! Thanks!
     
    Ony likes this.
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,149
    For my current project the main state machine in a scene is only responsible for enabling/disabling managers for each state. From there the manager either has its own state machine or something far simpler that handles just that state.
     
  3. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    This is basically what I do. Also its just preference but I prefer a long if-else chain to a switch statement, I find it far more readable tbh.

    I just really hate the way switches look! But that is preference that is purely based on opinion so may or may not help OP.
     
    Ony and Nanni like this.
  4. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Personally, my game state controllers are never responsible for invoking logic outside of changing states. Basically they're just a Dictionary<State, List<State>> where the values are the allowed transitions for a given key.

    When the state is changed, the state controller emits events and the other objects just handle whatever they're responsible for handling accordingly.
     
    aer0ace, angrypenguin and Nanni like this.
  5. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    Thanks for helping, Ryiah!

    So, you have a manager for each state that are activated/deactivated by a main state machine. What do you do with code that might happen in more than one state, like fading between scenes? Would you mind giving some examples of some of the states your games usually have? As I said in the thread, I tend to create A LOT of states for every little thing, which might not be so useful, so it'd helpful to see how deep the rabbit hole goes for others.
     
  6. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    Yeah, I can understand it! ;) I got used to switch, even though I dislike having to write "break" between each case. It would be better if the compiler understood that when a new case starts, the previous case ends. (I might be using the wrong words because I'm not exaclty a programmer, I'm just trying to make a game :p)
     
  7. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    Hey, AntoineDesbiens, thanks for explaining me your method! I never used Dictionary for states (in truth, I'm fairly new to them). Could you please elaborate more on how you make a Game State Controller with it and what exactly are those transitions? If I understood you correctly, each state knows to which states it can change to and when?

    I can't seem to figure how to do it that way. :/ My GameManager is usually a Singleton and when something happens outside that should change the GameState (like clicking the "Start Game" button on the main menu or when the "EnemyManager" identifies that the player killed every enemy), it calls the ChangeState() method in GameManager.

    Also, I've tried using events to run each state routine, but I couldn't sequence things correctly, specially when i needed to wait before going to the next step - like waiting for the fade to complete or the camera to finish zooming in, for example. With this, i mean that GameManager triggered every event needed for each state, which sounds wrong now typing it. :p In your approach, the GameManager triggers some event like "StartMatchState", "StartMainMenuState" and each respective controller listens for it and runs its own routines?

    Sorry for so many questions, it's just that I'm starting to make my first game as a product after a few years making experiments, prototypes and jam games and I'm a little tired of knocking my head with this. I've read game programming patterns books, watched many youtube videos, but couldn't find anything that I, in a below intermediate level, could understand fully and apply that gives me control and flexibility.
     
  8. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    The compiler cannot implicitly break out of cases because it's possible to have multiple cases perform the same action:
    Code (CSharp):
    1. int x = 2;
    2.  
    3. switch(x) {
    4.    case 0:
    5.       Debug.Log("0");
    6.       break;
    7.    case 1:
    8.    case 2:
    9.       Debug.Log("1 or 2");
    10.       break;
    11.    case 3:
    12.       Debug.Log("3");
    13.       break;
    14. }
     
    AlejUb, Nanni, Socrates and 1 other person like this.
  9. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Our game is a multiplayer game and we have a gamemode base class. Each game mode subclass is just a scriptable object with some special methods and behaviors on it. THis class handles everything from spawning players to game flow. Most basic game modes dont have that much flow. Like team deathmath, it dosnt mutate during its life. If they have flow its handleded by modules rather than from one god object. For example on search & destroy. The C4 system handles its state itself. AI is an entire behavior tree using its own system, etc, etc
     
    Nanni likes this.
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I think your concept of "game state" is far too fine grained, which is making this a fiddlier problem than it needs to be. In fact, rather than game states it sounds to me like you're just listing screens. Do splash, loading menu, menu, options menu and your quit dialog all really need their own state tracking? To me that's all just the main menu, which has a few screens it can display.

    My game states are things like Playing, Paused, Upgrading, Loading. Each of those might have a bunch of internal states that they can be in, but not all of that stuff has to be handled by one monolothic state manager. Furthermore, it's far easier to manage if each state manages its own relevant substates.
     
    frosted and Nanni like this.
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    My current project doesn't even have a state manager. Everything is just handled by events being emitted. Anything that cares about the game being paused or not just listens for that event... and so on for everything. I thought it was going to be a mess and a pain because I'm used to that kind of thing being rigidly and centrally controlled, but I experimented with it on a small project and everything just worked, and so far that has borne out on a much larger project, too.
     
    Nanni likes this.
  12. iamthwee

    iamthwee

    Joined:
    Nov 27, 2015
    Posts:
    2,149
    Indie Game states can be summarised below:

    Unfinished but the moxie is there
     
  13. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    I had no idea about that! o.0 Thanks!
     
  14. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    So, the game mode subclasses run their specific code at the beggining of the match and then each module has its own behavior and states. Sounds very simple and easy to add/edit/remove modes, which is awesome for a multiplayer game!

    Thanks for your input! Like angrypenguin said, I might be complicating things in my mind, so it's great for me to know about different (and better) approaches.
     
    Last edited: Sep 13, 2019
    AndersMalmgren likes this.
  15. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    What you said about having less and broader states and each one having substates might be exactly what I need. ;) Since this will be my biggest project yet, I keep trying to break things in smaller parts, which sounds great on paper, but since I'm not very experienced it's only making it harder to work it through my mind.

    To give it some context, here's my game's gameplay loop: there is a farming phase, a writing phase and a defense phase.

    They're not complex, so the farming isn't like Stardew Valley or something like that. During this phase, you control your character with point and click controls through a farm "two screens wide" with fixed structures. You have action points (the only resource) to spend on the farm structures.

    The writing phase will be about selecting pre-written texts to build a diary for the character. The options will be selected randomly based on the actions you performed during the farm phase, among other sources.

    The defense phase will kind of like a card game, but without turns - the player sees the available cards that the enemy has, but doesn't know which one he's gonna choose and needs to select cards that give the best defense against the enemy ones. Then, it plays automatically - the enemy advances through the player cards and tries to overcome it.

    Following your suggestion, I think I can have one state for each phase and inside these states I can have substates. And maybe try to reduce the amount of states for menu and other stuff outside the game. You also said in your answer below that you only use events, I'd like to know more about that, if possible. I'm not sure if I should use this thread or DM or e-mail you, if you don't mind, of course! ;)
     
  16. GoesTo11

    GoesTo11

    Joined:
    Jul 22, 2014
    Posts:
    604
    My project is several years old at this point. I am running a StateManager that I modelled after a book I read. The StateManager is attached to a game object that is set to don't destroy on load. Each state is just a script. The state manager calls the appropriate part of the state with code such as:

    Code (CSharp):
    1. void Update()
    2.     {
    3.        
    4.         if (activeState != null)
    5.             activeState.StateUpdate();
    6.     }
    And state can be switched with:

    Code (CSharp):
    1. public void SwitchState(IStateBase newState)
    2.     {
    3.        
    4.         var oldState = activeState;
    5.         activeState = newState;
    6.         oldState.OnDisable();
    7.     }
     
    Nanni likes this.
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Check out the documentation for UnityEvent and look up event based programming in general. I've also written about it here a few times before, so searching the forums for my username + UnityEvent might get what you're after.

    But... walk before you run. The manager based approach can get great results and is much more approachable to begin with.
     
    Nanni likes this.
  18. Deleted User

    Deleted User

    Guest

    Nanni likes this.
  19. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    I use coroutines as micro state machines all the time. Though I dont see the need for a state variable like you have when you are dealing with coroutines. I let the routine itself be the state handler.

    I have created some helper methods though, for example, you can execute a sequene of routines like

    Code (CSharp):
    1.         public override IEnumerator Execute()
    2.         {
    3.             yield return Execute(WaitForAttachmentGrab, WaitForAttachmentHoveringOverRail, WaitForPlacementOnRail);
    4.         }
    And then from within a routine you can restart it etc

    Code (CSharp):
    1.         private IEnumerator WaitForAttachmentHoveringOverRail()
    2.         {
    3.             var firearm = Get<Firearm>();
    4.             railSystem = firearm.GetComponent<RailSystemContainer>().RailSystems[RailIndex];
    5.             attachment = topItem.GetComponent<RailSystemAttachment>();
    6.  
    7.             ShowPopup(railSystem.transform, "Hover over the rail with the attachment.");
    8.  
    9.             while (attachment.AttachedSystem != railSystem)
    10.             {
    11.                 if (attachment.IsCompletelyAttached)
    12.                 {
    13.                     yield return Execute<DetachAttachmentStep>(step => { step.Attachment = attachment; step.CorrectAttachmentPlacement = true; });
    14.                     yield return SequenceState.RestartCurrent;
    15.                 }
    16.                 else
    17.                     yield return null;
    18.             }
    19.         }
    20.         private IEnumerator WaitForPlacementOnRail()
    21.         {
    22.             ShowPopup(railSystem.transform, "Slide the attachment over the rail until you find a good position and let go.");
    23.  
    24.             while (!attachment.IsCompletelyAttached)
    25.             {
    26.                 if (attachment.AttachedSystem != railSystem)
    27.                     yield return SequenceState.Previous;
    28.                 else
    29.                     yield return null;
    30.             }
    31.         }
    32.  
    33.  
     
    AlejUb and Nanni like this.
  20. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Generally: don't over-engineer. If this is your first time dealing with game states, or your last time dealing with game states, it's likely better to KISS and use a switch, right up front, no frills. Use an enum to control the switch so it all makes nice sense.

    So you can merely change the state at any point by writing state = State.MainMenu or state = State.Attack and so on. The switch takes care of it.


    TLDR: If you haven't done states before, use switch + enum for legibility. It seems like a special state machine is the best thing ever but it's not. You're just making it bloated when it doesn't need to be.

    Writing a bit more code that says exactly what it does on the tin is always preferable for finishing a project. Your bugs are less, your code is easier and cleaner to change. You end up coding the game and not the libraries for a mythical what-if scenario.

    Mythical what-ifs are for game engines and middleware like Unity. Our job is to keep it simple and ship.
     
    AlejUb, Nanni, Lurking-Ninja and 3 others like this.
  21. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Readability wise switch cases is just little better than else if blocks. And low readability equals bugs
     
    Nanni likes this.
  22. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    I don't have 'game states' in the large - but I do have 'mode' driven subcomponents.

    I tend to use these for more complex input schemes and it works very well IMO.

    Code (csharp):
    1.  
    2.     public enum InputOptions{
    3.       MoveAndAct,
    4.       MoveOrAct
    5.     }
    6.  
    7.     private enum ExecutionPhase{
    8.       Beginning,
    9.       OnPathBegin,
    10.       OnPath,
    11.       OnPathEnd,
    12.       OnActionBegin,
    13.       OnAction,
    14.       OnActionEnd,
    15.       OnCompleteBegin,
    16.       OnComplete
    17.     }
    18.  
    This is an example of the different execution phases after a user has clicked a destination for a unit. The execution has a movement phase 'OnPath' and an action phase 'OnAction' that occur once the movement portion has been completed.

    Code (csharp):
    1.  
    2. protected override void UpdateExecutePhase(){
    3.       var phase = m_ExecutionPhase;
    4.       var type = m_ArgType;
    5.       switch( phase ){
    6.         case ExecutionPhase.Beginning:
    7.           switch( type ){
    8.          
    9.             case ArgType.PathOnly:
    10.               m_ExecutingArg = m_PreviewArg;
    11.               m_ExecutingArg.Path.AssignPath( Character.AI, this );
    12.               LockSteeringIfLong();
    13.               m_ExecutionPhase = ExecutionPhase.OnPathBegin;
    14.               break;
    15.             case ArgType.PathAndTarget:
    16.               m_ExecutingArg = m_PreviewArg;
    17.               m_ExecutingArg.Path.AssignPath( Character.AI, this );
    18.               LockSteeringIfLong();
    19.               m_ExecutionPhase = ExecutionPhase.OnPathBegin;
    20.               break;
    21.             case ArgType.TargetOnly:
    22.               m_ExecutingArg = m_PreviewArg;
    23.               m_ExecutionPhase = ExecutionPhase.OnActionBegin;
    24.               break;
    25.           }
    26.           break;
    27.         case ExecutionPhase.OnPathBegin:
    28.           BeginExecutePath();
    29.           m_ExecutionPhase = ExecutionPhase.OnPath;
    30.           break;
    31.         case ExecutionPhase.OnPath:
    32.           UpdateExecutePath();
    33.           SceneParts.Get.PlayerPartingBlowManager.DrawPreviewZoC( Character, m_ExecutingArg.Path, m_ExecutingArg.Interrupts );
    34.           if( m_ExecutingArg.HasArrived )
    35.             m_ExecutionPhase = ExecutionPhase.OnPathEnd;
    36.           else if( (Character.AI.Destination - m_ExecutingArg.PathEnd ).HorizontalMagnitudeSq() > .01f ){
    37.             TryCancelExecution();
    38.           }
    39.           break;
    40.         case ExecutionPhase.OnPathEnd:
    41.           BeginPathEnd();
    42.        
    43.           break;
    44.         case ExecutionPhase.OnActionBegin:
    45.           BeginExecuteAction();
    46.           m_ExecutionPhase = ExecutionPhase.OnAction;
    47.           break;
    48.         case ExecutionPhase.OnAction:
    49.           UpdateExecutingAction();
    50.           if( Ability.IsExecutionComplete() )
    51.             m_ExecutionPhase = ExecutionPhase.OnActionEnd;
    52.           break;
    53.         case ExecutionPhase.OnActionEnd:
    54.           m_ExecutionPhase = ExecutionPhase.OnCompleteBegin;
    55.           break;
    56.         case ExecutionPhase.OnCompleteBegin:
    57.           BeginExecuteComplete();
    58.           m_ExecutionPhase = ExecutionPhase.OnComplete;
    59.           break;
    60.         case ExecutionPhase.OnComplete:
    61.           break;
    62.       }
    63.     }
    64.  
    This is the core phase management section. The code here isn't perfect, flawless code, and could certainly be improved, but it gives an idea of the structure I used here.

    The full implementation of the input/execution system is a lot more detailed, but I found that for managing really high detail, very specific stuff like this, this kind of organization was clear and easy to work with.

    The above was executed per frame and more or less centralized the logic branching.
     
    Nanni and Ryiah like this.
  23. Nanni

    Nanni

    Joined:
    Sep 12, 2014
    Posts:
    8
    Hey, just wanted to say that I'm really grateful for all your help!!! I'm still taking my time to read and understand each suggestion you gave me, but I just wanted to say thanks now! ;) The code examples are great too! I'm specially intrigued by the coroutine approach and the message system.

    Also, I appreaciate the tips given that aren't exaclty about game states, like keeping it simple and finishing the game instead of focusing on 'what-if' scenarios.